blob: 0425d4a5082db6826c18260d3977c2c6df40f94f [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
17use anyhow::{Context, Result};
18use keystore2_crypto::{
19 aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key,
20 ec_key_marshal_private_key, ec_key_parse_private_key, ec_point_oct_to_point,
21 ec_point_point_to_oct, ecdh_compute_key, generate_salt, hkdf_expand, hkdf_extract, ECKey, ZVec,
22 AES_256_KEY_LENGTH,
23};
24
25/// Private key for ECDH encryption.
26pub struct ECDHPrivateKey(ECKey);
27
28impl ECDHPrivateKey {
29 /// Randomly generate a fresh keypair.
30 pub fn generate() -> Result<ECDHPrivateKey> {
31 ec_key_generate_key()
32 .map(ECDHPrivateKey)
33 .context("In ECDHPrivateKey::generate: generation failed")
34 }
35
36 /// Deserialize bytes into an ECDH keypair
37 pub fn from_private_key(buf: &[u8]) -> Result<ECDHPrivateKey> {
38 ec_key_parse_private_key(buf)
39 .map(ECDHPrivateKey)
40 .context("In ECDHPrivateKey::from_private_key: parsing failed")
41 }
42
43 /// Serialize the ECDH key into bytes
44 pub fn private_key(&self) -> Result<ZVec> {
45 ec_key_marshal_private_key(&self.0)
46 .context("In ECDHPrivateKey::private_key: marshalling failed")
47 }
48
49 /// Generate the serialization of the corresponding public key
50 pub fn public_key(&self) -> Result<Vec<u8>> {
51 let point = ec_key_get0_public_key(&self.0);
52 ec_point_point_to_oct(point.get_point())
53 .context("In ECDHPrivateKey::public_key: marshalling failed")
54 }
55
56 /// Use ECDH to agree an AES key with another party whose public key we have.
57 /// Sender and recipient public keys are passed separately because they are
58 /// switched in encryption vs decryption.
59 fn agree_key(
60 &self,
61 salt: &[u8],
62 other_public_key: &[u8],
63 sender_public_key: &[u8],
64 recipient_public_key: &[u8],
65 ) -> Result<ZVec> {
66 let hkdf = hkdf_extract(sender_public_key, salt)
67 .context("In ECDHPrivateKey::agree_key: hkdf_extract on sender_public_key failed")?;
68 let hkdf = hkdf_extract(recipient_public_key, &hkdf)
69 .context("In ECDHPrivateKey::agree_key: hkdf_extract on recipient_public_key failed")?;
70 let other_public_key = ec_point_oct_to_point(other_public_key)
71 .context("In ECDHPrivateKey::agree_key: ec_point_oct_to_point failed")?;
72 let secret = ecdh_compute_key(other_public_key.get_point(), &self.0)
73 .context("In ECDHPrivateKey::agree_key: ecdh_compute_key failed")?;
74 let prk = hkdf_extract(&secret, &hkdf)
75 .context("In ECDHPrivateKey::agree_key: hkdf_extract on secret failed")?;
76
77 let aes_key = hkdf_expand(AES_256_KEY_LENGTH, &prk, b"AES-256-GCM key")
78 .context("In ECDHPrivateKey::agree_key: hkdf_expand failed")?;
79 Ok(aes_key)
80 }
81
82 /// Encrypt a message to the party with the given public key
83 pub fn encrypt_message(
84 recipient_public_key: &[u8],
85 message: &[u8],
86 ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>)> {
87 let sender_key =
88 Self::generate().context("In ECDHPrivateKey::encrypt_message: generate failed")?;
89 let sender_public_key = sender_key
90 .public_key()
91 .context("In ECDHPrivateKey::encrypt_message: public_key failed")?;
92 let salt =
93 generate_salt().context("In ECDHPrivateKey::encrypt_message: generate_salt failed")?;
94 let aes_key = sender_key
95 .agree_key(&salt, recipient_public_key, &sender_public_key, recipient_public_key)
96 .context("In ECDHPrivateKey::encrypt_message: agree_key failed")?;
97 let (ciphertext, iv, tag) = aes_gcm_encrypt(message, &aes_key)
98 .context("In ECDHPrivateKey::encrypt_message: aes_gcm_encrypt failed")?;
99 Ok((sender_public_key, salt, iv, ciphertext, tag))
100 }
101
102 /// Decrypt a message sent to us
103 pub fn decrypt_message(
104 &self,
105 sender_public_key: &[u8],
106 salt: &[u8],
107 iv: &[u8],
108 ciphertext: &[u8],
109 tag: &[u8],
110 ) -> Result<ZVec> {
111 let recipient_public_key = self.public_key()?;
112 let aes_key = self
113 .agree_key(salt, sender_public_key, sender_public_key, &recipient_public_key)
114 .context("In ECDHPrivateKey::decrypt_message: agree_key failed")?;
115 aes_gcm_decrypt(ciphertext, iv, tag, &aes_key)
116 .context("In ECDHPrivateKey::decrypt_message: aes_gcm_decrypt failed")
117 }
118}
119
120#[cfg(test)]
121mod test {
122 use super::*;
123
124 #[test]
125 fn test_crypto_roundtrip() -> Result<()> {
126 let message = b"Hello world";
127 let recipient = ECDHPrivateKey::generate()?;
128 let (sender_public_key, salt, iv, ciphertext, tag) =
129 ECDHPrivateKey::encrypt_message(&recipient.public_key()?, message)?;
130 let recipient = ECDHPrivateKey::from_private_key(&recipient.private_key()?)?;
131 let decrypted =
132 recipient.decrypt_message(&sender_public_key, &salt, &iv, &ciphertext, &tag)?;
133 let dc: &[u8] = &decrypted;
134 assert_eq!(message, dc);
135 Ok(())
136 }
137}