Paul Crowley | 7bb5edd | 2021-03-20 20:26:43 -0700 | [diff] [blame] | 1 | // 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 | |
| 17 | use anyhow::{Context, Result}; |
| 18 | use 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. |
| 26 | pub struct ECDHPrivateKey(ECKey); |
| 27 | |
| 28 | impl 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)] |
| 121 | mod 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 | } |