[attestation] Build the BoringSSL EcKey from the COSE public key
Bug: 309440321
Test: atest rialto_test
Change-Id: I674d8edf2d806e7455b836b7a50a776ac75bc800
diff --git a/libs/bssl/Android.bp b/libs/bssl/Android.bp
index 0a2f334..ff45af9 100644
--- a/libs/bssl/Android.bp
+++ b/libs/bssl/Android.bp
@@ -23,6 +23,7 @@
rustlibs: [
"libbssl_avf_error_nostd",
"libbssl_ffi_nostd",
+ "libciborium_nostd",
"libcoset_nostd",
"liblog_rust_nostd",
"libzeroize_nostd",
@@ -44,5 +45,6 @@
defaults: ["libbssl_avf_test_defaults"],
rustlibs: [
"libbssl_avf_nostd",
+ "libcoset_nostd",
],
}
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 88929af..c81d450 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -34,6 +34,12 @@
/// An unexpected internal error occurred.
InternalError,
+
+ /// Failed to decode the COSE_Key.
+ CoseKeyDecodingFailed,
+
+ /// Unimplemented operation.
+ Unimplemented,
}
impl fmt::Display for Error {
@@ -43,6 +49,8 @@
write!(f, "Failed to invoke the BoringSSL API: {api_name:?}. Reason: {reason}")
}
Self::InternalError => write!(f, "An unexpected internal error occurred"),
+ Self::CoseKeyDecodingFailed => write!(f, "Failed to decode the COSE_Key"),
+ Self::Unimplemented => write!(f, "Unimplemented operation"),
}
}
}
@@ -53,6 +61,7 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ApiName {
BN_new,
+ BN_bin2bn,
BN_bn2bin_padded,
CBB_flush,
CBB_len,
@@ -64,6 +73,7 @@
EC_KEY_marshal_private_key,
EC_KEY_parse_private_key,
EC_KEY_new_by_curve_name,
+ EC_KEY_set_public_key_affine_coordinates,
EC_POINT_get_affine_coordinates,
EVP_AEAD_CTX_new,
EVP_AEAD_CTX_open,
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 4c1ba5c..572ad4b 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -21,16 +21,24 @@
use alloc::vec::Vec;
use bssl_avf_error::{ApiName, Error, Result};
use bssl_ffi::{
- BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_GROUP_new_by_curve_name,
- EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key, EC_KEY_get0_group, EC_KEY_get0_public_key,
- EC_KEY_marshal_private_key, EC_KEY_new_by_curve_name, EC_KEY_parse_private_key,
+ BN_bin2bn, BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len,
+ EC_GROUP_new_by_curve_name, EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key,
+ EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_marshal_private_key,
+ EC_KEY_new_by_curve_name, EC_KEY_parse_private_key, EC_KEY_set_public_key_affine_coordinates,
EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM, EC_GROUP, EC_KEY, EC_POINT,
};
+use ciborium::Value;
use core::ptr::{self, NonNull};
use core::result;
-use coset::{iana, CoseKey, CoseKeyBuilder};
+use coset::{
+ iana::{self, EnumI64},
+ CborSerializable, CoseKey, CoseKeyBuilder, Label,
+};
+use log::error;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
+const ES256_ALGO: iana::Algorithm = iana::Algorithm::ES256;
+const P256_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
const P256_AFFINE_COORDINATE_SIZE: usize = 32;
type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
@@ -53,10 +61,46 @@
let ec_key = unsafe {
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1) // EC P-256 CURVE Nid
};
- let mut ec_key = NonNull::new(ec_key)
+ NonNull::new(ec_key)
.map(Self)
- .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))?;
- ec_key.generate_key()?;
+ .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))
+ }
+
+ /// Constructs an `EcKey` instance from the provided COSE_Key encoded public key slice.
+ pub fn from_cose_public_key(cose_key: &[u8]) -> Result<Self> {
+ let cose_key = CoseKey::from_slice(cose_key).map_err(|e| {
+ error!("Failed to deserialize COSE_Key: {e:?}");
+ Error::CoseKeyDecodingFailed
+ })?;
+ if cose_key.alg != Some(coset::Algorithm::Assigned(ES256_ALGO)) {
+ error!(
+ "Only ES256 algorithm is supported. Algo type in the COSE Key: {:?}",
+ cose_key.alg
+ );
+ return Err(Error::Unimplemented);
+ }
+ let crv = get_label_value(&cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+ if &Value::from(P256_CURVE.to_i64()) != crv {
+ error!("Only EC P-256 curve is supported. Curve type in the COSE Key: {crv:?}");
+ return Err(Error::Unimplemented);
+ }
+
+ let x = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+ let y = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+
+ check_p256_affine_coordinate_size(x)?;
+ check_p256_affine_coordinate_size(y)?;
+
+ let x = BigNum::from_slice(x)?;
+ let y = BigNum::from_slice(y)?;
+
+ let ec_key = EcKey::new_p256()?;
+ // SAFETY: All the parameters are checked non-null and initialized.
+ // The function only reads the coordinates x and y within their bounds.
+ let ret = unsafe {
+ EC_KEY_set_public_key_affine_coordinates(ec_key.0.as_ptr(), x.as_ref(), y.as_ref())
+ };
+ check_int_result(ret, ApiName::EC_KEY_set_public_key_affine_coordinates)?;
Ok(ec_key)
}
@@ -70,9 +114,23 @@
check_int_result(ret, ApiName::EC_KEY_check_key)
}
+ /// Verifies the DER-encoded ECDSA `signature` of the `message` with the current `EcKey`.
+ pub fn ecdsa_verify(&self, _signature: &[u8], _message: &[u8]) -> Result<()> {
+ // TODO(b/310634099): Implement ECDSA sign with `bssl::ECDSA_do_sign`.
+ Ok(())
+ }
+
+ /// Signs the `message` with the current `EcKey` using ECDSA.
+ ///
+ /// Returns the DER-encoded ECDSA signature.
+ pub fn ecdsa_sign(&self, _message: &[u8]) -> Result<Vec<u8>> {
+ // TODO(b/310634099): Implement ECDSA verify with `bssl::ECDSA_do_verify`.
+ Ok(Vec::new())
+ }
+
/// Generates a random, private key, calculates the corresponding public key and stores both
/// in the `EC_KEY`.
- fn generate_key(&mut self) -> Result<()> {
+ pub fn generate_key(&mut self) -> Result<()> {
// SAFETY: The non-null pointer is created with `EC_KEY_new_by_curve_name` and should
// point to a valid `EC_KEY`.
// The randomness is provided by `getentropy()` in `vmbase`.
@@ -82,12 +140,10 @@
/// Returns the `CoseKey` for the public key.
pub fn cose_public_key(&self) -> Result<CoseKey> {
- const ALGO: iana::Algorithm = iana::Algorithm::ES256;
- const CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
-
let (x, y) = self.public_key_coordinates()?;
- let key =
- CoseKeyBuilder::new_ec2_pub_key(CURVE, x.to_vec(), y.to_vec()).algorithm(ALGO).build();
+ let key = CoseKeyBuilder::new_ec2_pub_key(P256_CURVE, x.to_vec(), y.to_vec())
+ .algorithm(ES256_ALGO)
+ .build();
Ok(key)
}
@@ -183,6 +239,30 @@
}
}
+fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
+ Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
+ error!("Value not a bstr.");
+ Error::CoseKeyDecodingFailed
+ })?)
+}
+
+fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+ Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
+}
+
+fn check_p256_affine_coordinate_size(coordinate: &[u8]) -> Result<()> {
+ if P256_AFFINE_COORDINATE_SIZE == coordinate.len() {
+ Ok(())
+ } else {
+ error!(
+ "The size of the affine coordinate '{}' does not match the expected size '{}'",
+ coordinate.len(),
+ P256_AFFINE_COORDINATE_SIZE
+ );
+ Err(Error::CoseKeyDecodingFailed)
+ }
+}
+
/// A u8 vector that is zeroed when dropped.
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct ZVec(Vec<u8>);
@@ -210,6 +290,13 @@
}
impl BigNum {
+ fn from_slice(x: &[u8]) -> Result<Self> {
+ // SAFETY: The function reads `x` within its bounds, and the returned
+ // pointer is checked below.
+ let bn = unsafe { BN_bin2bn(x.as_ptr(), x.len(), ptr::null_mut()) };
+ NonNull::new(bn).map(Self).ok_or(to_call_failed_error(ApiName::BN_bin2bn))
+ }
+
fn new() -> Result<Self> {
// SAFETY: The returned pointer is checked below.
let bn = unsafe { BN_new() };
@@ -221,6 +308,14 @@
}
}
+impl AsRef<BIGNUM> for BigNum {
+ fn as_ref(&self) -> &BIGNUM {
+ // SAFETY: The pointer is valid and points to an initialized instance of `BIGNUM`
+ // when the instance was created.
+ unsafe { self.0.as_ref() }
+ }
+}
+
/// Converts the `BigNum` to a big-endian integer. The integer is padded with leading zeros up to
/// size `N`. The conversion fails if `N` is smaller thanthe size of the integer.
impl<const N: usize> TryFrom<BigNum> for [u8; N] {
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index a013fba..da176ae 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -13,13 +13,27 @@
// limitations under the License.
use bssl_avf::{EcKey, Result};
+use coset::CborSerializable;
#[test]
fn ec_private_key_serialization() -> Result<()> {
- let ec_key = EcKey::new_p256()?;
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
let der_encoded_ec_private_key = ec_key.ec_private_key()?;
let deserialized_ec_key = EcKey::from_ec_private_key(der_encoded_ec_private_key.as_slice())?;
assert_eq!(ec_key.cose_public_key()?, deserialized_ec_key.cose_public_key()?);
Ok(())
}
+
+#[test]
+fn cose_public_key_serialization() -> Result<()> {
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
+ let cose_key = ec_key.cose_public_key()?;
+ let cose_key_data = cose_key.clone().to_vec().unwrap();
+ let deserialized_ec_key = EcKey::from_cose_public_key(&cose_key_data)?;
+
+ assert_eq!(cose_key, deserialized_ec_key.cose_public_key()?);
+ Ok(())
+}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 74c26d3..b1fd03d 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -22,17 +22,34 @@
use coset::{CborSerializable, CoseSign};
use diced_open_dice::DiceArtifacts;
use log::error;
-use service_vm_comm::{ClientVmAttestationParams, Csr, RequestProcessingError};
+use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
type Result<T> = result::Result<T, RequestProcessingError>;
+const ATTESTATION_KEY_SIGNATURE_INDEX: usize = 1;
+
pub(super) fn request_attestation(
params: ClientVmAttestationParams,
dice_artifacts: &dyn DiceArtifacts,
) -> Result<Vec<u8>> {
let csr = Csr::from_cbor_slice(¶ms.csr)?;
- let _cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
- // TODO(b/309440321): Verify the signatures in the `_cose_sign`.
+ let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
+ let csr_payload = cose_sign.payload.as_ref().ok_or_else(|| {
+ error!("No CsrPayload found in the CSR");
+ RequestProcessingError::InternalError
+ })?;
+ let csr_payload = CsrPayload::from_cbor_slice(csr_payload)?;
+
+ // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
+ let aad = &[];
+
+ // TODO(b/310931749): Verify the first signature with CDI_Leaf_Pub of
+ // the DICE chain in `cose_sign`.
+
+ let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
+ cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
+ ec_public_key.ecdsa_verify(signature, message)
+ })?;
// TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
// cert with RKP VM's DICE chain.
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index c2c13b3..2c01451 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -44,7 +44,8 @@
dice_artifacts: &dyn DiceArtifacts,
) -> Result<EcdsaP256KeyPair> {
let hmac_key = derive_hmac_key(dice_artifacts)?;
- let ec_key = EcKey::new_p256()?;
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
let key_blob =