From a977ce2f4f05fe6002327eb1efb3065339a7fd25 Mon Sep 17 00:00:00 2001 From: Weiliang Li Date: Wed, 12 Jul 2023 19:16:10 +0900 Subject: [PATCH] Prepare for configuration in the future (#102) --- .cspell.jsonc | 41 ++++++++++++++++++++++++++++++ .vscode/settings.json | 9 +------ .vscode/spellright.dict | 30 ---------------------- Cargo.toml | 1 + LICENSE | 2 +- src/config.rs | 55 +++++++++++++++++++++++++++++++++++++++++ src/consts.rs | 2 ++ src/lib.rs | 25 ++++++++++++++----- src/utils.rs | 33 ++++++++++++++++--------- tests/integration.rs | 49 ++++++++++++++++++++++++++++++++++++ 10 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 .cspell.jsonc delete mode 100644 .vscode/spellright.dict create mode 100644 src/config.rs create mode 100644 tests/integration.rs diff --git a/.cspell.jsonc b/.cspell.jsonc new file mode 100644 index 0000000..c912085 --- /dev/null +++ b/.cspell.jsonc @@ -0,0 +1,41 @@ +{ + // Version of the setting file. Always 0.2 + "version": "0.2", + // language - current active spelling language + "language": "en", + // words - list of words to be always considered correct + "words": [ + "bindgen", + "Codacy", + "consts", + "Ctarget", + "ecies", + "eciespy", + "eciesrs", + "helloworld", + "HKDF", + "keypair", + "libsecp", + "reqwest", + "RUSTFLAGS", + "sandybridge", + "secp", + "secp256k1", + "ssse", + "symm", + "typenum" + ], + // flagWords - list of words to be always considered incorrect + // This is useful for offensive words and common spelling errors. + // For example "hte" should be "the" + "flagWords": ["hte"], + "ignorePaths": [ + ".git", + ".github", + ".gitignore", + ".cspell.jsonc", + "LICENSE", + "package.json", + "yarn.lock" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 0de10f5..65dd5b3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,6 @@ { - "spellright.language": ["en"], - "spellright.documentTypes": ["markdown", "latex", "plaintext", "rust", "toml"], "rust-analyzer.cargo.features": ["pure"], "rust-analyzer.cargo.noDefaultFeatures": true, "rust-analyzer.procMacro.enable": true, - "spellright.ignoreFiles": [ - "~/.cargo/", - "~/.rustup/", - "**/.gitignore", - "**/.spellignore" - ] + "rust-analyzer.linkedProjects": ["./Cargo.toml"] } diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict deleted file mode 100644 index bc69730..0000000 --- a/.vscode/spellright.dict +++ /dev/null @@ -1,30 +0,0 @@ -ecies -eciespy -secp256k1 -keypair -helloworld -sk -hkdf -Codacy -eciesrs -rng -mut -toml -aes -gcm -wasm -Golang -RUSTFLAGS -Ctarget -sse -sandybridge -ssse -bindgen -NONINFRINGEMENT -md -typenum -rs -ecc -rt -js -getrandom diff --git a/Cargo.toml b/Cargo.toml index 41fac5b..d183a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ repository = "https://github.com/ecies/rs" [dependencies] hkdf = "0.12.3" libsecp256k1 = "0.7.1" +once_cell = "1.18.0" sha2 = "0.10.7" # openssl aes diff --git a/LICENSE b/LICENSE index ef54f1e..d10073a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 Weiliang Li +Copyright (c) 2019-2023 Weiliang Li Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..27a2d9f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,55 @@ +use std::sync::Mutex; + +use once_cell::sync::Lazy; + +use crate::consts::{COMPRESSED_PUBLIC_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE}; + +pub enum SymmetricAlgorithm { + Aes256Gcm, +} + +pub struct Config { + pub is_ephemeral_key_compressed: bool, + pub is_hkdf_key_compressed: bool, + pub symmetric_algorithm: SymmetricAlgorithm, +} + +impl Config { + pub fn default() -> Self { + Config { + is_ephemeral_key_compressed: false, + is_hkdf_key_compressed: false, + symmetric_algorithm: SymmetricAlgorithm::Aes256Gcm, + } + } +} + +/// Global config +pub static ECIES_CONFIG: Lazy> = Lazy::new(|| { + let config: Config = Config::default(); + Mutex::new(config) +}); + +pub fn update_config(config: Config) { + *ECIES_CONFIG.lock().unwrap() = config; +} + +pub fn reset_config() { + update_config(Config::default()) +} + +pub fn is_ephemeral_key_compressed() -> bool { + ECIES_CONFIG.lock().unwrap().is_ephemeral_key_compressed +} + +pub fn get_ephemeral_key_size() -> usize { + if is_ephemeral_key_compressed() { + COMPRESSED_PUBLIC_KEY_SIZE + } else { + UNCOMPRESSED_PUBLIC_KEY_SIZE + } +} + +pub fn is_hkdf_key_compressed() -> bool { + ECIES_CONFIG.lock().unwrap().is_hkdf_key_compressed +} diff --git a/src/consts.rs b/src/consts.rs index 61980d2..365bc53 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,3 +1,5 @@ +pub use libsecp256k1::util::{COMPRESSED_PUBLIC_KEY_SIZE, FULL_PUBLIC_KEY_SIZE as UNCOMPRESSED_PUBLIC_KEY_SIZE}; + /// AES IV/nonce length pub const AES_IV_LENGTH: usize = 16; /// AES tag length diff --git a/src/lib.rs b/src/lib.rs index 811d3f4..b1d6123 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,8 @@ //! //! It's also possible to build to the `wasm32-unknown-unknown` target with the pure Rust backend. Check out [this repo](https://github.com/ecies/rs-wasm) for more details. -pub use libsecp256k1::{util::FULL_PUBLIC_KEY_SIZE, Error as SecpError, PublicKey, SecretKey}; +use config::{get_ephemeral_key_size, is_ephemeral_key_compressed}; +pub use libsecp256k1::{Error as SecpError, PublicKey, SecretKey}; /// Constant variables pub mod consts; @@ -59,6 +60,9 @@ pub mod types; /// Utility functions for ecies pub mod utils; +// ecies configuration +pub mod config; + #[cfg(feature = "openssl")] mod openssl_aes; #[cfg(feature = "pure")] @@ -79,8 +83,15 @@ pub fn encrypt(receiver_pub: &[u8], msg: &[u8]) -> Result, SecpError> { let aes_key = encapsulate(&ephemeral_sk, &receiver_pk)?; let encrypted = aes_encrypt(&aes_key, msg).ok_or(SecpError::InvalidMessage)?; - let mut cipher_text = Vec::with_capacity(FULL_PUBLIC_KEY_SIZE + encrypted.len()); - cipher_text.extend(ephemeral_pk.serialize().iter()); + let key_size = get_ephemeral_key_size(); + let mut cipher_text = Vec::with_capacity(key_size + encrypted.len()); + + if is_ephemeral_key_compressed() { + cipher_text.extend(ephemeral_pk.serialize_compressed().iter()); + } else { + cipher_text.extend(ephemeral_pk.serialize().iter()); + } + cipher_text.extend(encrypted); Ok(cipher_text) @@ -94,13 +105,14 @@ pub fn encrypt(receiver_pub: &[u8], msg: &[u8]) -> Result, SecpError> { /// * `msg` - The u8 array reference of the encrypted message pub fn decrypt(receiver_sec: &[u8], msg: &[u8]) -> Result, SecpError> { let receiver_sk = SecretKey::parse_slice(receiver_sec)?; + let key_size = get_ephemeral_key_size(); - if msg.len() < FULL_PUBLIC_KEY_SIZE { + if msg.len() < key_size { return Err(SecpError::InvalidMessage); } - let ephemeral_pk = PublicKey::parse_slice(&msg[..FULL_PUBLIC_KEY_SIZE], None)?; - let encrypted = &msg[FULL_PUBLIC_KEY_SIZE..]; + let ephemeral_pk = PublicKey::parse_slice(&msg[..key_size], None)?; + let encrypted = &msg[key_size..]; let aes_key = decapsulate(&ephemeral_pk, &receiver_sk)?; @@ -111,6 +123,7 @@ pub fn decrypt(receiver_sec: &[u8], msg: &[u8]) -> Result, SecpError> { mod tests { use super::*; + use utils::generate_keypair; const MSG: &str = "helloworld"; diff --git a/src/utils.rs b/src/utils.rs index 20e53e5..5964ef9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,9 @@ use hkdf::Hkdf; -use libsecp256k1::{util::FULL_PUBLIC_KEY_SIZE, Error as SecpError, PublicKey, SecretKey}; +use libsecp256k1::{Error as SecpError, PublicKey, SecretKey}; use rand::thread_rng; use sha2::Sha256; +use crate::config::{get_ephemeral_key_size, is_hkdf_key_compressed}; use crate::consts::EMPTY_BYTES; use crate::types::AesKey; @@ -23,11 +24,8 @@ pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey) -> Result Result Result { + let key_size = get_ephemeral_key_size(); + let mut master = Vec::with_capacity(key_size * 2); + + if is_hkdf_key_compressed() { + master.extend(pk.serialize_compressed().iter()); + master.extend(shared_point.serialize_compressed().iter()); + } else { + master.extend(pk.serialize().iter()); + master.extend(shared_point.serialize().iter()); + } hkdf_sha256(master.as_slice()) } -// private below fn hkdf_sha256(master: &[u8]) -> Result { let h = Hkdf::::new(None, master); let mut out = [0u8; 32]; @@ -53,11 +61,12 @@ fn hkdf_sha256(master: &[u8]) -> Result { #[cfg(test)] pub(crate) mod tests { - use hex::decode; - use libsecp256k1::Error; use rand::{thread_rng, Rng}; + // dev dep + use hex::decode; + use super::*; use crate::consts::{AES_IV_LENGTH, EMPTY_BYTES}; diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..d80377c --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,49 @@ +use ecies::{ + config::{reset_config, update_config, Config, SymmetricAlgorithm}, + decrypt, encrypt, + utils::{decapsulate, encapsulate, generate_keypair}, + PublicKey, SecretKey, +}; + +use hex::decode; + +const MSG: &[u8] = "helloworld".as_bytes(); + +#[test] +fn can_change_behavior_with_config() { + let mut two = [0u8; 32]; + let mut three = [0u8; 32]; + two[31] = 2u8; + three[31] = 3u8; + + let sk2 = SecretKey::parse_slice(&two).unwrap(); + let pk2 = PublicKey::from_secret_key(&sk2); + let sk3 = SecretKey::parse_slice(&three).unwrap(); + let pk3 = PublicKey::from_secret_key(&sk3); + + update_config(Config { + is_ephemeral_key_compressed: false, + is_hkdf_key_compressed: true, + symmetric_algorithm: SymmetricAlgorithm::Aes256Gcm, + }); + + assert_eq!(encapsulate(&sk2, &pk3), decapsulate(&pk2, &sk3)); + + assert_eq!( + encapsulate(&sk2, &pk3).map(|v| v.to_vec()).unwrap(), + decode("b192b226edb3f02da11ef9c6ce4afe1c7e40be304e05ae3b988f4834b1cb6c69").unwrap() + ); + + update_config(Config { + is_ephemeral_key_compressed: true, + is_hkdf_key_compressed: true, + symmetric_algorithm: SymmetricAlgorithm::Aes256Gcm, + }); + + let (sk, pk) = generate_keypair(); + let (sk, pk) = (&sk.serialize(), &pk.serialize_compressed()); + + assert_eq!(MSG, decrypt(sk, &encrypt(pk, MSG).unwrap()).unwrap().as_slice()); + + reset_config(); +}