Merge changes I817bce28,I2189ea4b,I674d8edf into main
* changes:
[bssl] Implement nostd bssl wrapper for ECDSA sign/verify
[bssl] Map BoringSSL error code in EC/ECDSA libraries to Error type
[attestation] Build the BoringSSL EcKey from the COSE public key
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/code.rs b/libs/bssl/error/src/code.rs
index 9b661e9..a318a07 100644
--- a/libs/bssl/error/src/code.rs
+++ b/libs/bssl/error/src/code.rs
@@ -25,6 +25,8 @@
NoError,
Global(GlobalError),
Cipher(CipherError),
+ Ec(EcError),
+ Ecdsa(EcdsaError),
Unknown(BsslReasonCode, BsslLibraryCode),
}
@@ -102,3 +104,86 @@
write!(f, "An error occurred in a Cipher function: {self:?}")
}
}
+
+/// Errors occurred in the EC functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/ec.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum EcError {
+ BufferTooSmall,
+ CoordinatesOutOfRange,
+ D2IEcpkparametersFailure,
+ EcGroupNewByNameFailure,
+ Group2PkparametersFailure,
+ I2DEcpkparametersFailure,
+ IncompatibleObjects,
+ InvalidCompressedPoint,
+ InvalidCompressionBit,
+ InvalidEncoding,
+ InvalidField,
+ InvalidForm,
+ InvalidGroupOrder,
+ InvalidPrivateKey,
+ MissingParameters,
+ MissingPrivateKey,
+ NonNamedCurve,
+ NotInitialized,
+ Pkparameters2GroupFailure,
+ PointAtInfinity,
+ PointIsNotOnCurve,
+ SlotFull,
+ UndefinedGenerator,
+ UnknownGroup,
+ UnknownOrder,
+ WrongOrder,
+ BignumOutOfRange,
+ WrongCurveParameters,
+ DecodeError,
+ EncodeError,
+ GroupMismatch,
+ InvalidCofactor,
+ PublicKeyValidationFailed,
+ InvalidScalar,
+}
+
+impl From<EcError> for ReasonCode {
+ fn from(e: EcError) -> ReasonCode {
+ ReasonCode::Ec(e)
+ }
+}
+
+impl fmt::Display for EcError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "An error occurred in an EC function: {self:?}")
+ }
+}
+
+/// Errors occurred in the ECDSA functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/ecdsa.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum EcdsaError {
+ BadSignature,
+ MissingParameters,
+ NeedNewSetupValues,
+ NotImplemented,
+ RandomNumberGenerationFailed,
+ EncodeError,
+ TooManyIterations,
+}
+
+impl From<EcdsaError> for ReasonCode {
+ fn from(e: EcdsaError) -> ReasonCode {
+ ReasonCode::Ecdsa(e)
+ }
+}
+
+impl fmt::Display for EcdsaError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "An error occurred in an ECDSA function: {self:?}")
+ }
+}
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 88929af..89865d4 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -21,7 +21,7 @@
use core::{fmt, result};
use serde::{Deserialize, Serialize};
-pub use crate::code::{CipherError, GlobalError, ReasonCode};
+pub use crate::code::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
/// libbssl_avf result type.
pub type Result<T> = result::Result<T, Error>;
@@ -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,11 +73,16 @@
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,
+ ECDSA_sign,
+ ECDSA_size,
+ ECDSA_verify,
EVP_AEAD_CTX_new,
EVP_AEAD_CTX_open,
EVP_AEAD_CTX_seal,
HKDF,
HMAC,
RAND_bytes,
+ SHA256,
}
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 4c1ba5c..7e677c4 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -18,19 +18,28 @@
use crate::cbb::CbbFixed;
use crate::cbs::Cbs;
use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec;
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, ECDSA_sign, ECDSA_size,
+ ECDSA_verify, 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 +62,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 +115,73 @@
check_int_result(ret, ApiName::EC_KEY_check_key)
}
+ /// Verifies the DER-encoded ECDSA `signature` of the `digest` with the current `EcKey`.
+ ///
+ /// Returns Ok(()) if the verification succeeds, otherwise an error will be returned.
+ pub fn ecdsa_verify(&self, signature: &[u8], digest: &[u8]) -> Result<()> {
+ // The `type` argument should be 0 as required in the BoringSSL spec.
+ const TYPE: i32 = 0;
+
+ // SAFETY: This function only reads the given data within its bounds.
+ // The `EC_KEY` passed to this function has been initialized and checked non-null.
+ let ret = unsafe {
+ ECDSA_verify(
+ TYPE,
+ digest.as_ptr(),
+ digest.len(),
+ signature.as_ptr(),
+ signature.len(),
+ self.0.as_ptr(),
+ )
+ };
+ check_int_result(ret, ApiName::ECDSA_verify)
+ }
+
+ /// Signs the `digest` with the current `EcKey` using ECDSA.
+ ///
+ /// Returns the DER-encoded ECDSA signature.
+ pub fn ecdsa_sign(&self, digest: &[u8]) -> Result<Vec<u8>> {
+ // The `type` argument should be 0 as required in the BoringSSL spec.
+ const TYPE: i32 = 0;
+
+ let mut signature = vec![0u8; self.ecdsa_size()?];
+ let mut signature_len = 0;
+ // SAFETY: This function only reads the given data within its bounds.
+ // The `EC_KEY` passed to this function has been initialized and checked non-null.
+ let ret = unsafe {
+ ECDSA_sign(
+ TYPE,
+ digest.as_ptr(),
+ digest.len(),
+ signature.as_mut_ptr(),
+ &mut signature_len,
+ self.0.as_ptr(),
+ )
+ };
+ check_int_result(ret, ApiName::ECDSA_sign)?;
+ if signature.len() < (signature_len as usize) {
+ Err(to_call_failed_error(ApiName::ECDSA_sign))
+ } else {
+ signature.truncate(signature_len as usize);
+ Ok(signature)
+ }
+ }
+
+ /// Returns the maximum size of an ECDSA signature using the current `EcKey`.
+ fn ecdsa_size(&self) -> Result<usize> {
+ // SAFETY: This function only reads the `EC_KEY` that has been initialized
+ // and checked non-null when this instance is created.
+ let size = unsafe { ECDSA_size(self.0.as_ptr()) };
+ if size == 0 {
+ Err(to_call_failed_error(ApiName::ECDSA_size))
+ } else {
+ Ok(size)
+ }
+ }
+
/// 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 +191,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 +290,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 +341,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 +359,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/src/err.rs b/libs/bssl/src/err.rs
index 1ee40c9..7040441 100644
--- a/libs/bssl/src/err.rs
+++ b/libs/bssl/src/err.rs
@@ -14,7 +14,7 @@
//! Wrappers of the error handling functions in BoringSSL err.h.
-use bssl_avf_error::{CipherError, GlobalError, ReasonCode};
+use bssl_avf_error::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
use bssl_ffi::{self, ERR_get_error, ERR_GET_LIB_RUST, ERR_GET_REASON_RUST};
const NO_ERROR_REASON_CODE: i32 = 0;
@@ -75,6 +75,8 @@
fn map_library_reason_code(reason: i32, lib: i32) -> Option<ReasonCode> {
u32::try_from(lib).ok().and_then(|x| match x {
bssl_ffi::ERR_LIB_CIPHER => map_cipher_reason_code(reason).map(ReasonCode::Cipher),
+ bssl_ffi::ERR_LIB_EC => map_ec_reason_code(reason).map(ReasonCode::Ec),
+ bssl_ffi::ERR_LIB_ECDSA => map_ecdsa_reason_code(reason).map(ReasonCode::Ecdsa),
_ => None,
})
}
@@ -110,3 +112,60 @@
};
Some(error)
}
+
+fn map_ec_reason_code(reason: i32) -> Option<EcError> {
+ let error = match reason {
+ bssl_ffi::EC_R_BUFFER_TOO_SMALL => EcError::BufferTooSmall,
+ bssl_ffi::EC_R_COORDINATES_OUT_OF_RANGE => EcError::CoordinatesOutOfRange,
+ bssl_ffi::EC_R_D2I_ECPKPARAMETERS_FAILURE => EcError::D2IEcpkparametersFailure,
+ bssl_ffi::EC_R_EC_GROUP_NEW_BY_NAME_FAILURE => EcError::EcGroupNewByNameFailure,
+ bssl_ffi::EC_R_GROUP2PKPARAMETERS_FAILURE => EcError::Group2PkparametersFailure,
+ bssl_ffi::EC_R_I2D_ECPKPARAMETERS_FAILURE => EcError::I2DEcpkparametersFailure,
+ bssl_ffi::EC_R_INCOMPATIBLE_OBJECTS => EcError::IncompatibleObjects,
+ bssl_ffi::EC_R_INVALID_COMPRESSED_POINT => EcError::InvalidCompressedPoint,
+ bssl_ffi::EC_R_INVALID_COMPRESSION_BIT => EcError::InvalidCompressionBit,
+ bssl_ffi::EC_R_INVALID_ENCODING => EcError::InvalidEncoding,
+ bssl_ffi::EC_R_INVALID_FIELD => EcError::InvalidField,
+ bssl_ffi::EC_R_INVALID_FORM => EcError::InvalidForm,
+ bssl_ffi::EC_R_INVALID_GROUP_ORDER => EcError::InvalidGroupOrder,
+ bssl_ffi::EC_R_INVALID_PRIVATE_KEY => EcError::InvalidPrivateKey,
+ bssl_ffi::EC_R_MISSING_PARAMETERS => EcError::MissingParameters,
+ bssl_ffi::EC_R_MISSING_PRIVATE_KEY => EcError::MissingPrivateKey,
+ bssl_ffi::EC_R_NON_NAMED_CURVE => EcError::NonNamedCurve,
+ bssl_ffi::EC_R_NOT_INITIALIZED => EcError::NotInitialized,
+ bssl_ffi::EC_R_PKPARAMETERS2GROUP_FAILURE => EcError::Pkparameters2GroupFailure,
+ bssl_ffi::EC_R_POINT_AT_INFINITY => EcError::PointAtInfinity,
+ bssl_ffi::EC_R_POINT_IS_NOT_ON_CURVE => EcError::PointIsNotOnCurve,
+ bssl_ffi::EC_R_SLOT_FULL => EcError::SlotFull,
+ bssl_ffi::EC_R_UNDEFINED_GENERATOR => EcError::UndefinedGenerator,
+ bssl_ffi::EC_R_UNKNOWN_GROUP => EcError::UnknownGroup,
+ bssl_ffi::EC_R_UNKNOWN_ORDER => EcError::UnknownOrder,
+ bssl_ffi::EC_R_WRONG_ORDER => EcError::WrongOrder,
+ bssl_ffi::EC_R_BIGNUM_OUT_OF_RANGE => EcError::BignumOutOfRange,
+ bssl_ffi::EC_R_WRONG_CURVE_PARAMETERS => EcError::WrongCurveParameters,
+ bssl_ffi::EC_R_DECODE_ERROR => EcError::DecodeError,
+ bssl_ffi::EC_R_ENCODE_ERROR => EcError::EncodeError,
+ bssl_ffi::EC_R_GROUP_MISMATCH => EcError::GroupMismatch,
+ bssl_ffi::EC_R_INVALID_COFACTOR => EcError::InvalidCofactor,
+ bssl_ffi::EC_R_PUBLIC_KEY_VALIDATION_FAILED => EcError::PublicKeyValidationFailed,
+ bssl_ffi::EC_R_INVALID_SCALAR => EcError::InvalidScalar,
+ _ => return None,
+ };
+ Some(error)
+}
+
+fn map_ecdsa_reason_code(reason: i32) -> Option<EcdsaError> {
+ let error = match reason {
+ bssl_ffi::ECDSA_R_BAD_SIGNATURE => EcdsaError::BadSignature,
+ bssl_ffi::ECDSA_R_MISSING_PARAMETERS => EcdsaError::MissingParameters,
+ bssl_ffi::ECDSA_R_NEED_NEW_SETUP_VALUES => EcdsaError::NeedNewSetupValues,
+ bssl_ffi::ECDSA_R_NOT_IMPLEMENTED => EcdsaError::NotImplemented,
+ bssl_ffi::ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED => {
+ EcdsaError::RandomNumberGenerationFailed
+ }
+ bssl_ffi::ECDSA_R_ENCODE_ERROR => EcdsaError::EncodeError,
+ bssl_ffi::ECDSA_R_TOO_MANY_ITERATIONS => EcdsaError::TooManyIterations,
+ _ => return None,
+ };
+ Some(error)
+}
diff --git a/libs/bssl/src/hmac.rs b/libs/bssl/src/hmac.rs
index ddbbe4a..1b3a403 100644
--- a/libs/bssl/src/hmac.rs
+++ b/libs/bssl/src/hmac.rs
@@ -15,15 +15,14 @@
//! Wrappers of the HMAC functions in BoringSSL hmac.h.
use crate::digest::Digester;
+use crate::sha::SHA256_DIGEST_LENGTH;
use crate::util::to_call_failed_error;
use bssl_avf_error::{ApiName, Result};
-use bssl_ffi::{HMAC, SHA256_DIGEST_LENGTH};
-
-const SHA256_LEN: usize = SHA256_DIGEST_LENGTH as usize;
+use bssl_ffi::HMAC;
/// Computes the HMAC using SHA-256 for the given `data` with the given `key`.
-pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; SHA256_LEN]> {
- hmac::<SHA256_LEN>(key, data, Digester::sha256())
+pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; SHA256_DIGEST_LENGTH]> {
+ hmac::<SHA256_DIGEST_LENGTH>(key, data, Digester::sha256())
}
/// Computes the HMAC for the given `data` with the given `key` and `digester`.
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index de81368..8e3abcf 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -27,9 +27,10 @@
mod hkdf;
mod hmac;
mod rand;
+mod sha;
mod util;
-pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
+pub use bssl_avf_error::{ApiName, CipherError, EcError, EcdsaError, Error, ReasonCode, Result};
pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
pub use cbb::CbbFixed;
@@ -39,3 +40,4 @@
pub use hkdf::hkdf;
pub use hmac::hmac_sha256;
pub use rand::rand_bytes;
+pub use sha::sha256;
diff --git a/libs/bssl/src/sha.rs b/libs/bssl/src/sha.rs
new file mode 100644
index 0000000..6c65d7f
--- /dev/null
+++ b/libs/bssl/src/sha.rs
@@ -0,0 +1,35 @@
+// 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 SHA functions in BoringSSL sha.h.
+
+use crate::util::to_call_failed_error;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::SHA256;
+
+/// The length of a SHA256 digest.
+pub(crate) const SHA256_DIGEST_LENGTH: usize = bssl_ffi::SHA256_DIGEST_LENGTH as usize;
+
+/// Computes the SHA256 digest of the provided `data``.
+pub fn sha256(data: &[u8]) -> Result<[u8; SHA256_DIGEST_LENGTH]> {
+ let mut out = [0u8; SHA256_DIGEST_LENGTH];
+ // SAFETY: This function reads `data` and writes to `out` within its bounds.
+ // `out` has `SHA256_DIGEST_LENGTH` bytes of space for write.
+ let ret = unsafe { SHA256(data.as_ptr(), data.len(), out.as_mut_ptr()) };
+ if ret.is_null() {
+ Err(to_call_failed_error(ApiName::SHA256))
+ } else {
+ Ok(out)
+ }
+}
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index a013fba..3dd243c 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,14 +12,70 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use bssl_avf::{EcKey, Result};
+use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, Result};
+use coset::CborSerializable;
+
+const MESSAGE1: &[u8] = b"test message 1";
+const MESSAGE2: &[u8] = b"test message 2";
#[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(())
+}
+
+#[test]
+fn ecdsa_p256_signing_and_verification_succeed() -> Result<()> {
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
+ let digest = sha256(MESSAGE1)?;
+
+ let signature = ec_key.ecdsa_sign(&digest)?;
+ ec_key.ecdsa_verify(&signature, &digest)
+}
+
+#[test]
+fn verifying_ecdsa_p256_signed_with_a_different_key_fails() -> Result<()> {
+ let mut ec_key1 = EcKey::new_p256()?;
+ ec_key1.generate_key()?;
+ let digest = sha256(MESSAGE1)?;
+ let signature = ec_key1.ecdsa_sign(&digest)?;
+
+ let mut ec_key2 = EcKey::new_p256()?;
+ ec_key2.generate_key()?;
+ let err = ec_key2.ecdsa_verify(&signature, &digest).unwrap_err();
+ let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
+ assert_eq!(expected_err, err);
+ Ok(())
+}
+
+#[test]
+fn verifying_ecdsa_p256_signed_with_a_different_message_fails() -> Result<()> {
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
+ let digest1 = sha256(MESSAGE1)?;
+ let signature = ec_key.ecdsa_sign(&digest1)?;
+ let digest2 = sha256(MESSAGE2)?;
+
+ let err = ec_key.ecdsa_verify(&signature, &digest2).unwrap_err();
+ let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
+ assert_eq!(expected_err, err);
+ Ok(())
+}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 74c26d3..612605f 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -17,22 +17,39 @@
use crate::keyblob::decrypt_private_key;
use alloc::vec::Vec;
-use bssl_avf::EcKey;
+use bssl_avf::{sha256, EcKey};
use core::result;
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| {
+ ecdsa_verify(&ec_public_key, signature, message)
+ })?;
// TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
// cert with RKP VM's DICE chain.
@@ -49,3 +66,9 @@
// `_private_key`.
Err(RequestProcessingError::OperationUnimplemented)
}
+
+fn ecdsa_verify(key: &EcKey, signature: &[u8], message: &[u8]) -> bssl_avf::Result<()> {
+ // The message was signed with ECDSA with curve P-256 and SHA-256 at the signature generation.
+ let digest = sha256(message)?;
+ key.ecdsa_verify(signature, &digest)
+}
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 =