[service-vm] Deserialize CBOR-encoded CSR byte array into struct

This CL deserializes the CBOR-encoded CSR provided as a param for
VM attestation into a `Csr` struct.

It also extracts the CSR/attestation key generation function from
microdroid into a standalone library so that we can reuse it in
e2e tests.

The new test target has been added to the busytown config at
cl/581170242.

Test: atest rialto_test
Bug: 309440321
Change-Id: Idbfadd0418822bdf46d6764b9db484f4865aa267
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 77ccc1d..a9193d7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -105,6 +105,9 @@
       "path": "packages/modules/Virtualization/rialto"
     },
     {
+      "path": "packages/modules/Virtualization/service_vm/client_vm_csr"
+    },
+    {
       "path": "packages/modules/Virtualization/service_vm/comm"
     },
     {
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 93f49ef..8481edf 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -23,6 +23,7 @@
         "libbinder_rs",
         "libbyteorder",
         "libcap_rust",
+        "libclient_vm_csr",
         "libciborium",
         "libcoset",
         "libdiced_open_dice",
@@ -46,12 +47,10 @@
         "libserde",
         "libserde_cbor",
         "libserde_json",
-        "libservice_vm_comm",
         "libthiserror",
         "libuuid",
         "libvsock",
         "librand",
-        "libzeroize",
     ],
     init_rc: ["microdroid_manager.rc"],
     multilib: {
@@ -72,7 +71,6 @@
     defaults: ["microdroid_manager_defaults"],
     test_suites: ["general-tests"],
     rustlibs: [
-        "libhwtrust",
         "libtempfile",
     ],
     multilib: {
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 0661314..d3346d8 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -22,31 +22,12 @@
 use anyhow::{anyhow, Context, Result};
 use avflog::LogResult;
 use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult, Status};
-use diced_open_dice::{DiceArtifacts, derive_cdi_leaf_priv, PrivateKey, sign};
+use client_vm_csr::{generate_attestation_key_and_csr, ClientVmAttestationData};
+use diced_open_dice::DiceArtifacts;
 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 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 {
@@ -90,11 +71,21 @@
 
     fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
         self.check_restricted_apis_allowed()?;
-        let (private_key, csr) = generate_attestation_key_and_csr(challenge, self.secret.dice())
+        let ClientVmAttestationData { 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 csr = csr
+            .into_cbor_vec()
             .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:?}")),
+                    Some(format!("Failed to serialize CSR into CBOR: {e:?}")),
                 )
             })
             .with_log()?;
@@ -106,93 +97,6 @@
     }
 }
 
-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 {
@@ -237,106 +141,3 @@
     });
     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/rialto/Android.bp b/rialto/Android.bp
index 326f6fc..28c261e 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -109,6 +109,8 @@
         "libandroid_logger",
         "libanyhow",
         "libciborium",
+        "libclient_vm_csr",
+        "libdiced_sample_inputs",
         "liblibc",
         "liblog_rust",
         "libservice_vm_comm",
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 53be583..0f59350 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -23,6 +23,7 @@
 };
 use anyhow::{bail, Context, Result};
 use ciborium::value::Value;
+use client_vm_csr::generate_attestation_key_and_csr;
 use log::info;
 use service_vm_comm::{
     ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
@@ -110,8 +111,18 @@
 }
 
 fn check_attestation_request(vm: &mut ServiceVm, key_blob: &[u8]) -> Result<()> {
-    let params =
-        ClientVmAttestationParams { csr: vec![], remotely_provisioned_key_blob: key_blob.to_vec() };
+    /// The following data was generated randomly with urandom.
+    const CHALLENGE: [u8; 16] = [
+        0x7d, 0x86, 0x58, 0x79, 0x3a, 0x09, 0xdf, 0x1c, 0xa5, 0x80, 0x80, 0x15, 0x2b, 0x13, 0x17,
+        0x5c,
+    ];
+    let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+    let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+
+    let params = ClientVmAttestationParams {
+        csr: attestation_data.csr.into_cbor_vec()?,
+        remotely_provisioned_key_blob: key_blob.to_vec(),
+    };
     let request = Request::RequestClientVmAttestation(params);
 
     let response = vm.process_request(request)?;
diff --git a/service_vm/client_vm_csr/Android.bp b/service_vm/client_vm_csr/Android.bp
new file mode 100644
index 0000000..8d738d8
--- /dev/null
+++ b/service_vm/client_vm_csr/Android.bp
@@ -0,0 +1,37 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libclient_vm_csr_defaults",
+    crate_name: "client_vm_csr",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libcoset",
+        "libdiced_open_dice",
+        "libopenssl",
+        "libservice_vm_comm",
+        "libzeroize",
+    ],
+}
+
+rust_library {
+    name: "libclient_vm_csr",
+    defaults: ["libclient_vm_csr_defaults"],
+    prefer_rlib: true,
+    apex_available: [
+        "com.android.virt",
+    ],
+}
+
+rust_test {
+    name: "libclient_vm_csr.test",
+    defaults: ["libclient_vm_csr_defaults"],
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "libciborium",
+        "libdiced_sample_inputs",
+        "libhwtrust",
+    ],
+}
diff --git a/service_vm/client_vm_csr/TEST_MAPPING b/service_vm/client_vm_csr/TEST_MAPPING
new file mode 100644
index 0000000..5bc06c0
--- /dev/null
+++ b/service_vm/client_vm_csr/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+  "avf-presubmit" : [
+    {
+      "name" : "libclient_vm_csr.test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/service_vm/client_vm_csr/src/lib.rs b/service_vm/client_vm_csr/src/lib.rs
new file mode 100644
index 0000000..512ecaf
--- /dev/null
+++ b/service_vm/client_vm_csr/src/lib.rs
@@ -0,0 +1,242 @@
+// 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.
+
+//! Generate the attestation key and CSR for client VM in the remote
+//! attestation.
+
+use anyhow::{anyhow, Context, Result};
+use coset::{
+    iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
+    CoseSignatureBuilder, HeaderBuilder,
+};
+use diced_open_dice::{derive_cdi_leaf_priv, sign, DiceArtifacts, PrivateKey};
+use openssl::{
+    bn::{BigNum, BigNumContext},
+    ec::{EcGroup, EcKey, EcKeyRef},
+    ecdsa::EcdsaSig,
+    nid::Nid,
+    pkey::Private,
+    sha::sha256,
+};
+use service_vm_comm::{Csr, CsrPayload};
+use zeroize::Zeroizing;
+
+/// Key parameters for the attestation key.
+///
+/// See service_vm/comm/client_vm_csr.cddl for more information about the attestation key.
+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;
+
+/// Represents the output of generating the attestation key and CSR for the client VM.
+pub struct ClientVmAttestationData {
+    /// DER-encoded ECPrivateKey to be attested.
+    pub private_key: Zeroizing<Vec<u8>>,
+
+    /// CSR containing client VM information and the public key corresponding to the
+    /// private key to be attested.
+    pub csr: Csr,
+}
+
+/// Generates the attestation key and CSR including the public key to be attested for the
+/// client VM in remote attestation.
+pub fn generate_attestation_key_and_csr(
+    challenge: &[u8],
+    dice_artifacts: &dyn DiceArtifacts,
+) -> Result<ClientVmAttestationData> {
+    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 private_key = attestation_key.private_key_to_der()?;
+    Ok(ClientVmAttestationData { private_key: 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())
+}
+
+#[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 was 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 ClientVmAttestationData { private_key, csr } =
+            generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+        let ec_private_key = EcKey::private_key_from_der(&private_key)?;
+        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/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 1081f3a..d53580b 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -18,9 +18,10 @@
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
 use core::result;
+use coset::{CborSerializable, CoseSign};
 use diced_open_dice::DiceArtifacts;
 use log::error;
-use service_vm_comm::{ClientVmAttestationParams, RequestProcessingError};
+use service_vm_comm::{ClientVmAttestationParams, Csr, RequestProcessingError};
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
@@ -28,10 +29,12 @@
     params: ClientVmAttestationParams,
     dice_artifacts: &dyn DiceArtifacts,
 ) -> Result<Vec<u8>> {
-    // TODO(b/309440321): Verify the signatures in the csr.
+    let csr = Csr::from_cbor_slice(&params.csr)?;
+    let _cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
+    // TODO(b/309440321): Verify the signatures in the `_cose_sign`.
 
-    // TODO(b/278717513): Compare client VM's DICE chain up to pvmfw cert with
-    // RKP VM's DICE chain.
+    // TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
+    // cert with RKP VM's DICE chain.
 
     let _private_key =
         decrypt_private_key(&params.remotely_provisioned_key_blob, dice_artifacts.cdi_seal())
@@ -41,6 +44,6 @@
             })?;
 
     // TODO(b/309441500): Build a new certificate signed with the remotely provisioned
-    // private key.
+    // `_private_key`.
     Err(RequestProcessingError::OperationUnimplemented)
 }