[client-vm] Build client VM CSR and sign the CSR with two keys

This cl builds the CSR that a client VM sends to the RKP VM for
remote attestation and adjusted the API accordingly as discussed
in the doc go/pvm-remote-attestation

The CSR payload is signed with both the CDI_Leaf_Priv of the
client VM's DICE chain and the attestation key. RKP VM should
verify the signature later with the CDI_Leaf_Pub extracted
from the same DICE chain in the CSR and the attestation public
key.

The new unit tests are added to config at cl/577763874.

Bug: 303807447
Test: run ServiceVmClientTestApp
Test: atest libservice_vm_comm.test
Test: atest microdroid_manager_test
Change-Id: Ic2c09e7339d9981edda028e2694fa551c911a274
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 1410534..77ccc1d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -105,6 +105,9 @@
       "path": "packages/modules/Virtualization/rialto"
     },
     {
+      "path": "packages/modules/Virtualization/service_vm/comm"
+    },
+    {
       "path": "packages/modules/Virtualization/service_vm/requests"
     },
     {
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 8710e54..93f49ef 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -46,10 +46,12 @@
         "libserde",
         "libserde_cbor",
         "libserde_json",
+        "libservice_vm_comm",
         "libthiserror",
         "libuuid",
         "libvsock",
         "librand",
+        "libzeroize",
     ],
     init_rc: ["microdroid_manager.rc"],
     multilib: {
@@ -70,6 +72,7 @@
     defaults: ["microdroid_manager_defaults"],
     test_suites: ["general-tests"],
     rustlibs: [
+        "libhwtrust",
         "libtempfile",
     ],
     multilib: {
diff --git a/microdroid_manager/aidl/Android.bp b/microdroid_manager/aidl/Android.bp
index 0aa8662..353e9cc 100644
--- a/microdroid_manager/aidl/Android.bp
+++ b/microdroid_manager/aidl/Android.bp
@@ -5,8 +5,12 @@
 aidl_interface {
     name: "android.system.virtualization.payload",
     srcs: ["android/system/virtualization/payload/*.aidl"],
+    imports: ["android.system.virtualizationcommon"],
     unstable: true,
     backend: {
+        java: {
+            enabled: false,
+        },
         rust: {
             enabled: true,
             apex_available: [
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index b9a7a64..51796f1 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -16,11 +16,17 @@
 
 package android.system.virtualization.payload;
 
+import android.system.virtualizationcommon.Certificate;
+
 /**
  * This interface regroups the tasks that payloads delegate to
  * Microdroid Manager for execution.
  */
 interface IVmPayloadService {
+    /** The constants STATUS_* are status code returned by this service. */
+    /** Failed to prepare the CSR and key pair for attestation. */
+    const int STATUS_FAILED_TO_PREPARE_CSR_AND_KEY = 1;
+
     /** Socket name of the service IVmPayloadService. */
     const String VM_PAYLOAD_SERVICE_SOCKET_NAME = "vm_payload_service";
 
@@ -33,6 +39,32 @@
      */
     const String ENCRYPTEDSTORE_MOUNTPOINT = "/mnt/encryptedstore";
 
+    /**
+     * An {@link AttestationResult} holds an attested private key and the remotely
+     * provisioned certificate chain covering its corresponding public key.
+     */
+    parcelable AttestationResult {
+        /**
+         * DER-encoded ECPrivateKey structure specified in [RFC 5915 s3] for the
+         * EC P-256 private key, which is attested.
+         *
+         * The corresponding public key is included in the leaf certificate of
+         * the certificate chain.
+         *
+         * [RFC 5915 s3]: https://datatracker.ietf.org/doc/html/rfc5915#section-3
+         */
+        byte[] privateKey;
+
+        /**
+         * Sequence of DER-encoded X.509 certificates that make up the attestation
+         * key's certificate chain.
+         *
+         * The certificate chain starts with a root certificate and ends with a leaf
+         * certificate covering the attested public key.
+         */
+        Certificate[] certificateChain;
+    }
+
     /** Notifies that the payload is ready to serve. */
     void notifyPayloadReady();
 
@@ -75,7 +107,9 @@
      * serving as proof of the freshness of the result.
      *
      * @param challenge the maximum supported challenge size is 64 bytes.
-     * @return the X.509 encoded certificate.
+     *
+     * @return An {@link AttestationResult} parcelable containing an attested key pair and its
+     *         certification chain.
      */
-    byte[] requestAttestation(in byte[] challenge);
+    AttestationResult requestAttestation(in byte[] challenge);
 }
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 5b5fb9e..0661314 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -15,16 +15,38 @@
 //! Implementation of the AIDL interface `IVmPayloadService`.
 
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
-    BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
+    BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, AttestationResult::AttestationResult,
+    STATUS_FAILED_TO_PREPARE_CSR_AND_KEY
+};
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 use anyhow::{anyhow, Context, Result};
 use avflog::LogResult;
-use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult};
-use diced_open_dice::DiceArtifacts;
+use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult, Status};
+use diced_open_dice::{DiceArtifacts, derive_cdi_leaf_priv, PrivateKey, sign};
 use log::info;
 use rpcbinder::RpcServer;
+
+use crate::vm_secret::VmSecret;
+use coset::{
+    iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
+    CoseSignatureBuilder, HeaderBuilder,
+};
+use openssl::{
+    bn::{BigNum, BigNumContext},
+    ec::{EcGroup, EcKey, EcKeyRef},
+    ecdsa::EcdsaSig,
+    nid::Nid,
+    pkey::Private,
+    sha::sha256,
+};
+use service_vm_comm::{Csr, CsrPayload};
 use std::os::unix::io::OwnedFd;
-use crate::vm_secret::{VmSecret};
+use zeroize::Zeroizing;
+
+const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
+const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
+const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
+const ATTESTATION_KEY_AFFINE_COORDINATE_SIZE: i32 = 32;
 
 /// Implementation of `IVmPayloadService`.
 struct VmPayloadService {
@@ -66,12 +88,111 @@
         Ok(self.secret.dice().cdi_attest().to_vec())
     }
 
-    fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<Vec<u8>> {
+    fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
         self.check_restricted_apis_allowed()?;
-        self.virtual_machine_service.requestAttestation(challenge)
+        let (private_key, csr) = generate_attestation_key_and_csr(challenge, self.secret.dice())
+            .map_err(|e| {
+                Status::new_service_specific_error_str(
+                    STATUS_FAILED_TO_PREPARE_CSR_AND_KEY,
+                    Some(format!("Failed to prepare the CSR and key pair: {e:?}")),
+                )
+            })
+            .with_log()?;
+        let cert_chain = self.virtual_machine_service.requestAttestation(&csr)?;
+        Ok(AttestationResult {
+            privateKey: private_key.as_slice().to_vec(),
+            certificateChain: cert_chain,
+        })
     }
 }
 
+fn generate_attestation_key_and_csr(
+    challenge: &[u8],
+    dice_artifacts: &dyn DiceArtifacts,
+) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
+    let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+    let attestation_key = EcKey::generate(&group)?;
+    let csr = build_csr(challenge, attestation_key.as_ref(), dice_artifacts)?;
+
+    let csr = csr.into_cbor_vec().context("Failed to serialize CSR")?;
+    let private_key = attestation_key.private_key_to_der()?;
+    Ok((Zeroizing::new(private_key), csr))
+}
+
+fn build_csr(
+    challenge: &[u8],
+    attestation_key: &EcKeyRef<Private>,
+    dice_artifacts: &dyn DiceArtifacts,
+) -> Result<Csr> {
+    // Builds CSR Payload to be signed.
+    let public_key =
+        to_cose_public_key(attestation_key)?.to_vec().context("Failed to serialize public key")?;
+    let csr_payload = CsrPayload { public_key, challenge: challenge.to_vec() };
+    let csr_payload = csr_payload.into_cbor_vec()?;
+
+    // Builds signed CSR Payload.
+    let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts)?;
+    let signed_csr_payload = build_signed_data(csr_payload, &cdi_leaf_priv, attestation_key)?
+        .to_vec()
+        .context("Failed to serialize signed CSR payload")?;
+
+    // Builds CSR.
+    let dice_cert_chain = dice_artifacts.bcc().ok_or(anyhow!("bcc is none"))?.to_vec();
+    Ok(Csr { dice_cert_chain, signed_csr_payload })
+}
+
+fn build_signed_data(
+    payload: Vec<u8>,
+    cdi_leaf_priv: &PrivateKey,
+    attestation_key: &EcKeyRef<Private>,
+) -> Result<CoseSign> {
+    let cdi_leaf_sig_headers = build_signature_headers(iana::Algorithm::EdDSA);
+    let attestation_key_sig_headers = build_signature_headers(ATTESTATION_KEY_ALGO);
+    let aad = &[];
+    let signed_data = CoseSignBuilder::new()
+        .payload(payload)
+        .try_add_created_signature(cdi_leaf_sig_headers, aad, |message| {
+            sign(message, cdi_leaf_priv.as_array()).map(|v| v.to_vec())
+        })?
+        .try_add_created_signature(attestation_key_sig_headers, aad, |message| {
+            ecdsa_sign(message, attestation_key)
+        })?
+        .build();
+    Ok(signed_data)
+}
+
+/// Builds a signature with headers filled with the provided algorithm.
+/// The signature data will be filled later when building the signed data.
+fn build_signature_headers(alg: iana::Algorithm) -> CoseSignature {
+    let protected = HeaderBuilder::new().algorithm(alg).build();
+    CoseSignatureBuilder::new().protected(protected).build()
+}
+
+fn ecdsa_sign(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>> {
+    let digest = sha256(message);
+    // Passes the digest to `ECDSA_do_sign` as recommended in the spec:
+    // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ecdsa.h.html#ECDSA_do_sign
+    let sig = EcdsaSig::sign::<Private>(&digest, key)?;
+    Ok(sig.to_der()?)
+}
+
+fn get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)> {
+    let mut ctx = BigNumContext::new()?;
+    let mut x = BigNum::new()?;
+    let mut y = BigNum::new()?;
+    key.public_key().affine_coordinates_gfp(key.group(), &mut x, &mut y, &mut ctx)?;
+    let x = x.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+    let y = y.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+    Ok((x, y))
+}
+
+fn to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey> {
+    let (x, y) = get_affine_coordinates(key)?;
+    Ok(CoseKeyBuilder::new_ec2_pub_key(ATTESTATION_KEY_CURVE, x, y)
+        .algorithm(ATTESTATION_KEY_ALGO)
+        .build())
+}
+
 impl Interface for VmPayloadService {}
 
 impl VmPayloadService {
@@ -116,3 +237,106 @@
     });
     Ok(())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use anyhow::bail;
+    use ciborium::Value;
+    use coset::{iana::EnumI64, Label};
+    use hwtrust::{dice, session::Session};
+    use openssl::pkey::Public;
+
+    /// The following data is generated randomly with urandom.
+    const CHALLENGE: [u8; 16] = [
+        0xb3, 0x66, 0xfa, 0x72, 0x92, 0x32, 0x2c, 0xd4, 0x99, 0xcb, 0x00, 0x1f, 0x0e, 0xe0, 0xc7,
+        0x41,
+    ];
+
+    #[test]
+    fn csr_and_private_key_have_correct_format() -> Result<()> {
+        let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+
+        let (private_key, csr) = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+        let ec_private_key = EcKey::private_key_from_der(&private_key)?;
+        let csr = Csr::from_cbor_slice(&csr).unwrap();
+        let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload).unwrap();
+        let aad = &[];
+
+        // Checks CSR payload.
+        let csr_payload =
+            cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
+        let public_key = to_cose_public_key(&ec_private_key)?.to_vec().unwrap();
+        let expected_csr_payload = CsrPayload { challenge: CHALLENGE.to_vec(), public_key };
+        assert_eq!(expected_csr_payload, csr_payload);
+
+        // Checks the first signature is signed with CDI_Leaf_Priv.
+        let session = Session::default();
+        let chain = dice::Chain::from_cbor(&session, &csr.dice_cert_chain)?;
+        let public_key = chain.leaf().subject_public_key();
+        cose_sign
+            .verify_signature(0, aad, |signature, message| public_key.verify(signature, message))?;
+
+        // Checks the second signature is signed with attestation key.
+        let attestation_public_key = CoseKey::from_slice(&csr_payload.public_key).unwrap();
+        let ec_public_key = to_ec_public_key(&attestation_public_key)?;
+        cose_sign.verify_signature(1, aad, |signature, message| {
+            ecdsa_verify(signature, message, &ec_public_key)
+        })?;
+
+        // Verifies that private key and the public key form a valid key pair.
+        let message = b"test message";
+        let signature = ecdsa_sign(message, &ec_private_key)?;
+        ecdsa_verify(&signature, message, &ec_public_key)?;
+
+        Ok(())
+    }
+
+    fn ecdsa_verify(
+        signature: &[u8],
+        message: &[u8],
+        ec_public_key: &EcKeyRef<Public>,
+    ) -> Result<()> {
+        let sig = EcdsaSig::from_der(signature)?;
+        let digest = sha256(message);
+        if sig.verify(&digest, ec_public_key)? {
+            Ok(())
+        } else {
+            bail!("Signature does not match")
+        }
+    }
+
+    fn to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>> {
+        check_ec_key_params(cose_key)?;
+        let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+        let x = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+        let y = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+        let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
+        key.check_key()?;
+        Ok(key)
+    }
+
+    fn check_ec_key_params(cose_key: &CoseKey) -> Result<()> {
+        assert_eq!(coset::KeyType::Assigned(iana::KeyType::EC2), cose_key.kty);
+        assert_eq!(Some(coset::Algorithm::Assigned(ATTESTATION_KEY_ALGO)), cose_key.alg);
+        let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+        assert_eq!(&Value::from(ATTESTATION_KEY_CURVE.to_i64()), crv);
+        Ok(())
+    }
+
+    fn get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum> {
+        get_label_value(key, label)?
+            .as_bytes()
+            .map(|v| BigNum::from_slice(&v[..]).unwrap())
+            .ok_or_else(|| anyhow!("Value not a bstr."))
+    }
+
+    fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+        Ok(&key
+            .params
+            .iter()
+            .find(|(k, _)| k == &label)
+            .ok_or_else(|| anyhow!("Label {:?} not found", label))?
+            .1)
+    }
+}
diff --git a/service_vm/comm/Android.bp b/service_vm/comm/Android.bp
index 3a18052..6e05587 100644
--- a/service_vm/comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -43,3 +43,31 @@
         "std",
     ],
 }
+
+rust_defaults {
+    name: "libservice_vm_comm_test_defaults",
+    crate_name: "diced_open_dice_test",
+    srcs: ["tests/*.rs"],
+    test_suites: ["general-tests"],
+    prefer_rlib: true,
+    rustlibs: [
+        "libdiced_sample_inputs",
+        "libdiced_open_dice",
+    ],
+}
+
+rust_test {
+    name: "libservice_vm_comm.test",
+    defaults: ["libservice_vm_comm_test_defaults"],
+    rustlibs: [
+        "libservice_vm_comm",
+    ],
+}
+
+rust_test {
+    name: "libservice_vm_comm_nostd.test",
+    defaults: ["libservice_vm_comm_test_defaults"],
+    rustlibs: [
+        "libservice_vm_comm_nostd",
+    ],
+}
diff --git a/service_vm/comm/TEST_MAPPING b/service_vm/comm/TEST_MAPPING
new file mode 100644
index 0000000..e677ba2
--- /dev/null
+++ b/service_vm/comm/TEST_MAPPING
@@ -0,0 +1,12 @@
+// 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" : "libservice_vm_comm.test"
+    },
+    {
+      "name" : "libservice_vm_comm_nostd.test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/service_vm/comm/src/csr.rs b/service_vm/comm/src/csr.rs
new file mode 100644
index 0000000..5e1cbad
--- /dev/null
+++ b/service_vm/comm/src/csr.rs
@@ -0,0 +1,121 @@
+// 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 structs related to the CSR (Certificate Signing Request)
+//! sent from the client VM to the service VM for attestation.
+
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::Value;
+use coset::{self, CborSerializable, CoseError};
+
+/// Represents a CSR sent from the client VM to the service VM for attestation.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Csr {
+    /// The DICE certificate chain of the client VM.
+    pub dice_cert_chain: Vec<u8>,
+
+    /// The signed CSR payload in COSE_Sign structure, which includes two signatures:
+    /// - one by CDI_Leaf_Priv of the client VM's DICE chain,
+    /// - another by the private key corresponding to the public key.
+    pub signed_csr_payload: Vec<u8>,
+}
+
+impl Csr {
+    /// Serializes this object to a CBOR-encoded vector.
+    pub fn into_cbor_vec(self) -> coset::Result<Vec<u8>> {
+        let value = Value::Array(vec![
+            Value::Bytes(self.dice_cert_chain),
+            Value::Bytes(self.signed_csr_payload),
+        ]);
+        value.to_vec()
+    }
+
+    /// Creates an object instance from the provided CBOR-encoded slice.
+    pub fn from_cbor_slice(data: &[u8]) -> coset::Result<Self> {
+        let value = Value::from_slice(data)?;
+        let Value::Array(mut arr) = value else {
+            return Err(CoseError::UnexpectedItem(cbor_value_type(&value), "array"));
+        };
+        if arr.len() != 2 {
+            return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
+        }
+        Ok(Self {
+            signed_csr_payload: try_as_bytes(arr.remove(1))?,
+            dice_cert_chain: try_as_bytes(arr.remove(0))?,
+        })
+    }
+}
+
+/// Represents the data to be signed and sent from the client VM to the service VM
+/// for attestation.
+///
+/// It will be signed by both CDI_Leaf_Priv of the client VM's DICE chain and
+/// the private key corresponding to the public key to be attested.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct CsrPayload {
+    /// COSE_Key encoded EC P-256 public key to be attested.
+    pub public_key: Vec<u8>,
+
+    /// A random array with a length between 0 and 64.
+    /// It will be included in the certificate chain in the attestation result,
+    /// serving as proof of the freshness of the result.
+    pub challenge: Vec<u8>,
+}
+
+impl CsrPayload {
+    /// Serializes this object to a CBOR-encoded vector.
+    pub fn into_cbor_vec(self) -> coset::Result<Vec<u8>> {
+        let value = Value::Array(vec![Value::Bytes(self.public_key), Value::Bytes(self.challenge)]);
+        value.to_vec()
+    }
+
+    /// Creates an object instance from the provided CBOR-encoded slice.
+    pub fn from_cbor_slice(data: &[u8]) -> coset::Result<Self> {
+        let value = Value::from_slice(data)?;
+        let Value::Array(mut arr) = value else {
+            return Err(CoseError::UnexpectedItem(cbor_value_type(&value), "array"));
+        };
+        if arr.len() != 2 {
+            return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
+        }
+        Ok(Self {
+            challenge: try_as_bytes(arr.remove(1))?,
+            public_key: try_as_bytes(arr.remove(0))?,
+        })
+    }
+}
+
+fn try_as_bytes(v: Value) -> coset::Result<Vec<u8>> {
+    if let Value::Bytes(data) = v {
+        Ok(data)
+    } else {
+        Err(CoseError::UnexpectedItem(cbor_value_type(&v), "bytes"))
+    }
+}
+
+fn cbor_value_type(v: &Value) -> &'static str {
+    match v {
+        Value::Integer(_) => "int",
+        Value::Bytes(_) => "bstr",
+        Value::Float(_) => "float",
+        Value::Text(_) => "tstr",
+        Value::Bool(_) => "bool",
+        Value::Null => "nul",
+        Value::Tag(_, _) => "tag",
+        Value::Array(_) => "array",
+        Value::Map(_) => "map",
+        _ => "other",
+    }
+}
diff --git a/service_vm/comm/src/lib.rs b/service_vm/comm/src/lib.rs
index d8f7bd7..0818f24 100644
--- a/service_vm/comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -19,9 +19,11 @@
 
 extern crate alloc;
 
+mod csr;
 mod message;
 mod vsock;
 
+pub use csr::{Csr, CsrPayload};
 pub use message::{
     EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, RequestProcessingError, Response,
     ServiceVmRequest,
diff --git a/service_vm/comm/tests/api_test.rs b/service_vm/comm/tests/api_test.rs
new file mode 100644
index 0000000..44a3ef9
--- /dev/null
+++ b/service_vm/comm/tests/api_test.rs
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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 diced_open_dice::DiceArtifacts;
+use service_vm_comm::{Csr, CsrPayload};
+
+/// The following test data are generated with urandom
+const DATA1: [u8; 32] = [
+    0x8b, 0x09, 0xc0, 0x7e, 0x20, 0x3c, 0xa2, 0x11, 0x7e, 0x7f, 0x0b, 0xdd, 0x2b, 0x68, 0x98, 0xb0,
+    0x2b, 0x34, 0xb5, 0x63, 0x39, 0x01, 0x90, 0x06, 0xaf, 0x5f, 0xdd, 0xb7, 0x81, 0xca, 0xc7, 0x46,
+];
+const DATA2: [u8; 16] = [
+    0x6c, 0xb9, 0x39, 0x86, 0x9b, 0x2f, 0x12, 0xd8, 0x45, 0x92, 0x57, 0x44, 0x65, 0xce, 0x94, 0x63,
+];
+
+#[test]
+fn csr_payload_cbor_serialization() {
+    let csr_payload = CsrPayload { public_key: DATA1.to_vec(), challenge: DATA2.to_vec() };
+    let expected_csr_payload = csr_payload.clone();
+    let cbor_vec = csr_payload.into_cbor_vec().unwrap();
+    let deserialized_csr_payload = CsrPayload::from_cbor_slice(&cbor_vec).unwrap();
+
+    assert_eq!(expected_csr_payload, deserialized_csr_payload);
+}
+
+#[test]
+fn csr_cbor_serialization() {
+    let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis().unwrap();
+    let dice_cert_chain = dice_artifacts.bcc().unwrap().to_vec();
+    let csr = Csr { signed_csr_payload: DATA1.to_vec(), dice_cert_chain };
+    let expected_csr = csr.clone();
+    let cbor_vec = csr.into_cbor_vec().unwrap();
+    let deserialized_csr = Csr::from_cbor_slice(&cbor_vec).unwrap();
+
+    assert_eq!(expected_csr, deserialized_csr);
+}
diff --git a/service_vm/test_apk/src/main.rs b/service_vm/test_apk/src/main.rs
index 7d8416f..ba65aca 100644
--- a/service_vm/test_apk/src/main.rs
+++ b/service_vm/test_apk/src/main.rs
@@ -14,10 +14,20 @@
 
 //! Main executable of Service VM client for manual testing.
 
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
 use log::{error, info};
-use std::{ffi::c_void, panic};
-use vm_payload_bindgen::AVmPayload_requestAttestation;
+use std::{
+    ffi::{c_void, CStr},
+    panic,
+    ptr::{self, NonNull},
+    result,
+};
+use vm_payload_bindgen::{
+    attestation_status_t, AVmAttestationResult, AVmAttestationResult_free,
+    AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
+    AVmAttestationResult_getPrivateKey, AVmAttestationResult_resultToString,
+    AVmAttestationResult_sign, AVmPayload_requestAttestation,
+};
 
 /// Entry point of the Service VM client.
 #[allow(non_snake_case)]
@@ -40,38 +50,180 @@
 
 fn try_main() -> Result<()> {
     info!("Welcome to Service VM Client!");
+
+    let too_big_challenge = &[0u8; 66];
+    let res = AttestationResult::request_attestation(too_big_challenge);
+    ensure!(res.is_err());
+    let status = res.unwrap_err();
+    ensure!(
+        status == attestation_status_t::ATTESTATION_ERROR_INVALID_CHALLENGE,
+        "Unexpected status: {:?}",
+        status
+    );
+    info!("Status: {:?}", status_to_cstr(status));
+
     // 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,
     ];
-    info!("Sending challenge: {:?}", challenge);
-    let certificate = request_attestation(challenge);
-    info!("Certificate: {:?}", certificate);
+    let res = AttestationResult::request_attestation(challenge)
+        .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))?;
+
+    let cert_chain = res.certificate_chain()?;
+    info!("Attestation result certificateChain = {:?}", cert_chain);
+
+    let private_key = res.private_key()?;
+    info!("Attestation result privateKey = {:?}", private_key);
+
+    let message = b"Hello from Service VM client";
+    info!("Signing message: {:?}", message);
+    let signature = res.sign(message)?;
+    info!("Signature: {:?}", signature);
+
     Ok(())
 }
 
-fn request_attestation(challenge: &[u8]) -> Vec<u8> {
-    // SAFETY: It is safe as we only request the size of the certificate in this call.
-    let certificate_size = unsafe {
-        AVmPayload_requestAttestation(
-            challenge.as_ptr() as *const c_void,
-            challenge.len(),
-            [].as_mut_ptr(),
+#[derive(Debug)]
+struct AttestationResult(NonNull<AVmAttestationResult>);
+
+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_requestAttestation(
+                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)
+    }
+}
+
+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 certificate = vec![0u8; certificate_size];
-    // SAFETY: It is safe as we only write the data into the given buffer within the buffer
-    // size in this call.
-    unsafe {
-        AVmPayload_requestAttestation(
-            challenge.as_ptr() as *const c_void,
-            challenge.len(),
-            certificate.as_mut_ptr() as *mut c_void,
-            certificate.len(),
-        );
+    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(),
+        )
     };
-    certificate
+    ensure!(size == signature.len());
+    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.
+    unsafe { CStr::from_ptr(message) }
 }
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index da7dffe..4024b04 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -24,6 +24,7 @@
 use crate::selinux::{getfilecon, SeContext};
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
+    Certificate::Certificate,
     DeathReason::DeathReason,
     ErrorCode::ErrorCode,
 };
@@ -1245,7 +1246,7 @@
         }
     }
 
-    fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<u8>> {
+    fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
         GLOBAL_SERVICE.requestAttestation(csr)
     }
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl b/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl
new file mode 100644
index 0000000..d587541
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.system.virtualizationcommon;
+
+/**
+ * This encodes a X.509 certificate returned in the pVM remote attestation.
+ */
+parcelable Certificate {
+    /**
+     * Contains the bytes of a DER-encoded X.509 certificate.
+     */
+    byte[] encodedCertificate;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 099a2c0..2592135 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -15,6 +15,7 @@
  */
 package android.system.virtualizationservice_internal;
 
+import android.system.virtualizationcommon.Certificate;
 import android.system.virtualizationservice.AssignableDevice;
 import android.system.virtualizationservice.VirtualMachineDebugInfo;
 import android.system.virtualizationservice_internal.AtomVmBooted;
@@ -62,7 +63,7 @@
      * @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.
      */
-    byte[] requestAttestation(in byte[] csr);
+    Certificate[] requestAttestation(in byte[] csr);
 
     /**
      * 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 87d3056..3c60478 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -15,6 +15,7 @@
  */
 package android.system.virtualmachineservice;
 
+import android.system.virtualizationcommon.Certificate;
 import android.system.virtualizationcommon.ErrorCode;
 
 /** {@hide} */
@@ -52,5 +53,5 @@
      * @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.
      */
-    byte[] requestAttestation(in byte[] csr);
+    Certificate[] requestAttestation(in byte[] csr);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 4daa0cf..2be2b19 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -18,6 +18,7 @@
 use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
 use crate::rkpvm::request_attestation;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
 use android_system_virtualizationservice::{
     aidl::android::system::virtualizationservice::AssignableDevice::AssignableDevice,
     aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo,
@@ -158,7 +159,7 @@
         Ok(cids)
     }
 
-    fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<u8>> {
+    fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
         check_manage_access()?;
         info!("Received csr. Requestting attestation...");
         if cfg!(remote_attestation) {
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 443b280..8f1de6b 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -17,18 +17,23 @@
 //! serves as a trusted platform to attest a client VM.
 
 use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
 use anyhow::{bail, Context, Result};
 use service_vm_comm::{GenerateCertificateRequestParams, Request, Response};
 use service_vm_manager::ServiceVm;
 
-pub(crate) fn request_attestation(csr: &[u8]) -> Result<Vec<u8>> {
+pub(crate) fn request_attestation(csr: &[u8]) -> Result<Vec<Certificate>> {
     let mut vm = ServiceVm::start()?;
 
     // TODO(b/271275206): Send the correct request type with client VM's
     // information to be attested.
     let request = Request::Reverse(csr.to_vec());
     match vm.process_request(request).context("Failed to process request")? {
-        Response::Reverse(cert) => Ok(cert),
+        // TODO(b/271275206): Adjust the response type.
+        Response::Reverse(cert) => {
+            let cert = Certificate { encodedCertificate: cert };
+            Ok(vec![cert])
+        }
         _ => bail!("Incorrect response type"),
     }
 }
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index b5322a8..7f2b9df 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -19,11 +19,26 @@
         "liblazy_static",
         "liblibc",
         "liblog_rust",
+        "libopenssl",
         "librpcbinder_rs",
+        "libvm_payload_status_bindgen",
         "libvsock",
     ],
 }
 
+rust_bindgen {
+    name: "libvm_payload_status_bindgen",
+    wrapper_src: "include/vm_payload.h",
+    crate_name: "vm_payload_status_bindgen",
+    defaults: ["avf_build_flags_rust"],
+    source_stem: "bindings",
+    bindgen_flags: [
+        "--default-enum-style rust",
+        "--allowlist-type=attestation_status_t",
+    ],
+    visibility: [":__subpackages__"],
+}
+
 // Rust wrappers round the C API for Rust clients.
 // (Yes, this involves going Rust -> C -> Rust.)
 rust_bindgen {
@@ -33,6 +48,9 @@
     defaults: ["avf_build_flags_rust"],
     source_stem: "bindings",
     apex_available: ["com.android.compos"],
+    bindgen_flags: [
+        "--default-enum-style rust",
+    ],
     visibility: [
         "//packages/modules/Virtualization/compos",
         "//packages/modules/Virtualization/service_vm/test_apk",
@@ -49,6 +67,7 @@
         "libbinder_ndk",
         "libbinder_rpc_unstable",
         "liblog",
+        "libcrypto",
     ],
     whole_static_libs: ["libvm_payload_impl"],
     export_static_lib_headers: ["libvm_payload_impl"],
diff --git a/vm_payload/include-restricted/vm_payload_restricted.h b/vm_payload/include-restricted/vm_payload_restricted.h
index ee92366..15c37ed 100644
--- a/vm_payload/include-restricted/vm_payload_restricted.h
+++ b/vm_payload/include-restricted/vm_payload_restricted.h
@@ -55,23 +55,4 @@
  */
 size_t AVmPayload_getDiceAttestationCdi(void* _Nullable data, size_t size);
 
-/**
- * Requests the remote attestation of the client VM.
- *
- * The challenge will be included in the certificate chain in the attestation result,
- * serving as proof of the freshness of the result.
- *
- * \param challenge A pointer to the challenge buffer.
- * \param challenge_size size of the challenge, the maximum supported challenge size is
- *                       64 bytes. An error will be returned if an invalid challenge is
- *                       passed.
- * \param buffer A pointer to the certificate buffer.
- * \param size number of bytes that can be written to the certificate buffer.
- *
- * \return the total size of the certificate
- */
-size_t AVmPayload_requestAttestation(const void* _Nonnull challenge, size_t challenge_size,
-                                     void* _Nullable buffer, size_t size)
-        __INTRODUCED_IN(__ANDROID_API_V__);
-
 __END_DECLS
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index c28cd42..2dfa2cb 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -30,6 +30,27 @@
 typedef struct AIBinder AIBinder;
 
 /**
+ * Introduced in API 35.
+ * Remote attestation result if the attestation succeeds.
+ */
+struct AVmAttestationResult;
+
+/**
+ * Introduced in API 35.
+ * Remote attestation status types returned from remote attestation functions.
+ */
+typedef enum attestation_status_t : int32_t {
+    /** The remote attestation completes successfully. */
+    ATTESTATION_OK = 0,
+
+    /** The remote attestation has failed due to an unspecified cause. */
+    ATTESTATION_UNKNOWN_ERROR = -10000,
+
+    /** The challenge size is not between 0 and 64. */
+    ATTESTATION_ERROR_INVALID_CHALLENGE = -10001,
+} attestation_status_t;
+
+/**
  * Notifies the host that the payload is ready.
  *
  * If the host app has set a `VirtualMachineCallback` for the VM, its
@@ -112,4 +133,129 @@
  */
 const char* _Nullable AVmPayload_getEncryptedStoragePath(void);
 
+/**
+ * Requests the remote attestation of the client VM.
+ *
+ * The challenge will be included in the certificate chain in the attestation result,
+ * serving as proof of the freshness of the result.
+ *
+ * \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`.
+ *
+ * \return ATTESTATION_OK upon successful attestation.
+ */
+attestation_status_t AVmPayload_requestAttestation(
+        const void* _Nonnull challenge, size_t challenge_size,
+        struct AVmAttestationResult* _Nullable* _Nonnull result) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Converts the return value from `AVmPayload_requestAttestation` to a text string
+ * representing the status code.
+ *
+ * \return a constant string value representing the status code. The string should not
+ * be deleted or freed by the application and remains valid for the lifetime of the VM.
+ */
+const char* _Nonnull AVmAttestationResult_resultToString(attestation_status_t status)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Frees all the data owned by the provided attestation result, including the result itself.
+ *
+ * Callers should ensure to invoke this API only once on a valid attestation result
+ * returned by `AVmPayload_requestAttestation` to avoid undefined behavior.
+ *
+ * \param result A pointer to the attestation result.
+ */
+void AVmAttestationResult_free(struct AVmAttestationResult* _Nullable result)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Reads the DER-encoded ECPrivateKey structure specified in [RFC 5915 s3] for the
+ * EC P-256 private key from the provided attestation result.
+ *
+ * \param result A pointer to the attestation result filled in
+ *              `AVmPayload_requestAttestation` when the attestation succeeds.
+ * \param data A pointer to the memory where the private key will be written
+ * (can be null if size is 0).
+ * \param size The maximum number of bytes that can be written to the data buffer.
+ * If `size` is smaller than the total size of the private key, the key data will be
+ * truncated to this `size`.
+ *
+ * \return The total size of the private key.
+ *
+ * [RFC 5915 s3]: https://datatracker.ietf.org/doc/html/rfc5915#section-3
+ */
+size_t AVmAttestationResult_getPrivateKey(const struct AVmAttestationResult* _Nonnull result,
+                                          void* _Nullable data, size_t size)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Signs the given message using ECDSA P-256, the message is first hashed with SHA-256 and
+ * then it is signed with the attested EC P-256 private key in the attestation result.
+ *
+ * \param result A pointer to the attestation result filled in
+ *              `AVmPayload_requestAttestation` when the attestation succeeds.
+ * \param message A pointer to the message buffer.
+ * \param message_size size of the message.
+ * \param data A pointer to the memory where the signature will be written
+ * (can be null if size is 0). The signature is a DER-encoded ECDSASignature structure
+ * detailed in the [RFC 6979].
+ * \param size The maximum number of bytes that can be written to the data buffer.
+ * 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.
+ *
+ * [RFC 6979]: https://datatracker.ietf.org/doc/html/rfc6979
+ */
+size_t AVmAttestationResult_sign(const struct AVmAttestationResult* _Nonnull result,
+                                 const void* _Nonnull message, size_t message_size,
+                                 void* _Nullable data, size_t size)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Gets the number of certificates in the certificate chain.
+ *
+ * The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
+ * the attestation key's certificate chain. It starts with a root certificate and ends with a
+ * leaf certificate covering the attested public key.
+ *
+ * \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
+ *               when the attestation succeeds.
+ *
+ * \return The number of certificates in the certificate chain.
+ */
+size_t AVmAttestationResult_getCertificateCount(const struct AVmAttestationResult* _Nonnull result)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Retrieves the certificate at the given `index` from the certificate chain in the provided
+ * attestation result.
+ *
+ * The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
+ * the attestation key's certificate chain. It starts with a root certificate and ends with a
+ * leaf certificate covering the attested public key.
+ *
+ * \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
+ *               when the attestation succeeds.
+ * \param index Index of the certificate to retrieve. The `index` must be within the range of
+ *              [0, number of certificates). The number of certificates can be obtained with
+ *              `AVmAttestationResult_getCertificateCount`.
+ * \param data A pointer to the memory where the certificate will be written
+ *             (can be null if size is 0).
+ * \param size The maximum number of bytes that can be written to the data buffer. If `size`
+ *             is smaller than the total size of the certificate, the certificate will be
+ *             truncated to this `size`.
+ *
+ * \return The total size of the certificate at the given `index`.
+ */
+size_t AVmAttestationResult_getCertificateAt(const struct AVmAttestationResult* _Nonnull result,
+                                             size_t index, void* _Nullable data, size_t size)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
 __END_DECLS
diff --git a/vm_payload/libvm_payload.map.txt b/vm_payload/libvm_payload.map.txt
index 32dd33b..975a5a3 100644
--- a/vm_payload/libvm_payload.map.txt
+++ b/vm_payload/libvm_payload.map.txt
@@ -8,6 +8,12 @@
     AVmPayload_getApkContentsPath;       # systemapi introduced=UpsideDownCake
     AVmPayload_getEncryptedStoragePath;  # systemapi introduced=UpsideDownCake
     AVmPayload_requestAttestation;       # systemapi introduced=VanillaIceCream
+    AVmAttestationResult_getPrivateKey;  # systemapi introduced=VanillaIceCream
+    AVmAttestationResult_sign;           # systemapi introduced=VanillaIceCream
+    AVmAttestationResult_free;           # systemapi introduced=VanillaIceCream
+    AVmAttestationResult_resultToString; # systemapi introduced=VanillaIceCream
+    AVmAttestationResult_getCertificateCount; # systemapi introduced=VanillaIceCream
+    AVmAttestationResult_getCertificateAt; # systemapi introduced=VanillaIceCream
   local:
     *;
 };
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index 93dbd1c..64f8d6a 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -14,20 +14,30 @@
 
 //! This module handles the interaction with virtual machine payload service.
 
-use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
-    ENCRYPTEDSTORE_MOUNTPOINT, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
-use anyhow::{ensure, bail, Context, Result};
-use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload:: IVmPayloadService::{
+    IVmPayloadService, ENCRYPTEDSTORE_MOUNTPOINT, VM_APK_CONTENTS_PATH,
+    VM_PAYLOAD_SERVICE_SOCKET_NAME, AttestationResult::AttestationResult,
+};
+use anyhow::{bail, ensure, Context, Result};
+use binder::{
+    unstable_api::{new_spibinder, AIBinder},
+    Strong,
+};
 use lazy_static::lazy_static;
 use log::{error, info, Level};
-use rpcbinder::{RpcSession, RpcServer};
+use rpcbinder::{RpcServer, RpcSession};
+use openssl::{ec::EcKey, sha::sha256, ecdsa::EcdsaSig};
 use std::convert::Infallible;
-use std::ffi::CString;
+use std::ffi::{CString, CStr};
 use std::fmt::Debug;
 use std::os::raw::{c_char, c_void};
 use std::path::Path;
-use std::ptr;
-use std::sync::{Mutex, atomic::{AtomicBool, Ordering}};
+use std::ptr::{self, NonNull};
+use std::sync::{
+    atomic::{AtomicBool, Ordering},
+    Mutex,
+};
+use vm_payload_status_bindgen::attestation_status_t;
 
 lazy_static! {
     static ref VM_APK_CONTENTS_PATH_C: CString =
@@ -263,42 +273,210 @@
 /// Behavior is undefined if any of the following conditions are violated:
 ///
 /// * `challenge` must be [valid] for reads of `challenge_size` bytes.
-/// * `buffer` must be [valid] for writes of `size` bytes. `buffer` can be null if `size` is 0.
+/// * `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]
 pub unsafe extern "C" fn AVmPayload_requestAttestation(
     challenge: *const u8,
     challenge_size: usize,
-    buffer: *mut u8,
+    res: &mut *mut AttestationResult,
+) -> attestation_status_t {
+    initialize_logging();
+    const MAX_CHALLENGE_SIZE: usize = 64;
+    if challenge_size > MAX_CHALLENGE_SIZE {
+        return attestation_status_t::ATTESTATION_ERROR_INVALID_CHALLENGE;
+    }
+    let challenge = if challenge_size == 0 {
+        &[]
+    } else {
+        // SAFETY: The caller guarantees that `challenge` is valid for reads of
+        // `challenge_size` bytes and `challenge_size` is not zero.
+        unsafe { std::slice::from_raw_parts(challenge, challenge_size) }
+    };
+    let attestation_res = unwrap_or_abort(try_request_attestation(challenge));
+    *res = Box::into_raw(Box::new(attestation_res));
+    attestation_status_t::ATTESTATION_OK
+}
+
+fn try_request_attestation(public_key: &[u8]) -> Result<AttestationResult> {
+    get_vm_payload_service()?
+        .requestAttestation(public_key)
+        .context("Failed to request attestation")
+}
+
+/// Converts the return value from `AVmPayload_requestAttestation` to a text string
+/// representing the error code.
+#[no_mangle]
+pub extern "C" fn AVmAttestationResult_resultToString(
+    status: attestation_status_t,
+) -> *const c_char {
+    let message = match status {
+        attestation_status_t::ATTESTATION_OK => {
+            CStr::from_bytes_with_nul(b"The remote attestation completes successfully.\0").unwrap()
+        }
+        attestation_status_t::ATTESTATION_ERROR_INVALID_CHALLENGE => {
+            CStr::from_bytes_with_nul(b"The challenge size is not between 0 and 64.\0").unwrap()
+        }
+        _ => CStr::from_bytes_with_nul(
+            b"The remote attestation has failed due to an unspecified cause.\0",
+        )
+        .unwrap(),
+    };
+    message.as_ptr()
+}
+
+/// Reads the DER-encoded ECPrivateKey structure specified in [RFC 5915 s3] for the
+/// EC P-256 private key from the provided attestation result.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
+/// * The region of memory beginning at `data` with `size` bytes must not overlap with the
+///  region of memory `res` points to.
+///
+/// [valid]: ptr#safety
+/// [RFC 5915 s3]: https://datatracker.ietf.org/doc/html/rfc5915#section-3
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_getPrivateKey(
+    res: &AttestationResult,
+    data: *mut u8,
     size: usize,
 ) -> usize {
-    initialize_logging();
+    let private_key = &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 `private_key` 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(
+                private_key.as_ptr(),
+                data.as_ptr(),
+                std::cmp::min(private_key.len(), size),
+            )
+        };
+    }
+    private_key.len()
+}
 
-    // SAFETY: See the requirements on `challenge` above.
-    let challenge = unsafe { std::slice::from_raw_parts(challenge, challenge_size) };
-    let certificate = unwrap_or_abort(try_request_attestation(challenge));
+/// Signs the given message using ECDSA P-256, the message is first hashed with SHA-256 and
+/// then it is signed with the attested EC P-256 private key in the attestation result.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `message` must be [valid] for reads of `message_size` bytes.
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
+/// * The region of memory beginning at `data` with `size` bytes must not overlap with the
+///  region of memory `res` or `message` point to.
+///
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_sign(
+    res: &AttestationResult,
+    message: *const u8,
+    message_size: usize,
+    data: *mut u8,
+    size: usize,
+) -> usize {
+    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),
+            )
+        };
+    }
+    signature.len()
+}
 
-    if size != 0 || buffer.is_null() {
-        // SAFETY: See the requirements on `buffer` above. The number of bytes copied doesn't exceed
-        // the length of either buffer, and `certificate` cannot overlap `buffer` because we just
-        // allocated it.
+fn try_ecdsa_sign(message: &[u8], der_encoded_ec_private_key: &[u8]) -> Result<Vec<u8>> {
+    let private_key = EcKey::private_key_from_der(der_encoded_ec_private_key)?;
+    let digest = sha256(message);
+    let sig = EcdsaSig::sign(&digest, &private_key)?;
+    Ok(sig.to_der()?)
+}
+
+/// Gets the number of certificates in the certificate chain.
+#[no_mangle]
+pub extern "C" fn AVmAttestationResult_getCertificateCount(res: &AttestationResult) -> usize {
+    res.certificateChain.len()
+}
+
+/// Retrieves the certificate at the given `index` from the certificate chain in the provided
+/// attestation result.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
+/// * `index` must be within the range of [0, number of certificates). The number of certificates
+///   can be obtained with `AVmAttestationResult_getCertificateCount`.
+/// * The region of memory beginning at `data` with `size` bytes must not overlap with the
+///  region of memory `res` points to.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_getCertificateAt(
+    res: &AttestationResult,
+    index: usize,
+    data: *mut u8,
+    size: usize,
+) -> usize {
+    let certificate =
+        &res.certificateChain.get(index).expect("The index is out of bounds.").encodedCertificate;
+    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 `certificate` 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(
                 certificate.as_ptr(),
-                buffer,
+                data.as_ptr(),
                 std::cmp::min(certificate.len(), size),
-            );
-        }
+            )
+        };
     }
     certificate.len()
 }
 
-fn try_request_attestation(challenge: &[u8]) -> Result<Vec<u8>> {
-    let certificate = get_vm_payload_service()?
-        .requestAttestation(challenge)
-        .context("Failed to request attestation")?;
-    Ok(certificate)
+/// Frees all the data owned by given attestation result and result itself.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `res` must point to a valid `AttestationResult` and has not been freed before.
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_free(res: *mut AttestationResult) {
+    if !res.is_null() {
+        // SAFETY: The result is only freed once is ensured by the caller.
+        let res = unsafe { Box::from_raw(res) };
+        drop(res)
+    }
 }
 
 /// Gets the path to the APK contents.
diff --git a/vm_payload/src/lib.rs b/vm_payload/src/lib.rs
index 4d059d1..e305769 100644
--- a/vm_payload/src/lib.rs
+++ b/vm_payload/src/lib.rs
@@ -17,7 +17,9 @@
 mod api;
 
 pub use api::{
-    AVmPayload_getCertificate, AVmPayload_getDiceAttestationCdi,
-    AVmPayload_getDiceAttestationChain, AVmPayload_getVmInstanceSecret,
-    AVmPayload_notifyPayloadReady,
+    AVmAttestationResult_free, AVmAttestationResult_getCertificateAt,
+    AVmAttestationResult_getCertificatesCount, AVmAttestationResult_getPrivateKey,
+    AVmAttestationResult_resultToString, AVmAttestationResult_sign,
+    AVmPayload_getDiceAttestationCdi, AVmPayload_getDiceAttestationChain,
+    AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_requestAttestation,
 };