Merge "[attestation] Validate DICE chain signatures and CSR signature" into main
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 7f01c6c..82a2d5e 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -91,6 +91,7 @@
     ECDSA_sign,
     ECDSA_size,
     ECDSA_verify,
+    ED25519_verify,
     EVP_AEAD_CTX_new,
     EVP_AEAD_CTX_open,
     EVP_AEAD_CTX_seal,
diff --git a/libs/bssl/src/curve25519.rs b/libs/bssl/src/curve25519.rs
new file mode 100644
index 0000000..499a3d0
--- /dev/null
+++ b/libs/bssl/src/curve25519.rs
@@ -0,0 +1,39 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Wrappers of the Curve25519 related functions in BoringSSL curve25519.h.
+
+use crate::util::check_int_result;
+use bssl_avf_error::{ApiName, Result};
+
+const ED25519_PUBLIC_KEY_LEN: usize = bssl_ffi::ED25519_PUBLIC_KEY_LEN as usize;
+const ED25519_SIGNATURE_LEN: usize = bssl_ffi::ED25519_SIGNATURE_LEN as usize;
+
+/// Verifies the signature of a message with the given ED25519 public key.
+pub fn ed25519_verify(
+    message: &[u8],
+    signature: &[u8; ED25519_SIGNATURE_LEN],
+    public_key: &[u8; ED25519_PUBLIC_KEY_LEN],
+) -> Result<()> {
+    // SAFETY: The function only reads the parameters within their bounds.
+    let ret = unsafe {
+        bssl_ffi::ED25519_verify(
+            message.as_ptr(),
+            message.len(),
+            signature.as_ptr(),
+            public_key.as_ptr(),
+        )
+    };
+    check_int_result(ret, ApiName::ED25519_verify)
+}
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index a420168..ad51b61 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -21,6 +21,7 @@
 mod aead;
 mod cbb;
 mod cbs;
+mod curve25519;
 mod digest;
 mod ec_key;
 mod err;
@@ -36,6 +37,7 @@
 pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
 pub use cbb::CbbFixed;
 pub use cbs::Cbs;
+pub use curve25519::ed25519_verify;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
 pub use evp::{PKey, PKeyType};
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index cfdac2d..871b5f1 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -30,6 +30,7 @@
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
+const DICE_CDI_LEAF_SIGNATURE_INDEX: usize = 0;
 const ATTESTATION_KEY_SIGNATURE_INDEX: usize = 1;
 
 pub(super) fn request_attestation(
@@ -58,8 +59,9 @@
     let aad = &[];
 
     // Verifies the first signature with the leaf private key in the DICE chain.
-    // TODO(b/310931749): Verify the first signature with CDI_Leaf_Pub of
-    // the DICE chain in `cose_sign`.
+    cose_sign.verify_signature(DICE_CDI_LEAF_SIGNATURE_INDEX, aad, |signature, message| {
+        client_vm_dice_chain.microdroid_payload().subject_public_key.verify(signature, message)
+    })?;
 
     // Verifies the second signature with the public key in the CSR payload.
     let ec_public_key = EcKey::from_cose_public_key_slice(&csr_payload.public_key)?;
diff --git a/service_vm/requests/src/dice.rs b/service_vm/requests/src/dice.rs
index 557b678..6f67049 100644
--- a/service_vm/requests/src/dice.rs
+++ b/service_vm/requests/src/dice.rs
@@ -16,15 +16,19 @@
 
 use alloc::string::String;
 use alloc::vec::Vec;
+use bssl_avf::{ed25519_verify, Digester, EcKey};
 use cbor_util::{
-    cbor_value_type, value_to_array, value_to_byte_array, value_to_bytes, value_to_map,
-    value_to_num, value_to_text,
+    cbor_value_type, get_label_value, get_label_value_as_bytes, value_to_array,
+    value_to_byte_array, value_to_bytes, value_to_map, value_to_num, value_to_text,
 };
 use ciborium::value::Value;
 use core::cell::OnceCell;
 use core::result;
 use coset::{
-    self, iana, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation,
+    self,
+    iana::{self, EnumI64},
+    Algorithm, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation, KeyType,
+    Label,
 };
 use diced_open_dice::{DiceMode, HASH_SIZE};
 use log::error;
@@ -58,7 +62,7 @@
 /// ]
 #[derive(Debug, Clone)]
 pub(crate) struct ClientVmDiceChain {
-    pub(crate) payloads: Vec<DiceChainEntryPayload>,
+    payloads: Vec<DiceChainEntryPayload>,
 }
 
 impl ClientVmDiceChain {
@@ -130,7 +134,7 @@
         &self.payloads[self.payloads.len() - 2]
     }
 
-    fn microdroid_payload(&self) -> &DiceChainEntryPayload {
+    pub(crate) fn microdroid_payload(&self) -> &DiceChainEntryPayload {
         &self.payloads[self.payloads.len() - 1]
     }
 
@@ -198,15 +202,69 @@
     }
 }
 
+impl PublicKey {
+    /// Verifies the signature of the provided message with the public key.
+    ///
+    /// This function supports the following key/algorithm types as specified in
+    /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
+    /// generateCertificateRequestV2.cddl:
+    ///
+    /// PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384
+    pub(crate) fn verify(&self, signature: &[u8], message: &[u8]) -> Result<()> {
+        match &self.0.kty {
+            KeyType::Assigned(iana::KeyType::EC2) => {
+                let public_key = EcKey::from_cose_public_key(&self.0)?;
+                let Some(Algorithm::Assigned(alg)) = self.0.alg else {
+                    error!("Invalid algorithm in COSE key {:?}", self.0.alg);
+                    return Err(RequestProcessingError::InvalidDiceChain);
+                };
+                let digester = match alg {
+                    iana::Algorithm::ES256 => Digester::sha256(),
+                    iana::Algorithm::ES384 => Digester::sha384(),
+                    _ => {
+                        error!("Unsupported algorithm in EC2 key: {:?}", alg);
+                        return Err(RequestProcessingError::InvalidDiceChain);
+                    }
+                };
+                let digest = digester.digest(message)?;
+                Ok(public_key.ecdsa_verify(signature, &digest)?)
+            }
+            KeyType::Assigned(iana::KeyType::OKP) => {
+                let curve_type =
+                    get_label_value(&self.0, Label::Int(iana::OkpKeyParameter::Crv.to_i64()))?;
+                if curve_type != &Value::from(iana::EllipticCurve::Ed25519.to_i64()) {
+                    error!("Unsupported curve type in OKP COSE key: {:?}", curve_type);
+                    return Err(RequestProcessingError::OperationUnimplemented);
+                }
+                let x = get_label_value_as_bytes(
+                    &self.0,
+                    Label::Int(iana::OkpKeyParameter::X.to_i64()),
+                )?;
+                let public_key = x.try_into().map_err(|_| {
+                    error!("Invalid ED25519 public key size: {}", x.len());
+                    RequestProcessingError::InvalidDiceChain
+                })?;
+                let signature = signature.try_into().map_err(|_| {
+                    error!("Invalid ED25519 signature size: {}", signature.len());
+                    RequestProcessingError::InvalidDiceChain
+                })?;
+                Ok(ed25519_verify(message, signature, public_key)?)
+            }
+            kty => {
+                error!("Unsupported key type in COSE key: {:?}", kty);
+                Err(RequestProcessingError::OperationUnimplemented)
+            }
+        }
+    }
+}
+
 /// Represents a partially decoded `DiceChainEntryPayload`. The whole payload is defined in:
 ///
 /// hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
 /// generateCertificateRequestV2.cddl
 #[derive(Debug, Clone)]
 pub(crate) struct DiceChainEntryPayload {
-    /// TODO(b/310931749): Verify the DICE chain entry using the subject public key.
-    #[allow(dead_code)]
-    subject_public_key: PublicKey,
+    pub(crate) subject_public_key: PublicKey,
     mode: DiceMode,
     /// TODO(b/271275206): Verify Microdroid kernel authority and code hashes.
     #[allow(dead_code)]
@@ -221,10 +279,13 @@
     /// extracts payload from the value.
     fn validate_cose_signature_and_extract_payload(
         value: Value,
-        _authority_public_key: &PublicKey,
+        authority_public_key: &PublicKey,
     ) -> Result<Self> {
         let cose_sign1 = CoseSign1::from_cbor_value(value)?;
-        // TODO(b/310931749): Verify the DICE chain entry using `authority_public_key`.
+        let aad = &[]; // AAD is not used in DICE chain entry.
+        cose_sign1.verify_signature(aad, |signature, message| {
+            authority_public_key.verify(signature, message)
+        })?;
 
         let payload = cose_sign1.payload.ok_or_else(|| {
             error!("No payload found in the DICE chain entry");