blob: 4fb37473bd7391898b9c1fc5fb348c96329c6c2d [file] [log] [blame]
// 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(())
}
}