Adding verification for P256 signatures.
This change adds the necessary ssl_bindgen functions required to support
verifying DICE chains signed with P256 keys.
Test: atest --host libcert_request_validator_tests
Change-Id: I1fb60d64adc300b6a1f184b0b43476a82c60ff78
diff --git a/remote_provisioning/cert_validator/Android.bp b/remote_provisioning/cert_validator/Android.bp
index e5ee4e2..7c59908 100644
--- a/remote_provisioning/cert_validator/Android.bp
+++ b/remote_provisioning/cert_validator/Android.bp
@@ -8,9 +8,21 @@
wrapper_src: "libssl_wrapper.h",
source_stem: "bindings",
bindgen_flags: [
+ "--size_t-is-usize",
+ "--allowlist-function", "BN_bin2bn",
+ "--allowlist-function", "ECDSA_SIG_free",
+ "--allowlist-function", "ECDSA_SIG_new",
+ "--allowlist-function", "ECDSA_SIG_to_bytes",
+ "--allowlist-function", "ECDSA_verify",
+ "--allowlist-function", "EC_KEY_new_by_curve_name",
+ "--allowlist-function", "EC_KEY_free",
+ "--allowlist-function", "o2i_ECPublicKey",
"--allowlist-function", "ED25519_verify",
+ "--allowlist-function", "OPENSSL_free",
+ "--allowlist-function", "SHA256",
"--allowlist-var", "ED25519_PUBLIC_KEY_LEN",
"--allowlist-var", "ED25519_SIGNATURE_LEN",
+ "--allowlist-var", "NID_X9_62_prime256v1",
],
cflags: ["-I/usr/include/android"],
shared_libs: ["libcrypto"],
@@ -73,4 +85,4 @@
"libchrono",
"libssl_bindgen",
],
-}
\ No newline at end of file
+}
diff --git a/remote_provisioning/cert_validator/libssl_wrapper.h b/remote_provisioning/cert_validator/libssl_wrapper.h
index 9516216..9681e81 100644
--- a/remote_provisioning/cert_validator/libssl_wrapper.h
+++ b/remote_provisioning/cert_validator/libssl_wrapper.h
@@ -1 +1,7 @@
-#include <openssl/curve25519.h>
\ No newline at end of file
+#include <openssl/bn.h>
+#include <openssl/curve25519.h>
+#include <openssl/ec_key.h>
+#include <openssl/ecdsa.h>
+#include <openssl/mem.h>
+#include <openssl/nid.h>
+#include <openssl/sha.h>
diff --git a/remote_provisioning/cert_validator/src/bcc.rs b/remote_provisioning/cert_validator/src/bcc.rs
index 5c2245f..f157cf7 100644
--- a/remote_provisioning/cert_validator/src/bcc.rs
+++ b/remote_provisioning/cert_validator/src/bcc.rs
@@ -5,7 +5,7 @@
use crate::valueas::ValueAs;
use self::entry::SubjectPublicKey;
-use anyhow::{anyhow, ensure, Context, Result};
+use anyhow::{anyhow, bail, ensure, Context, Result};
use coset::AsCborValue;
use coset::{
cbor::value::Value::{self, Array},
@@ -21,7 +21,6 @@
/// signs the first certificate), followed by a chain of BccEntry certificates. Apart from the
/// first, the issuer of each cert if the subject of the previous one.
pub struct Chain {
-
public_key: CoseKey,
entries: Vec<CoseSign1>,
}
@@ -44,7 +43,8 @@
let mut it = self.entries.iter();
let entry = it.next().unwrap();
- let mut payload = entry::Payload::check_sign1_signature(&public_key, entry)?;
+ let mut payload = entry::Payload::check_sign1_signature(&public_key, entry)
+ .context("Failed initial signature check.")?;
let mut payloads = Vec::with_capacity(self.entries.len());
for entry in it {
@@ -97,6 +97,22 @@
anyhow!("CoseError: {:?}", ce)
}
+/// Get the value corresponding to the provided label within the supplied CoseKey
+/// or error if it's not present.
+pub fn get_label_value(key: &coset::CoseKey, label: i64) -> Result<&Value> {
+ Ok(&key
+ .params
+ .iter()
+ .find(|(k, _)| k == &coset::Label::Int(label))
+ .ok_or_else(|| anyhow!("Label {:?} not found", label))?
+ .1)
+}
+
+/// Get the byte string for the corresponding label within the key if the label exists
+/// and the value is actually a byte array.
+pub fn get_label_value_as_bytes(key: &coset::CoseKey, label: i64) -> Result<&Vec<u8>> {
+ get_label_value(key, label)?.as_bytes().ok_or_else(|| anyhow!("Value not a bstr."))
+}
/// This module wraps the certificate validation functions intended for BccEntry.
pub mod entry {
use std::fmt::{Display, Formatter, Write};
@@ -278,21 +294,24 @@
/// Perform validation on the items in the public key.
pub fn check(&self) -> Result<()> {
let pkey = &self.0;
- ensure!(pkey.kty == coset::KeyType::Assigned(iana::KeyType::OKP));
- // TODO: Follow up cl - add the case for ECDSA.
- ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)));
if !pkey.key_ops.is_empty() {
ensure!(pkey
.key_ops
.contains(&coset::KeyOperation::Assigned(iana::KeyOperation::Verify)));
}
- let crv = &pkey
- .params
- .iter()
- .find(|(k, _)| k == &coset::Label::Int(iana::OkpKeyParameter::Crv as i64))
- .ok_or_else(|| anyhow!("Curve not found"))?
- .1;
- ensure!(crv == &Value::from(iana::EllipticCurve::Ed25519 as i64));
+ match pkey.kty {
+ coset::KeyType::Assigned(iana::KeyType::OKP) => {
+ ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)));
+ let crv = get_label_value(pkey, iana::OkpKeyParameter::Crv as i64)?;
+ ensure!(crv == &Value::from(iana::EllipticCurve::Ed25519 as i64));
+ }
+ coset::KeyType::Assigned(iana::KeyType::EC2) => {
+ ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)));
+ let crv = get_label_value(pkey, iana::Ec2KeyParameter::Crv as i64)?;
+ ensure!(crv == &Value::from(iana::EllipticCurve::P_256 as i64));
+ }
+ _ => bail!("Unexpected KeyType value: {:?}", pkey.kty),
+ }
Ok(())
}
}
diff --git a/remote_provisioning/cert_validator/src/lib.rs b/remote_provisioning/cert_validator/src/lib.rs
index 730d976..35cf7f7 100644
--- a/remote_provisioning/cert_validator/src/lib.rs
+++ b/remote_provisioning/cert_validator/src/lib.rs
@@ -42,17 +42,17 @@
&bcc::entry::read("testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert")
.unwrap(),
);
- assert!(payload.is_ok());
+ assert!(payload.is_ok(), "Payload not okay: {:?}", payload);
let payload = payload.unwrap().check_sign1(
&bcc::entry::read("testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_1.cert")
.unwrap(),
);
- assert!(payload.is_ok());
+ assert!(payload.is_ok(), "Payload not okay: {:?}", payload);
let payload = payload.unwrap().check_sign1(
&bcc::entry::read("testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_2.cert")
.unwrap(),
);
- assert!(payload.is_ok());
+ assert!(payload.is_ok(), "Payload not okay: {:?}", payload);
}
#[test]
@@ -91,6 +91,20 @@
}
#[test]
+ fn test_check_chain_valid_p256() -> Result<()> {
+ let chain = bcc::Chain::read("testdata/bcc/valid_p256.chain").unwrap();
+ let payloads = chain.check()?;
+ assert_eq!(payloads.len(), 3);
+ Ok(())
+ }
+
+ #[test]
+ fn test_check_chain_bad_p256() {
+ let chain = bcc::Chain::read("testdata/bcc/bad_p256.chain").unwrap();
+ assert!(chain.check().is_err());
+ }
+
+ #[test]
fn test_check_chain_bad_pub_key() {
let chain = bcc::Chain::read("testdata/bcc/bad_pub_key.chain").unwrap();
assert!(chain.check().is_err());
diff --git a/remote_provisioning/cert_validator/src/publickey.rs b/remote_provisioning/cert_validator/src/publickey.rs
index 90f15cc..280e1dd 100644
--- a/remote_provisioning/cert_validator/src/publickey.rs
+++ b/remote_provisioning/cert_validator/src/publickey.rs
@@ -2,53 +2,220 @@
//! used in the BccPayload. The key itself is stored as a simple byte array in
//! a vector. For now, only PubKeyEd25519 types of cbor public keys are supported.
-use anyhow::{anyhow, ensure, Result};
+use crate::bcc::get_label_value_as_bytes;
+use anyhow::{bail, ensure, Context, Result};
use coset::{iana, Algorithm, CoseKey};
+use std::ptr;
-/// Public key length.
-pub const PUBLIC_KEY_LEN: usize = ssl_bindgen::ED25519_PUBLIC_KEY_LEN as usize;
-/// Signature length.
-pub const SIGNATURE_LEN: usize = ssl_bindgen::ED25519_SIGNATURE_LEN as usize;
+/// Length of an Ed25519 public key.
+pub const ED25519_PUBLIC_KEY_LEN: usize = ssl_bindgen::ED25519_PUBLIC_KEY_LEN as usize;
+/// Length of an Ed25519 signatures.
+pub const ED25519_SIG_LEN: usize = ssl_bindgen::ED25519_SIGNATURE_LEN as usize;
+/// Length of a P256 coordinate.
+pub const P256_COORD_LEN: usize = 32;
+/// Length of a P256 signature.
+pub const P256_SIG_LEN: usize = 64;
+enum PubKey {
+ Ed25519 { pub_key: [u8; ED25519_PUBLIC_KEY_LEN] },
+ P256 { x_coord: [u8; P256_COORD_LEN], y_coord: [u8; P256_COORD_LEN] },
+}
/// Struct wrapping the public key byte array, and the relevant validation methods.
-pub struct PublicKey([u8; PUBLIC_KEY_LEN]);
+pub struct PublicKey {
+ key: PubKey,
+}
impl PublicKey {
/// Extract the PublicKey from Subject Public Key.
/// (CertificateRequest.BccEntry.payload[SubjectPublicKey].X)
pub fn from_cose_key(pkey: &CoseKey) -> Result<Self> {
- let x = pkey
- .params
- .iter()
- .find(|(k, _)| k == &coset::Label::Int(iana::OkpKeyParameter::X as i64))
- .ok_or_else(|| anyhow!("X not found"))?
- .1
- .as_bytes()
- .ok_or_else(|| anyhow!("X not bytes"))?;
-
- PublicKey::new(x)
+ let x = get_label_value_as_bytes(pkey, iana::OkpKeyParameter::X as i64)?;
+ match pkey.alg {
+ Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)) => {
+ PublicKey::new(PubKey::Ed25519 {
+ pub_key: x.as_slice().try_into().context(format!(
+ "Failed to convert x_coord to array. Len: {:?}",
+ x.len()
+ ))?,
+ })
+ }
+ Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)) => {
+ let y = get_label_value_as_bytes(pkey, iana::Ec2KeyParameter::Y as i64)?;
+ PublicKey::new(PubKey::P256 {
+ x_coord: x.as_slice().try_into().context(format!(
+ "Failed to convert x_coord to array. Len: {:?}",
+ x.len()
+ ))?,
+ y_coord: y.as_slice().try_into().context(format!(
+ "Failed to convert y_coord to array. Len: {:?}",
+ y.len()
+ ))?,
+ })
+ }
+ _ => bail!("Unsupported signature algorithm: {:?}", pkey.alg),
+ }
}
- fn new(public_key: &[u8]) -> Result<Self> {
- Ok(Self(public_key.try_into()?))
+ fn new(key: PubKey) -> Result<Self> {
+ Ok(Self { key })
+ }
+
+ fn sha256(message: &[u8]) -> Result<[u8; 32]> {
+ let mut digest: [u8; 32] = [0; 32];
+ // SAFETY: This function is safe due to message only being read, with the associated length
+ // on the slice passed in to ensure no buffer overreads. Additionally, the digest is sized
+ // accordingly to the output size of SHA256. No memory is allocated.
+ unsafe {
+ if ssl_bindgen::SHA256(message.as_ptr(), message.len(), digest.as_mut_ptr()).is_null() {
+ bail!("Failed to hash the message.");
+ }
+ }
+ Ok(digest)
+ }
+
+ fn raw_p256_sig_to_der(signature: &[u8]) -> Result<Vec<u8>> {
+ ensure!(
+ signature.len() == P256_SIG_LEN,
+ "Unexpected signature length: {:?}",
+ signature.len()
+ );
+ let mut der_sig: *mut u8 = ptr::null_mut();
+ let mut der_sig_len: usize = 0;
+ // SAFETY: The signature slice is verified to contain the expected length before it is
+ // indexed as read only memory for the boringssl code to generate a DER encoded signature.
+ // The final result from the boringssl operations is copied out to a standard vector so
+ // the specific boringSSL deallocators can be used on the memory buffers that were
+ // allocated, and a standard, safe Rust Vec can be returned.
+ unsafe {
+ let der_encoder = ssl_bindgen::ECDSA_SIG_new();
+ if der_encoder.is_null() {
+ bail!("Failed to allocate ECDSA_SIG");
+ }
+ let mut encoder_closure = || {
+ ssl_bindgen::BN_bin2bn(signature.as_ptr(), 32, (*der_encoder).r);
+ ssl_bindgen::BN_bin2bn(signature.as_ptr().offset(32), 32, (*der_encoder).s);
+ if (*der_encoder).r.is_null() || (*der_encoder).s.is_null() {
+ bail!("Failed to allocate BigNum.");
+ }
+ // ECDSA_SIG_to_bytes takes a uint8_t** and allocates a buffer
+ if ssl_bindgen::ECDSA_SIG_to_bytes(
+ &mut der_sig,
+ &mut der_sig_len as *mut usize,
+ der_encoder,
+ ) == 0
+ {
+ bail!("Failed to encode ECDSA_SIG into a DER byte array.");
+ }
+ // Copy the data out of der_sig so that the unsafe pointer and associated memory
+ // can be properly freed.
+ let mut safe_copy = Vec::with_capacity(der_sig_len);
+ ptr::copy(der_sig, safe_copy.as_mut_ptr(), der_sig_len);
+ safe_copy.set_len(der_sig_len);
+ Ok(safe_copy)
+ };
+ let safe_copy = encoder_closure();
+ ssl_bindgen::ECDSA_SIG_free(der_encoder);
+ ssl_bindgen::OPENSSL_free(der_sig as *mut std::ffi::c_void);
+ safe_copy
+ }
+ }
+
+ fn verify_p256(signature: &[u8], message: &[u8], ec_point: &[u8]) -> Result<i32> {
+ // len(0x04 || r || s) should be 65 for a p256 public key.
+ ensure!(ec_point.len() == 65);
+ let mut key_bytes: *const u8 = ec_point.as_ptr();
+ let digest = PublicKey::sha256(message)?;
+ let der_sig = PublicKey::raw_p256_sig_to_der(signature)?;
+ // SAFETY: The following unsafe block allocates and creates an EC_KEY, using that struct
+ // in conjunction with read only access to length checked rust slices to verify the
+ // signature. The boringSSL allocated memory is then freed, regardless of failures during
+ // the verification process.
+ unsafe {
+ let mut key = ssl_bindgen::EC_KEY_new_by_curve_name(
+ ssl_bindgen::NID_X9_62_prime256v1.try_into()?,
+ );
+ // Use a closure just to simplify freeing allocated memory and error
+ // handling in the event an error occurs.
+ let mut verifier_closure = || {
+ if key.is_null() {
+ bail!("Failed to allocate a new EC_KEY.");
+ }
+ if ssl_bindgen::o2i_ECPublicKey(
+ &mut key,
+ &mut key_bytes,
+ ec_point.len().try_into()?,
+ )
+ .is_null()
+ {
+ bail!("Failed to convert key byte array into an EC_KEY structure.");
+ }
+ Ok(ssl_bindgen::ECDSA_verify(
+ 0, /* type */
+ digest.as_ptr(),
+ digest.len(),
+ der_sig.as_slice().as_ptr(),
+ der_sig.len(),
+ key,
+ ))
+ };
+ let result = verifier_closure();
+ ssl_bindgen::EC_KEY_free(key);
+ result
+ }
}
/// Verify that the signature obtained from signing the given message
/// with the PublicKey matches the signature provided.
pub fn verify(&self, signature: &[u8], message: &[u8], alg: &Option<Algorithm>) -> Result<()> {
- ensure!(signature.len() == SIGNATURE_LEN);
- // TODO: add match(alg) and handle the case for ECDSA.
- ensure!(*alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)));
- ensure!(
- unsafe {
- ssl_bindgen::ED25519_verify(
- message.as_ptr(),
- message.len().try_into()?,
- signature.as_ptr(),
- self.0.as_ptr(),
- )
- } == 1
- );
+ match self.key {
+ PubKey::Ed25519 { pub_key } => {
+ ensure!(
+ *alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)),
+ "Unexpected algorithm. Ed25519 key, but alg is: {:?}",
+ *alg
+ );
+ ensure!(
+ signature.len() == ED25519_SIG_LEN,
+ "Unexpected signature length: {:?}",
+ signature.len()
+ );
+ ensure!(
+ pub_key.len() == ED25519_PUBLIC_KEY_LEN,
+ "Unexpected public key length {:?}:",
+ pub_key.len()
+ );
+ ensure!(
+ // SAFETY: The underlying API only reads from the provided pointers, which are
+ // themselves standard slices with their corresponding expended lengths checked
+ // before the function call.
+ unsafe {
+ ssl_bindgen::ED25519_verify(
+ message.as_ptr(),
+ message.len(),
+ signature.as_ptr(),
+ pub_key.as_ptr(),
+ )
+ } == 1,
+ "Signature verification failed."
+ );
+ }
+ PubKey::P256 { x_coord, y_coord } => {
+ ensure!(
+ *alg == Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)),
+ "Unexpected algorithm. P256 key, but alg is: {:?}",
+ *alg
+ );
+ let mut ec_point_uncompressed: Vec<u8> = vec![0x04];
+ ec_point_uncompressed.extend_from_slice(&x_coord);
+ ec_point_uncompressed.extend_from_slice(&y_coord);
+ ensure!(ec_point_uncompressed.len() == 65);
+ let ec_point_slice = ec_point_uncompressed.as_slice();
+ ensure!(
+ PublicKey::verify_p256(signature, message, ec_point_slice)? == 1,
+ "Signature verification failed."
+ );
+ }
+ }
Ok(())
}
}
diff --git a/remote_provisioning/cert_validator/testdata/bcc/bad_p256.chain b/remote_provisioning/cert_validator/testdata/bcc/bad_p256.chain
new file mode 100644
index 0000000..822438c
--- /dev/null
+++ b/remote_provisioning/cert_validator/testdata/bcc/bad_p256.chain
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/bcc/valid_p256.chain b/remote_provisioning/cert_validator/testdata/bcc/valid_p256.chain
new file mode 100644
index 0000000..9619521
--- /dev/null
+++ b/remote_provisioning/cert_validator/testdata/bcc/valid_p256.chain
Binary files differ