iot_device_bridge 1.1.1

Bridge between messaging of the device and the cloud IoT (e.g., AWS).
Documentation
    //! Functionality implementing the data payload encryption using ECIES (Elliptic Curve Integrated Encryption Scheme) 
    //! built upon AES-GCM-256 and HKDF-SHA256 and using the secp256k1 curve. 
//! 
//! The method is implemented in interoperable libraries in Golang, Python, Rust and Typescript ... 
//! i.e., these languages used also inthe backend (e.g., the decryption lambda in Golang).
//! 
//! This encryption framework is standardized as: ISO/IEC 18033-2.
//! 
//! The encryption can be set selectively per device using the IoT Shadow node `data_encryption_config`, like:
//! ```json
//! "data_encryption_config": {
//!     "method": "EciesSecp256k1",
//!     "public_key": [<byte>]
//! }
//! ``` 
//! 
//! The `DataEncryptionConfig` is currently implemented only for `ECIES-secp256k1` method.
//! Other methods may require different `CryptoParams`, e.g., _Nonce_ and may have the keys of different size.
//! 

use crate::error::IoTError;
use serde::{self, Deserialize, Serialize};
use ecies::{FULL_PUBLIC_KEY_SIZE, encrypt, /* decrypt, PublicKey, utils::generate_keypair */};

const NO_ENCRYPTION: &str = "NO ENCRYPTION";
const ECIES_SECP256K1: &str = "ECIES-secp256k1";

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum DataEncryptionMethod {
    NoEncryption,
    EciesSecp256k1,
}

impl std::fmt::Display for DataEncryptionMethod {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            DataEncryptionMethod::NoEncryption => write!(f, "{}", NO_ENCRYPTION),
            DataEncryptionMethod::EciesSecp256k1 => write!(f, "{}", ECIES_SECP256K1),
        }
    }
}

impl std::str::FromStr for DataEncryptionMethod {
    type Err = IoTError;

    fn from_str(s: &str) -> ::core::result::Result<Self, Self::Err> {
        match s {
            NO_ENCRYPTION => {
                ::core::result::Result::Ok(DataEncryptionMethod::NoEncryption)
            }
            ECIES_SECP256K1 => {
                ::core::result::Result::Ok(DataEncryptionMethod::EciesSecp256k1)
            }
            _ => ::core::result::Result::Err(IoTError::IotStatusParsingError),
        }
    }
}


#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DataEncryptionConfig {
    pub method: DataEncryptionMethod,
    pub public_key: Vec<u8>, 
    // params: CryptoParams, // for other methods, e.g., sym_key, init_vec, hmac for AES-CTR
}

#[derive(Serialize, Deserialize, Debug)]
pub struct DataEncryptionConfigOption {
    pub method: Option<DataEncryptionMethod>,
    pub public_key: Option<Vec<u8>>, 
    // params: Option<CryptoParams>,
}

impl Default for DataEncryptionConfig  {
    fn default()  -> DataEncryptionConfig {
        DataEncryptionConfig {
            method: DataEncryptionMethod::NoEncryption,
            public_key: Vec::with_capacity(FULL_PUBLIC_KEY_SIZE),
        }
    }
}

impl DataEncryptionConfig {
    pub fn new() -> DataEncryptionConfig {
        Default::default()
    }

    // currently the encryption is supporting one method only
    // pub fn set_method(&mut self, method: String) {
    //     self.method = method;
    // }

    pub fn set_public_key(&mut self, public_key: Vec<u8>) {
        self.public_key = public_key;
    }
        
    pub fn get_public_key(&self) -> &Vec<u8> {
        &self.public_key
    }

    /// `update` function only updates only attributes changed in the IotShadow
    pub fn update(&mut self, data_encryption_config_option: &DataEncryptionConfigOption) {
        if let Some(x) = &data_encryption_config_option.method {
            self.method = x.clone();
        }
        if let Some(x) = &data_encryption_config_option.public_key {
            self.public_key = x.to_vec();
        }
    }

    /// `encrypt` the plaintext returning base64 encoded ciphertext
    pub fn encrypt_b64(&self, plaintext: String) -> Result<String, IoTError> {
        let public_key = self.get_public_key();
        let ciphertext = encrypt(public_key, plaintext.as_bytes())?;
        return Ok(base64::encode_config(&ciphertext, base64::STANDARD));
    }
}

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

    #[test]
    fn update_test() {
        println!("\nTEST PART 1");
        let data = r#"
        {
            "data_encryption_config": {
                "method": "EciesSecp256k1",
                "public_key": [
                    4,89,117,155,81,243,172,179,
                    90,195,137,53,151,179,94,29,
                    83,81,109,41,239,43,231,104,
                    14,189,163,2,229,86,3,148,
                    164,194,250,198,166,60,62,162,
                    124,188,178,137,87,61,52,245,
                    18,210,207,175,130,234,120,161,
                    45,205,156,7,34,37,164,106,
                    128
                ]
            }
        }"#;

        let mut dec = DataEncryptionConfig::new();
        println!("DataEncryptionConfig - NEW: {:?}", dec);

        let val: Value = serde_json::from_str(data).unwrap();
        let val: serde_json::Value = val["data_encryption_config"].clone();
        let deco: DataEncryptionConfigOption = serde_json::from_value(val).unwrap();
        println!("DataEncryptionConfig - OPTION: {:?}", deco);

        dec.update(&deco);
        println!("DataEncryptionConfig - UPDATED: {:?}", dec);

        assert_eq!(dec.method, DataEncryptionMethod::EciesSecp256k1);
        assert_eq!(dec.public_key.len(), 65);
        assert_eq!(dec.public_key.capacity(), 65);
        assert_eq!(dec.public_key[0], 4u8);
        assert_eq!(dec.public_key[64], 128u8);

        println!("\nTEST PART 2");
        let data = r#"
        {
            "data_encryption_config": {
                "method": "NoEncryption"
            }
        }"#;

        let mut dec = DataEncryptionConfig::new();
        println!("DataEncryptionConfig - NEW: {:?}", dec);

        let val: Value = serde_json::from_str(data).unwrap();
        let val: serde_json::Value = val["data_encryption_config"].clone();
        let deco: DataEncryptionConfigOption = serde_json::from_value(val).unwrap();
        println!("DataEncryptionConfig - OPTION: {:?}", deco);

        dec.update(&deco);
        println!("DataEncryptionConfig - UPDATED: {:?}", dec);

        assert_eq!(dec.method, DataEncryptionMethod::NoEncryption);
        assert_eq!(dec.public_key.len(), 0);
        assert_eq!(dec.public_key.capacity(), 65);

    }

    use ecies::{decrypt};
    use serde_json::Value;

    #[test]
    fn encrypt_b64_test() {
        let public_key: Vec<u8> = vec![
            4u8,89u8,117u8,155u8,81u8,243u8,172u8,179u8,
            90u8,195u8,137u8,53u8,151u8,179u8,94u8,29u8,
            83u8,81u8,109u8,41u8,239u8,43u8,231u8,104u8,
            14u8,189u8,163u8,2u8,229u8,86u8,3u8,148u8,
            164u8,194u8,250u8,198u8,166u8,60u8,62u8,162u8,
            124u8,188u8,178u8,137u8,87u8,61u8,52u8,245u8,
            18u8,210u8,207u8,175u8,130u8,234u8,120u8,161u8,
            45u8,205u8,156u8,7u8,34u8,37u8,164u8,106u8,
            128u8
        ];

        let secret_key: Vec<u8> = vec![
            209, 69, 249, 227, 132, 172, 11, 253, 
            70, 42, 122, 86, 14, 10, 141, 99, 
            31, 156, 13, 179, 38, 112, 240, 242, 
            141, 211, 124, 250, 74, 47, 242, 195
        ];

        let plaintext = "Hello World";

        // set crypto config
        let mut data_encr_config = DataEncryptionConfig::new();
        data_encr_config.set_public_key(public_key);

        // encrypt
        let ciphertext = data_encr_config.encrypt_b64(plaintext.to_string()).unwrap();

        // decode and decrypt
        let ciphertext = base64::decode_config(ciphertext, base64::STANDARD).unwrap();
        let decrypted_text = decrypt(&secret_key, &ciphertext).unwrap();

        assert_eq!(plaintext.as_bytes(), decrypted_text);

    }

    
}