ECDH encryption module
Add a module for encrypting using ECDH, HKDF, and AES-GCM.
Also, add serialization of EC private keys, and remove derivation
from secrets; it turns out this is a better fit for the way
superencryption currently works.
Add a more thorough ECDH test in the crypto module, which simulates an
ephemeral key being used to send a message to a long-term key. The
high-level module has a similar test.
Bug: 163866361
Test: keystore2_crypto_test_rust, keystore2_test
Change-Id: I4c2bb1d8938de078ea37b930619918acc3c28fbe
diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp
index e386735..21c9b74 100644
--- a/keystore2/src/crypto/Android.bp
+++ b/keystore2/src/crypto/Android.bp
@@ -68,7 +68,8 @@
"--whitelist-function", "HKDFExpand",
"--whitelist-function", "ECDHComputeKey",
"--whitelist-function", "ECKEYGenerateKey",
- "--whitelist-function", "ECKEYDeriveFromSecret",
+ "--whitelist-function", "ECKEYMarshalPrivateKey",
+ "--whitelist-function", "ECKEYParsePrivateKey",
"--whitelist-function", "EC_KEY_get0_public_key",
"--whitelist-function", "ECPOINTPoint2Oct",
"--whitelist-function", "ECPOINTOct2Point",
diff --git a/keystore2/src/crypto/crypto.cpp b/keystore2/src/crypto/crypto.cpp
index 2e613fd..e4a1ac3 100644
--- a/keystore2/src/crypto/crypto.cpp
+++ b/keystore2/src/crypto/crypto.cpp
@@ -236,10 +236,28 @@
return key;
}
-EC_KEY* ECKEYDeriveFromSecret(const uint8_t* secret, size_t secret_len) {
+size_t ECKEYMarshalPrivateKey(const EC_KEY* priv_key, uint8_t* buf, size_t len) {
+ CBB cbb;
+ size_t out_len;
+ if (!CBB_init_fixed(&cbb, buf, len) ||
+ !EC_KEY_marshal_private_key(&cbb, priv_key, EC_PKEY_NO_PARAMETERS | EC_PKEY_NO_PUBKEY) ||
+ !CBB_finish(&cbb, nullptr, &out_len)) {
+ return 0;
+ } else {
+ return out_len;
+ }
+}
+
+EC_KEY* ECKEYParsePrivateKey(const uint8_t* buf, size_t len) {
+ CBS cbs;
+ CBS_init(&cbs, buf, len);
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
- auto result = EC_KEY_derive_from_secret(group, secret, secret_len);
+ auto result = EC_KEY_parse_private_key(&cbs, group);
EC_GROUP_free(group);
+ if (result != nullptr && CBS_len(&cbs) != 0) {
+ EC_KEY_free(result);
+ return nullptr;
+ }
return result;
}
diff --git a/keystore2/src/crypto/crypto.hpp b/keystore2/src/crypto/crypto.hpp
index 6686c8c..f841eb3 100644
--- a/keystore2/src/crypto/crypto.hpp
+++ b/keystore2/src/crypto/crypto.hpp
@@ -55,7 +55,9 @@
EC_KEY* ECKEYGenerateKey();
- EC_KEY* ECKEYDeriveFromSecret(const uint8_t *secret, size_t secret_len);
+ size_t ECKEYMarshalPrivateKey(const EC_KEY *priv_key, uint8_t *buf, size_t len);
+
+ EC_KEY* ECKEYParsePrivateKey(const uint8_t *buf, size_t len);
size_t ECPOINTPoint2Oct(const EC_POINT *point, uint8_t *buf, size_t len);
diff --git a/keystore2/src/crypto/error.rs b/keystore2/src/crypto/error.rs
index 1eec321..a369012 100644
--- a/keystore2/src/crypto/error.rs
+++ b/keystore2/src/crypto/error.rs
@@ -74,9 +74,13 @@
#[error("Failed to generate key.")]
ECKEYGenerateKeyFailed,
- /// This is returned if the C implementation of ECKEYDeriveFromSecret returned null.
- #[error("Failed to derive key.")]
- ECKEYDeriveFailed,
+ /// This is returned if the C implementation of ECKEYMarshalPrivateKey returned 0.
+ #[error("Failed to marshal private key.")]
+ ECKEYMarshalPrivateKeyFailed,
+
+ /// This is returned if the C implementation of ECKEYParsePrivateKey returned null.
+ #[error("Failed to parse private key.")]
+ ECKEYParsePrivateKeyFailed,
/// This is returned if the C implementation of ECPOINTPoint2Oct returned 0.
#[error("Failed to convert point to oct.")]
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index 98e6eef..3523a9d 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -20,9 +20,9 @@
pub use error::Error;
use keystore2_crypto_bindgen::{
extractSubjectFromCertificate, generateKeyFromPassword, randomBytes, AES_gcm_decrypt,
- AES_gcm_encrypt, ECDHComputeKey, ECKEYDeriveFromSecret, ECKEYGenerateKey, ECPOINTOct2Point,
- ECPOINTPoint2Oct, EC_KEY_free, EC_KEY_get0_public_key, EC_POINT_free, HKDFExpand, HKDFExtract,
- EC_KEY, EC_MAX_BYTES, EC_POINT, EVP_MAX_MD_SIZE,
+ AES_gcm_encrypt, ECDHComputeKey, ECKEYGenerateKey, ECKEYMarshalPrivateKey,
+ ECKEYParsePrivateKey, ECPOINTOct2Point, ECPOINTPoint2Oct, EC_KEY_free, EC_KEY_get0_public_key,
+ EC_POINT_free, HKDFExpand, HKDFExtract, EC_KEY, EC_MAX_BYTES, EC_POINT, EVP_MAX_MD_SIZE,
};
use std::convert::TryFrom;
use std::convert::TryInto;
@@ -338,14 +338,32 @@
Ok(ECKey(key))
}
-/// Calls the boringssl EC_KEY_derive_from_secret function.
-pub fn ec_key_derive_from_secret(secret: &[u8]) -> Result<ECKey, Error> {
- // Safety: secret is a valid buffer.
- let result = unsafe { ECKEYDeriveFromSecret(secret.as_ptr(), secret.len()) };
- if result.is_null() {
- return Err(Error::ECKEYDeriveFailed);
+/// Calls the boringssl EC_KEY_marshal_private_key function.
+pub fn ec_key_marshal_private_key(key: &ECKey) -> Result<ZVec, Error> {
+ let len = 39; // Empirically observed length of private key
+ let mut buf = ZVec::new(len)?;
+ // Safety: the key is valid.
+ // This will not write past the specified length of the buffer; if the
+ // len above is too short, it returns 0.
+ let written_len =
+ unsafe { ECKEYMarshalPrivateKey(key.0, buf.as_mut_ptr(), buf.len()) } as usize;
+ if written_len == len {
+ Ok(buf)
+ } else {
+ Err(Error::ECKEYMarshalPrivateKeyFailed)
}
- Ok(ECKey(result))
+}
+
+/// Calls the boringssl EC_KEY_parse_private_key function.
+pub fn ec_key_parse_private_key(buf: &[u8]) -> Result<ECKey, Error> {
+ // Safety: this will not read past the specified length of the buffer.
+ // It fails if less than the whole buffer is consumed.
+ let key = unsafe { ECKEYParsePrivateKey(buf.as_ptr(), buf.len()) };
+ if key.is_null() {
+ Err(Error::ECKEYParsePrivateKeyFailed)
+ } else {
+ Ok(ECKey(key))
+ }
}
/// Calls the boringssl EC_KEY_get0_public_key function.
@@ -519,26 +537,26 @@
}
#[test]
- fn test_ec() {
- let key = ec_key_generate_key();
- assert!(key.is_ok());
- assert!(!key.unwrap().0.is_null());
+ fn test_ec() -> Result<(), Error> {
+ let priv0 = ec_key_generate_key()?;
+ assert!(!priv0.0.is_null());
+ let pub0 = ec_key_get0_public_key(&priv0);
- let key = ec_key_derive_from_secret(&[42; 16]);
- assert!(key.is_ok());
- let key = key.unwrap();
- assert!(!key.0.is_null());
+ let priv1 = ec_key_generate_key()?;
+ let pub1 = ec_key_get0_public_key(&priv1);
- let point = ec_key_get0_public_key(&key);
+ let priv0s = ec_key_marshal_private_key(&priv0)?;
+ let pub0s = ec_point_point_to_oct(pub0.get_point())?;
+ let pub1s = ec_point_point_to_oct(pub1.get_point())?;
- let result = ecdh_compute_key(point.get_point(), &key);
- assert!(result.is_ok());
+ let priv0 = ec_key_parse_private_key(&priv0s)?;
+ let pub0 = ec_point_oct_to_point(&pub0s)?;
+ let pub1 = ec_point_oct_to_point(&pub1s)?;
- let oct = ec_point_point_to_oct(point.get_point());
- assert!(oct.is_ok());
- let oct = oct.unwrap();
+ let left_key = ecdh_compute_key(pub0.get_point(), &priv1)?;
+ let right_key = ecdh_compute_key(pub1.get_point(), &priv0)?;
- let point2 = ec_point_oct_to_point(oct.as_slice());
- assert!(point2.is_ok());
+ assert_eq!(left_key, right_key);
+ Ok(())
}
}