| // Copyright 2021, 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. |
| |
| //! Implement ECDH-based encryption. |
| |
| use crate::ks_err; |
| use anyhow::{Context, Result}; |
| use keystore2_crypto::{ |
| aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key, |
| ec_key_marshal_private_key, ec_key_parse_private_key, ec_point_oct_to_point, |
| ec_point_point_to_oct, ecdh_compute_key, generate_salt, hkdf_expand, hkdf_extract, ECKey, ZVec, |
| AES_256_KEY_LENGTH, |
| }; |
| |
| /// Private key for ECDH encryption. |
| pub struct ECDHPrivateKey(ECKey); |
| |
| impl ECDHPrivateKey { |
| /// Randomly generate a fresh keypair. |
| pub fn generate() -> Result<ECDHPrivateKey> { |
| ec_key_generate_key().map(ECDHPrivateKey).context(ks_err!("generation failed")) |
| } |
| |
| /// Deserialize bytes into an ECDH keypair |
| pub fn from_private_key(buf: &[u8]) -> Result<ECDHPrivateKey> { |
| ec_key_parse_private_key(buf).map(ECDHPrivateKey).context(ks_err!("parsing failed")) |
| } |
| |
| /// Serialize the ECDH key into bytes |
| pub fn private_key(&self) -> Result<ZVec> { |
| ec_key_marshal_private_key(&self.0).context(ks_err!("marshalling failed")) |
| } |
| |
| /// Generate the serialization of the corresponding public key |
| pub fn public_key(&self) -> Result<Vec<u8>> { |
| let point = ec_key_get0_public_key(&self.0); |
| ec_point_point_to_oct(point.get_point()).context(ks_err!("marshalling failed")) |
| } |
| |
| /// Use ECDH to agree an AES key with another party whose public key we have. |
| /// Sender and recipient public keys are passed separately because they are |
| /// switched in encryption vs decryption. |
| fn agree_key( |
| &self, |
| salt: &[u8], |
| other_public_key: &[u8], |
| sender_public_key: &[u8], |
| recipient_public_key: &[u8], |
| ) -> Result<ZVec> { |
| let hkdf = hkdf_extract(sender_public_key, salt) |
| .context(ks_err!("hkdf_extract on sender_public_key failed"))?; |
| let hkdf = hkdf_extract(recipient_public_key, &hkdf) |
| .context(ks_err!("hkdf_extract on recipient_public_key failed"))?; |
| let other_public_key = ec_point_oct_to_point(other_public_key) |
| .context(ks_err!("ec_point_oct_to_point failed"))?; |
| let secret = ecdh_compute_key(other_public_key.get_point(), &self.0) |
| .context(ks_err!("ecdh_compute_key failed"))?; |
| let prk = hkdf_extract(&secret, &hkdf).context(ks_err!("hkdf_extract on secret failed"))?; |
| |
| let aes_key = hkdf_expand(AES_256_KEY_LENGTH, &prk, b"AES-256-GCM key") |
| .context(ks_err!("hkdf_expand failed"))?; |
| Ok(aes_key) |
| } |
| |
| /// Encrypt a message to the party with the given public key |
| pub fn encrypt_message( |
| recipient_public_key: &[u8], |
| message: &[u8], |
| ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>)> { |
| let sender_key = Self::generate().context(ks_err!("generate failed"))?; |
| let sender_public_key = sender_key.public_key().context(ks_err!("public_key failed"))?; |
| let salt = generate_salt().context(ks_err!("generate_salt failed"))?; |
| let aes_key = sender_key |
| .agree_key(&salt, recipient_public_key, &sender_public_key, recipient_public_key) |
| .context(ks_err!("agree_key failed"))?; |
| let (ciphertext, iv, tag) = |
| aes_gcm_encrypt(message, &aes_key).context(ks_err!("aes_gcm_encrypt failed"))?; |
| Ok((sender_public_key, salt, iv, ciphertext, tag)) |
| } |
| |
| /// Decrypt a message sent to us |
| pub fn decrypt_message( |
| &self, |
| sender_public_key: &[u8], |
| salt: &[u8], |
| iv: &[u8], |
| ciphertext: &[u8], |
| tag: &[u8], |
| ) -> Result<ZVec> { |
| let recipient_public_key = self.public_key()?; |
| let aes_key = self |
| .agree_key(salt, sender_public_key, sender_public_key, &recipient_public_key) |
| .context(ks_err!("agree_key failed"))?; |
| aes_gcm_decrypt(ciphertext, iv, tag, &aes_key).context(ks_err!("aes_gcm_decrypt failed")) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn test_crypto_roundtrip() -> Result<()> { |
| let message = b"Hello world"; |
| let recipient = ECDHPrivateKey::generate()?; |
| let (sender_public_key, salt, iv, ciphertext, tag) = |
| ECDHPrivateKey::encrypt_message(&recipient.public_key()?, message)?; |
| let recipient = ECDHPrivateKey::from_private_key(&recipient.private_key()?)?; |
| let decrypted = |
| recipient.decrypt_message(&sender_public_key, &salt, &iv, &ciphertext, &tag)?; |
| let dc: &[u8] = &decrypted; |
| assert_eq!(message, dc); |
| Ok(()) |
| } |
| } |