blob: 4fb37473bd7391898b9c1fc5fb348c96329c6c2d [file] [log] [blame]
Paul Crowley7bb5edd2021-03-20 20:26:43 -07001// Copyright 2021, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Implement ECDH-based encryption.
16
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000017use crate::ks_err;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070018use anyhow::{Context, Result};
19use keystore2_crypto::{
20 aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key,
21 ec_key_marshal_private_key, ec_key_parse_private_key, ec_point_oct_to_point,
22 ec_point_point_to_oct, ecdh_compute_key, generate_salt, hkdf_expand, hkdf_extract, ECKey, ZVec,
23 AES_256_KEY_LENGTH,
24};
25
26/// Private key for ECDH encryption.
27pub struct ECDHPrivateKey(ECKey);
28
29impl ECDHPrivateKey {
30 /// Randomly generate a fresh keypair.
31 pub fn generate() -> Result<ECDHPrivateKey> {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000032 ec_key_generate_key().map(ECDHPrivateKey).context(ks_err!("generation failed"))
Paul Crowley7bb5edd2021-03-20 20:26:43 -070033 }
34
35 /// Deserialize bytes into an ECDH keypair
36 pub fn from_private_key(buf: &[u8]) -> Result<ECDHPrivateKey> {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000037 ec_key_parse_private_key(buf).map(ECDHPrivateKey).context(ks_err!("parsing failed"))
Paul Crowley7bb5edd2021-03-20 20:26:43 -070038 }
39
40 /// Serialize the ECDH key into bytes
41 pub fn private_key(&self) -> Result<ZVec> {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000042 ec_key_marshal_private_key(&self.0).context(ks_err!("marshalling failed"))
Paul Crowley7bb5edd2021-03-20 20:26:43 -070043 }
44
45 /// Generate the serialization of the corresponding public key
46 pub fn public_key(&self) -> Result<Vec<u8>> {
47 let point = ec_key_get0_public_key(&self.0);
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000048 ec_point_point_to_oct(point.get_point()).context(ks_err!("marshalling failed"))
Paul Crowley7bb5edd2021-03-20 20:26:43 -070049 }
50
51 /// Use ECDH to agree an AES key with another party whose public key we have.
52 /// Sender and recipient public keys are passed separately because they are
53 /// switched in encryption vs decryption.
54 fn agree_key(
55 &self,
56 salt: &[u8],
57 other_public_key: &[u8],
58 sender_public_key: &[u8],
59 recipient_public_key: &[u8],
60 ) -> Result<ZVec> {
61 let hkdf = hkdf_extract(sender_public_key, salt)
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000062 .context(ks_err!("hkdf_extract on sender_public_key failed"))?;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070063 let hkdf = hkdf_extract(recipient_public_key, &hkdf)
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000064 .context(ks_err!("hkdf_extract on recipient_public_key failed"))?;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070065 let other_public_key = ec_point_oct_to_point(other_public_key)
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000066 .context(ks_err!("ec_point_oct_to_point failed"))?;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070067 let secret = ecdh_compute_key(other_public_key.get_point(), &self.0)
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000068 .context(ks_err!("ecdh_compute_key failed"))?;
69 let prk = hkdf_extract(&secret, &hkdf).context(ks_err!("hkdf_extract on secret failed"))?;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070070
71 let aes_key = hkdf_expand(AES_256_KEY_LENGTH, &prk, b"AES-256-GCM key")
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000072 .context(ks_err!("hkdf_expand failed"))?;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070073 Ok(aes_key)
74 }
75
76 /// Encrypt a message to the party with the given public key
77 pub fn encrypt_message(
78 recipient_public_key: &[u8],
79 message: &[u8],
80 ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>)> {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000081 let sender_key = Self::generate().context(ks_err!("generate failed"))?;
82 let sender_public_key = sender_key.public_key().context(ks_err!("public_key failed"))?;
83 let salt = generate_salt().context(ks_err!("generate_salt failed"))?;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070084 let aes_key = sender_key
85 .agree_key(&salt, recipient_public_key, &sender_public_key, recipient_public_key)
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000086 .context(ks_err!("agree_key failed"))?;
87 let (ciphertext, iv, tag) =
88 aes_gcm_encrypt(message, &aes_key).context(ks_err!("aes_gcm_encrypt failed"))?;
Paul Crowley7bb5edd2021-03-20 20:26:43 -070089 Ok((sender_public_key, salt, iv, ciphertext, tag))
90 }
91
92 /// Decrypt a message sent to us
93 pub fn decrypt_message(
94 &self,
95 sender_public_key: &[u8],
96 salt: &[u8],
97 iv: &[u8],
98 ciphertext: &[u8],
99 tag: &[u8],
100 ) -> Result<ZVec> {
101 let recipient_public_key = self.public_key()?;
102 let aes_key = self
103 .agree_key(salt, sender_public_key, sender_public_key, &recipient_public_key)
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000104 .context(ks_err!("agree_key failed"))?;
105 aes_gcm_decrypt(ciphertext, iv, tag, &aes_key).context(ks_err!("aes_gcm_decrypt failed"))
Paul Crowley7bb5edd2021-03-20 20:26:43 -0700106 }
107}
108
109#[cfg(test)]
110mod test {
111 use super::*;
112
113 #[test]
114 fn test_crypto_roundtrip() -> Result<()> {
115 let message = b"Hello world";
116 let recipient = ECDHPrivateKey::generate()?;
117 let (sender_public_key, salt, iv, ciphertext, tag) =
118 ECDHPrivateKey::encrypt_message(&recipient.public_key()?, message)?;
119 let recipient = ECDHPrivateKey::from_private_key(&recipient.private_key()?)?;
120 let decrypted =
121 recipient.decrypt_message(&sender_public_key, &salt, &iv, &ciphertext, &tag)?;
122 let dc: &[u8] = &decrypted;
123 assert_eq!(message, dc);
124 Ok(())
125 }
126}