summaryrefslogtreecommitdiff
path: root/keystore2/src/ec_crypto.rs
blob: 4fb37473bd7391898b9c1fc5fb348c96329c6c2d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// 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(())
    }
}