Add EcDsa::generate_key for convenience am: 0987e270cf am: 170113645b am: ba53954d48

Original change: https://android-review.googlesource.com/c/platform/system/authgraph/+/2901989

Change-Id: Ic8cd02599d246ed412b0ec9c851daef2899f8fe2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/boringssl/src/ec.rs b/boringssl/src/ec.rs
index 53cd168..88abb56 100644
--- a/boringssl/src/ec.rs
+++ b/boringssl/src/ec.rs
@@ -16,12 +16,13 @@
 
 //! BoringSSL-based implementation for the AuthGraph `Ec` trait.
 
+use alloc::vec::Vec;
 use authgraph_core::{
     ag_err, ag_verr,
     error::Error,
     key::{
         check_cose_key_params, EcExchangeKey, EcExchangeKeyPriv, EcExchangeKeyPub, EcSignKey,
-        EcVerifyKey, EcdhSecret,
+        EcVerifyKey, EcdhSecret, CURVE25519_PRIV_KEY_LEN,
     },
     traits, try_to_vec,
 };
@@ -68,6 +69,18 @@
 pub struct BoringEcDsa;
 
 impl traits::EcDsa for BoringEcDsa {
+    fn generate_key(&self, curve: iana::EllipticCurve) -> Result<(EcSignKey, EcVerifyKey), Error> {
+        match curve {
+            iana::EllipticCurve::P_256 => create_p256_key_pair(iana::Algorithm::ES256)
+                .map(|(priv_key, pub_key)| (EcSignKey::P256(priv_key), EcVerifyKey::P256(pub_key))),
+            iana::EllipticCurve::P_384 => create_p384_key_pair(iana::Algorithm::ES384)
+                .map(|(priv_key, pub_key)| (EcSignKey::P384(priv_key), EcVerifyKey::P384(pub_key))),
+            iana::EllipticCurve::Ed25519 => create_ed25519_key_pair().map(|(priv_key, pub_key)| {
+                (EcSignKey::Ed25519(priv_key), EcVerifyKey::Ed25519(pub_key))
+            }),
+            _ => Err(ag_err!(Unimplemented, "unexpected curve {curve:?} for ECDSA keygen")),
+        }
+    }
     fn sign(&self, sign_key: &EcSignKey, data: &[u8]) -> Result<Vec<u8>, Error> {
         let pkey;
         let mut signer = match sign_key {
@@ -135,9 +148,27 @@
 }
 
 /// Create an EC key pair on P-256 curve and return a tuple consisting of the private key bytes
-/// and the public key as a `CoseKey`.
+/// and the public key as a `CoseKey`.  The `algorithm` parameter indicates the intended use
+/// for the keypair (ECDH or ECDSA).
 pub fn create_p256_key_pair(algorithm: iana::Algorithm) -> Result<(Vec<u8>, CoseKey), Error> {
     let group = ossl!(openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1))?;
+    create_nist_key_pair(algorithm, iana::EllipticCurve::P_256, group, 32)
+}
+
+/// Create an EC key pair on P-384 curve and return a tuple consisting of the private key bytes
+/// and the public key as a `CoseKey`.  The `algorithm` parameter indicates the intended use
+/// for the keypair (ECDH or ECDSA).
+pub fn create_p384_key_pair(algorithm: iana::Algorithm) -> Result<(Vec<u8>, CoseKey), Error> {
+    let group = ossl!(openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::SECP384R1))?;
+    create_nist_key_pair(algorithm, iana::EllipticCurve::P_384, group, 48)
+}
+
+fn create_nist_key_pair(
+    algorithm: iana::Algorithm,
+    curve: iana::EllipticCurve,
+    group: openssl::ec::EcGroup,
+    coord_len: usize,
+) -> Result<(Vec<u8>, CoseKey), Error> {
     let ec_priv_key = ossl!(openssl::ec::EcKey::<openssl::pkey::Private>::generate(&group))?;
     // Convert the private key to DER-encoded `ECPrivateKey` as per RFC5915 s3, which is
     // our choice of private key encoding.
@@ -151,28 +182,55 @@
         openssl::ec::PointConversionForm::UNCOMPRESSED,
         bn_ctx.deref_mut()
     ))?;
-    let (x, y) = crate::ec::coords_from_p256_pub_key(&pub_key_sec1)?;
-    let pub_key = coset::CoseKeyBuilder::new_ec2_pub_key(iana::EllipticCurve::P_256, x, y)
-        .algorithm(algorithm)
+    let (x, y) = crate::ec::coords_from_nist_pub_key(&pub_key_sec1, coord_len)?;
+    let pub_key = coset::CoseKeyBuilder::new_ec2_pub_key(curve, x, y).algorithm(algorithm).build();
+    Ok((priv_key, pub_key))
+}
+
+fn create_ed25519_key_pair() -> Result<([u8; CURVE25519_PRIV_KEY_LEN], CoseKey), Error> {
+    let pkey = ossl!(openssl::pkey::PKey::generate_ed25519())?;
+    let priv_key = ossl!(pkey.raw_private_key())?;
+    let priv_key: [u8; CURVE25519_PRIV_KEY_LEN] = priv_key
+        .try_into()
+        .map_err(|e| ag_err!(InternalError, "generated Ed25519 key of unexpected size: {:?}", e))?;
+    let raw_pub_key = ossl!(pkey.raw_public_key())?;
+    let pub_key = coset::CoseKeyBuilder::new_okp_key()
+        .param(iana::OkpKeyParameter::Crv as i64, Value::from(iana::EllipticCurve::Ed25519 as u64))
+        .param(iana::OkpKeyParameter::X as i64, Value::from(raw_pub_key))
+        .algorithm(coset::iana::Algorithm::EdDSA)
         .build();
     Ok((priv_key, pub_key))
 }
 
-/// Helper function to return the (x,y) coordinates, given the public key as a SEC-1 encoded
+/// Helper function to return the (x,y) coordinates of a P-256 public key, as a SEC-1 encoded
 /// uncompressed point. 0x04: uncompressed, followed by x || y coordinates.
 pub fn coords_from_p256_pub_key(pub_key: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
-    let coord_len = 32; // For P-256
+    coords_from_nist_pub_key(pub_key, 32) // For P-256
+}
+
+/// Helper function to return the (x,y) coordinates of a P-384 public key, as a SEC-1 encoded
+/// uncompressed point. 0x04: uncompressed, followed by x || y coordinates.
+pub fn coords_from_p384_pub_key(pub_key: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
+    coords_from_nist_pub_key(pub_key, 48) // For P-384
+}
+
+/// Helper function to return the (x,y) coordinates, given the public key as a SEC-1 encoded
+/// uncompressed point. 0x04: uncompressed, followed by x || y coordinates.
+pub fn coords_from_nist_pub_key(
+    pub_key: &[u8],
+    coord_len: usize,
+) -> Result<(Vec<u8>, Vec<u8>), Error> {
     if pub_key.len() != (1 + 2 * coord_len) {
         return Err(ag_err!(
             InternalError,
-            "unexpected SEC1 pubkey len of {} for P-256",
+            "unexpected SEC1 pubkey len of {} for coord_len {coord_len}",
             pub_key.len(),
         ));
     }
     if pub_key[0] != SEC1_UNCOMPRESSED_PREFIX {
         return Err(ag_err!(
             InternalError,
-            "unexpected SEC1 pubkey initial byte {} for P-256",
+            "unexpected SEC1 pubkey initial byte {} for coord_len {coord_len}",
             pub_key[0],
         ));
     }
@@ -289,3 +347,48 @@
         ossl!(openssl::pkey::PKey::public_key_from_raw_bytes(x, openssl::pkey::Id::ED25519))?;
     Ok(pkey)
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use authgraph_core::traits::EcDsa;
+
+    #[test]
+    fn test_p256_keygen() {
+        let ecdsa = BoringEcDsa;
+        let (priv_key, pub_key) = ecdsa.generate_key(iana::EllipticCurve::P_256).unwrap();
+        assert!(matches!(priv_key, EcSignKey::P256(_)));
+        let cose_key = if let EcVerifyKey::P256(cose_key) = pub_key {
+            cose_key
+        } else {
+            panic!("Unexpected pub key variant from generate_key()");
+        };
+        assert!(p256_ecdsa_pkey_from_cose(&cose_key).is_ok());
+    }
+
+    #[test]
+    fn test_p384_keygen() {
+        let ecdsa = BoringEcDsa;
+        let (priv_key, pub_key) = ecdsa.generate_key(iana::EllipticCurve::P_384).unwrap();
+        assert!(matches!(priv_key, EcSignKey::P384(_)));
+        let cose_key = if let EcVerifyKey::P384(cose_key) = pub_key {
+            cose_key
+        } else {
+            panic!("Unexpected pub key variant from generate_key()");
+        };
+        assert!(p384_ecdsa_pkey_from_cose(&cose_key).is_ok());
+    }
+
+    #[test]
+    fn test_ed25519_keygen() {
+        let ecdsa = BoringEcDsa;
+        let (priv_key, pub_key) = ecdsa.generate_key(iana::EllipticCurve::Ed25519).unwrap();
+        assert!(matches!(priv_key, EcSignKey::Ed25519(_)));
+        let cose_key = if let EcVerifyKey::Ed25519(cose_key) = pub_key {
+            cose_key
+        } else {
+            panic!("Unexpected pub key variant from generate_key()");
+        };
+        assert!(ed25519_ecdsa_pkey_from_cose(&cose_key).is_ok());
+    }
+}
diff --git a/core/src/traits.rs b/core/src/traits.rs
index 3666a77..ffc10f1 100644
--- a/core/src/traits.rs
+++ b/core/src/traits.rs
@@ -24,7 +24,7 @@
 use alloc::boxed::Box;
 use alloc::vec::Vec;
 use authgraph_wire::SESSION_ID_LEN;
-use coset::iana::Algorithm;
+use coset::iana::{Algorithm, EllipticCurve};
 
 /// The first version of the AuthGraph key exchange protocol
 pub const AG_KEY_EXCHANGE_PROTOCOL_VERSION_1: i32 = 1;
@@ -91,6 +91,13 @@
 /// Trait methods for EC sign/verify with EdDSA (with Ed25519 curve) and ECDSA (with P256 curve and
 /// SHA-256 digest, and P384 curve and SHA-384 digest).
 pub trait EcDsa: Send {
+    /// Generate an ECDSA keypair identified by the given `curve`.
+    ///
+    /// This method is included in the trait for convenience of AuthGraph users; it is not required
+    /// by AuthGraph itself.
+    fn generate_key(&self, _curve: EllipticCurve) -> Result<(EcSignKey, EcVerifyKey), Error> {
+        unimplemented!();
+    }
     /// Compute signature over the given data, using the given EC private key. You can mark this as
     /// unimplemented and instead implement the `sign_data` method in the `Device` trait if the
     /// private signing key is not readily available (i.e. `get_identity` in the `Device` trait