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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// Copyright (c) 2020 Apple Inc.
// SPDX-License-Identifier: MPL-2.0

//! Utilities for ECIES encryption / decryption

use aes_gcm::aead::generic_array::typenum::U16;
use aes_gcm::aead::generic_array::GenericArray;
use aes_gcm::{AeadInPlace, NewAead};
use ring::agreement;
type Aes128 = aes_gcm::AesGcm<aes_gcm::aes::Aes128, U16>;

/// Length of the EC public key (X9.62 format)
pub const PUBLICKEY_LENGTH: usize = 65;
/// Length of the AES-GCM tag
pub const TAG_LENGTH: usize = 16;
/// Length of the symmetric AES-GCM key
const KEY_LENGTH: usize = 16;

/// Possible errors from encryption / decryption.
#[derive(Debug, thiserror::Error)]
pub enum EncryptError {
    /// Base64 decoding error
    #[error("base64 decoding error")]
    DecodeBase64(#[from] base64::DecodeError),
    /// Error in ECDH
    #[error("error in ECDH")]
    KeyAgreement,
    /// Buffer for ciphertext was not large enough
    #[error("buffer for ciphertext was not large enough")]
    Encryption,
    /// Authentication tags did not match.
    #[error("authentication tags did not match")]
    Decryption,
    /// Input ciphertext was too small
    #[error("input ciphertext was too small")]
    DecryptionLength,
}

/// NIST P-256, public key in X9.62 uncompressed format
#[derive(Debug, Clone)]
pub struct PublicKey(Vec<u8>);

/// NIST P-256, private key
///
/// X9.62 uncompressed public key concatenated with the secret scalar.
#[derive(Debug, Clone)]
pub struct PrivateKey(Vec<u8>);

impl PublicKey {
    /// Load public key from a base64 encoded X9.62 uncompressed representation.
    pub fn from_base64(key: &str) -> Result<Self, EncryptError> {
        let keydata = base64::decode(key)?;
        Ok(PublicKey(keydata))
    }
}

/// Copy public key from a private key
impl std::convert::From<&PrivateKey> for PublicKey {
    fn from(pk: &PrivateKey) -> Self {
        PublicKey(pk.0[..PUBLICKEY_LENGTH].to_owned())
    }
}

impl PrivateKey {
    /// Load private key from a base64 encoded string.
    pub fn from_base64(key: &str) -> Result<Self, EncryptError> {
        let keydata = base64::decode(key)?;
        Ok(PrivateKey(keydata))
    }
}

/// Encrypt a bytestring using the public key
///
/// This uses ECIES with X9.63 key derivation function and AES-GCM for the
/// symmetic encryption and MAC.
pub fn encrypt_share(share: &[u8], key: &PublicKey) -> Result<Vec<u8>, EncryptError> {
    let rng = ring::rand::SystemRandom::new();
    let ephemeral_priv = agreement::EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng)
        .map_err(|_| EncryptError::KeyAgreement)?;
    let peer_public = agreement::UnparsedPublicKey::new(&agreement::ECDH_P256, &key.0);
    let ephemeral_pub = ephemeral_priv
        .compute_public_key()
        .map_err(|_| EncryptError::KeyAgreement)?;

    let symmetric_key_bytes = agreement::agree_ephemeral(
        ephemeral_priv,
        &peer_public,
        EncryptError::KeyAgreement,
        |material| Ok(x963_kdf(material, ephemeral_pub.as_ref())),
    )?;

    let in_out = share.to_owned();
    let encrypted = encrypt_aes_gcm(
        &symmetric_key_bytes[..16],
        &symmetric_key_bytes[16..],
        in_out,
    )?;

    let mut output = Vec::with_capacity(encrypted.len() + ephemeral_pub.as_ref().len());
    output.extend_from_slice(ephemeral_pub.as_ref());
    output.extend_from_slice(&encrypted);

    Ok(output)
}

/// Decrypt a bytestring using the private key
///
/// This uses ECIES with X9.63 key derivation function and AES-GCM for the
/// symmetic encryption and MAC.
pub fn decrypt_share(share: &[u8], key: &PrivateKey) -> Result<Vec<u8>, EncryptError> {
    if share.len() < PUBLICKEY_LENGTH + TAG_LENGTH {
        return Err(EncryptError::DecryptionLength);
    }
    let empheral_pub_bytes: &[u8] = &share[0..PUBLICKEY_LENGTH];

    let ephemeral_pub =
        agreement::UnparsedPublicKey::new(&agreement::ECDH_P256, empheral_pub_bytes);

    let fake_rng = ring::test::rand::FixedSliceRandom {
        // private key consists of the public key + private scalar
        bytes: &key.0[PUBLICKEY_LENGTH..],
    };

    let private_key = agreement::EphemeralPrivateKey::generate(&agreement::ECDH_P256, &fake_rng)
        .map_err(|_| EncryptError::KeyAgreement)?;

    let symmetric_key_bytes = agreement::agree_ephemeral(
        private_key,
        &ephemeral_pub,
        EncryptError::KeyAgreement,
        |material| Ok(x963_kdf(material, empheral_pub_bytes)),
    )?;

    // in_out is the AES-GCM ciphertext+tag, wihtout the ephemeral EC pubkey
    let in_out = share[PUBLICKEY_LENGTH..].to_owned();
    decrypt_aes_gcm(
        &symmetric_key_bytes[..KEY_LENGTH],
        &symmetric_key_bytes[KEY_LENGTH..],
        in_out,
    )
}

fn x963_kdf(z: &[u8], shared_info: &[u8]) -> [u8; 32] {
    let mut hasher = ring::digest::Context::new(&ring::digest::SHA256);
    hasher.update(z);
    hasher.update(&1u32.to_be_bytes());
    hasher.update(shared_info);
    let digest = hasher.finish();
    use std::convert::TryInto;
    // unwrap never fails because SHA256 output len is 32
    digest.as_ref().try_into().unwrap()
}

fn decrypt_aes_gcm(key: &[u8], nonce: &[u8], mut data: Vec<u8>) -> Result<Vec<u8>, EncryptError> {
    let cipher = Aes128::new(GenericArray::from_slice(key));
    cipher
        .decrypt_in_place(GenericArray::from_slice(nonce), &[], &mut data)
        .map_err(|_| EncryptError::Decryption)?;
    Ok(data)
}

fn encrypt_aes_gcm(key: &[u8], nonce: &[u8], mut data: Vec<u8>) -> Result<Vec<u8>, EncryptError> {
    let cipher = Aes128::new(GenericArray::from_slice(key));
    cipher
        .encrypt_in_place(GenericArray::from_slice(nonce), &[], &mut data)
        .map_err(|_| EncryptError::Encryption)?;
    Ok(data)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encrypt_decrypt() -> Result<(), EncryptError> {
        let pub_key = PublicKey::from_base64(
            "BIl6j+J6dYttxALdjISDv6ZI4/VWVEhUzaS05LgrsfswmbLOgNt9\
        HUC2E0w+9RqZx3XMkdEHBHfNuCSMpOwofVQ=",
        )?;
        let priv_key = PrivateKey::from_base64(
            "BIl6j+J6dYttxALdjISDv6ZI4/VWVEhUzaS05LgrsfswmbLOgN\
        t9HUC2E0w+9RqZx3XMkdEHBHfNuCSMpOwofVSq3TfyKwn0NrftKisKKVSaTOt5seJ67P5QL4hxgPWvxw==",
        )?;
        let data = (0..100).map(|_| rand::random::<u8>()).collect::<Vec<u8>>();
        let encrypted = encrypt_share(&data, &pub_key)?;

        let decrypted = decrypt_share(&encrypted, &priv_key)?;
        assert_eq!(decrypted, data);
        Ok(())
    }

    #[test]
    fn test_interop() {
        let share1 = base64::decode("Kbnd2ZWrsfLfcpuxHffMrJ1b7sCrAsNqlb6Y1eAMfwCVUNXt").unwrap();
        let share2 = base64::decode("hu+vT3+8/taHP7B/dWXh/g==").unwrap();
        let encrypted_share1 = base64::decode(
            "BEWObg41JiMJglSEA6Ebk37xOeflD2a1t2eiLmX0OPccJhAER5NmOI+4r4Cfm7aJn141sGKnTbCuIB9+AeVuw\
            MAQnzjsGPu5aNgkdpp+6VowAcVAV1DlzZvtwlQkCFlX4f3xmafTPFTPOokYi2a+H1n8GKwd",
        )
        .unwrap();
        let encrypted_share2 = base64::decode(
            "BNRzQ6TbqSc4pk0S8aziVRNjWm4DXQR5yCYTK2w22iSw4XAPW4OB9RxBpWVa1C/3ywVBT/3yLArOMXEsCEMOG\
            1+d2CiEvtuU1zADH2MVaCnXL/dVXkDchYZsvPWPkDcjQA==",
        )
        .unwrap();

        let priv_key1 = PrivateKey::from_base64(
            "BIl6j+J6dYttxALdjISDv6ZI4/VWVEhUzaS05LgrsfswmbLOg\
        Nt9HUC2E0w+9RqZx3XMkdEHBHfNuCSMpOwofVSq3TfyKwn0NrftKisKKVSaTOt5seJ67P5QL4hxgPWvxw==",
        )
        .unwrap();
        let priv_key2 = PrivateKey::from_base64(
            "BNNOqoU54GPo+1gTPv+hCgA9U2ZCKd76yOMrWa1xTWgeb4LhF\
        LMQIQoRwDVaW64g/WTdcxT4rDULoycUNFB60LER6hPEHg/ObBnRPV1rwS3nj9Bj0tbjVPPyL9p8QW8B+w==",
        )
        .unwrap();

        let decrypted1 = decrypt_share(&encrypted_share1, &priv_key1).unwrap();
        let decrypted2 = decrypt_share(&encrypted_share2, &priv_key2).unwrap();

        assert_eq!(decrypted1, share1);
        assert_eq!(decrypted2, share2);
    }
}