diff --git a/Cargo.lock b/Cargo.lock index cc3d668..b70646e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,6 +214,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -517,6 +518,12 @@ dependencies = [ "syn 2.0.41", ] +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "ecdsa" version = "0.16.9" @@ -661,6 +668,12 @@ dependencies = [ "polyval", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "group" version = "0.13.0" @@ -783,6 +796,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.66" @@ -1116,6 +1138,37 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pqcrypto-internals" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d34bec6abe2283e6de7748b68b292d1ffa2203397e3e71380ff8418a49fb46" +dependencies = [ + "cc", + "dunce", + "getrandom", + "libc", +] + +[[package]] +name = "pqcrypto-kyber" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc5d857fb0a0a0695dbe379f449a185bf73d0173cdcaffa86c015b5d1b11490" +dependencies = [ + "cc", + "glob", + "libc", + "pqcrypto-internals", + "pqcrypto-traits", +] + +[[package]] +name = "pqcrypto-traits" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e851c7654eed9e68d7d27164c454961a616cf8c203d500607ef22c737b51bb" + [[package]] name = "primeorder" version = "0.13.6" @@ -1700,7 +1753,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.5.4" +version = "1.6.0" dependencies = [ "aes-gcm-stream", "base64", @@ -1715,6 +1768,8 @@ dependencies = [ "openpgp-card-pcsc", "p256", "p384", + "pqcrypto-kyber", + "pqcrypto-traits", "rand 0.8.5", "rpassword", "rsa", diff --git a/Cargo.toml b/Cargo.toml index 949ebba..313cdb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.5.4" +version = "1.6.0" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" @@ -46,6 +46,8 @@ yubikey = { version = "0.8", features = ["untested"], optional = true } zeroize = "1.7" swift-rs = { path = "swift-rs", optional = true } spki = "0.7.3" +pqcrypto-kyber = "0.8.0" +pqcrypto-traits = "0.3.5" [build-dependencies] swift-rs = { path = "swift-rs", features = ["build"], optional = true } diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index 4b63555..ff69b6e 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -25,8 +25,9 @@ use crate::compress::GzStreamDecoder; use crate::config::TinyEncryptConfig; use crate::consts::{ DATE_TIME_FORMAT, - ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, - ENC_CHACHA20_POLY1305_P256, ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519, + ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, + ENC_AES256_GCM_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256, + ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT, }; use crate::crypto_cryptor::{Cryptor, KeyNonce}; @@ -451,6 +452,8 @@ pub fn try_decrypt_key(config: &Option, #[cfg(feature = "secure-enclave")] TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop), TinyEncryptEnvelopType::PivRsa => try_decrypt_piv_key_rsa(config, envelop, pin, slot), + #[cfg(feature = "macos")] + TinyEncryptEnvelopType::StaticKyber1024 => try_decrypt_key_ecdh_static_kyber1204(config, envelop), unknown_type => simple_error!("Unknown or unsupported type: {}", unknown_type.get_name()), } } @@ -619,7 +622,42 @@ fn try_decrypt_key_ecdh_static_x25519(config: &Option, envelo }; let shared_secret = opt_result!( - util_keychainstatic::decrypt_data(&keychain_key, &e_pub_key_bytes), "Decrypt static x25519 failed: {}"); + util_keychainstatic::decrypt_x25519_data(&keychain_key, &e_pub_key_bytes), "Decrypt static x25519 failed: {}"); + + let key = util::simple_kdf(shared_secret.as_slice()); + let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce }; + let decrypted_key = crypto_simple::decrypt( + cryptor, &key_nonce, &wrap_key.encrypted_data)?; + util::zeroize(key); + util::zeroize(shared_secret); + Ok(decrypted_key) +} + +#[cfg(feature = "macos")] +fn try_decrypt_key_ecdh_static_kyber1204(config: &Option, envelop: &TinyEncryptEnvelop) -> XResult> { + let wrap_key = WrapKey::parse(&envelop.encrypted_key)?; + let cryptor = match wrap_key.header.enc.as_str() { + ENC_AES256_GCM_KYBER1204 => Cryptor::Aes256Gcm, + ENC_CHACHA20_POLY1305_KYBER1204 => Cryptor::ChaCha20Poly1305, + _ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc), + }; + let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?; + let config = opt_value_result!(config, "Tiny encrypt config is not found"); + let config_envelop = opt_value_result!( + config.find_by_kid(&envelop.kid), "Cannot find config for: {}", &envelop.kid); + let config_envelop_args = opt_value_result!(&config_envelop.args, "No arguments found for: {}", &envelop.kid); + if config_envelop_args.len() != 1 && config_envelop_args.len() != 3 { + return simple_error!("Not enough arguments for: {}", &envelop.kid); + } + + let keychain_key = if config_envelop_args.len() == 1 { + KeychainKey::parse(&config_envelop_args[0])? + } else { + KeychainKey::from(&config_envelop_args[0], &config_envelop_args[1], &config_envelop_args[2]) + }; + + let shared_secret = opt_result!( + util_keychainstatic::decrypt_kyber1204_data(&keychain_key, &e_pub_key_bytes), "Decrypt static kyber1204 failed: {}"); let key = util::simple_kdf(shared_secret.as_slice()); let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce }; diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index c9de05b..ea4c8ff 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -13,18 +13,14 @@ use rust_util::util_time::UnixEpochTime; use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env}; use crate::compress::GzStreamEncoder; use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop}; -use crate::consts::{ - ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, - ENC_CHACHA20_POLY1305_P256, ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519, - SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT, -}; +use crate::consts::{ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256, ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT}; use crate::crypto_cryptor::{Cryptor, KeyNonce}; use crate::crypto_rsa; use crate::spec::{ EncEncryptedMeta, EncMetadata, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta, }; -use crate::util_ecdh::{ecdh_p256, ecdh_p384, ecdh_x25519}; +use crate::util_ecdh::{ecdh_kyber1024, ecdh_p256, ecdh_p384, ecdh_x25519}; use crate::util_progress::Progress; use crate::wrap_key::{WrapKey, WrapKeyHeader}; @@ -279,6 +275,9 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig TinyEncryptEnvelopType::PivP384 => { encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?); } + TinyEncryptEnvelopType::StaticKyber1024 => { + encrypted_envelops.push(encrypt_envelop_ecdh_kyber1204(cryptor, key, envelop)?); + } _ => return simple_error!("Not supported type: {:?}", envelop.r#type), } } @@ -315,6 +314,16 @@ fn encrypt_envelop_ecdh_x25519(cryptor: Cryptor, key: &[u8], envelop: &TinyEncry encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop) } +fn encrypt_envelop_ecdh_kyber1204(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult { + let public_key_point_hex = &envelop.public_part; + let (shared_secret, ephemeral_spki) = ecdh_kyber1024::compute_kyber1024_shared_secret(public_key_point_hex)?; + let enc_type = match cryptor { + Cryptor::Aes256Gcm => ENC_AES256_GCM_KYBER1204, + Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_KYBER1204, + }; + encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop) +} + fn encrypt_envelop_shared_secret(cryptor: Cryptor, key: &[u8], shared_secret: &[u8], diff --git a/src/cmd_initkeychain.rs b/src/cmd_initkeychain.rs index 11e1bc4..9ad633c 100644 --- a/src/cmd_initkeychain.rs +++ b/src/cmd_initkeychain.rs @@ -1,4 +1,5 @@ use clap::Args; +use pqcrypto_traits::kem::PublicKey; use rust_util::{debugging, information, opt_result, simple_error, success, warning, XResult}; use crate::config::TinyEncryptConfigEnvelop; @@ -6,25 +7,33 @@ use crate::spec::TinyEncryptEnvelopType; #[cfg(feature = "secure-enclave")] use crate::util_keychainkey; use crate::util_keychainstatic; -use crate::util_keychainstatic::{KeychainKey, X25519StaticSecret}; +use crate::util_keychainstatic::{KeychainKey, KeychainStaticSecret, KeychainStaticSecretAlgorithm}; #[derive(Debug, Args)] pub struct CmdInitKeychain { /// Secure Enclave #[arg(long, short = 'S')] pub secure_enclave: bool, + /// Expose secure enclave private key data #[arg(long, short = 'E')] pub expose_secure_enclave_private_key: bool, + // /// Keychain name, or default // #[arg(long, short = 'c')] // pub keychain_name: Option, + /// Service name, or default: tiny-encrypt #[arg(long, short = 's')] pub server_name: Option, + /// Key name #[arg(long, short = 'n')] pub key_name: String, + + /// Algorithm (x25519, or kyber1024, default x25519) + #[arg(long, short = 'a')] + pub algorithm: Option, } const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt"; @@ -79,31 +88,77 @@ pub fn keychain_key_static(cmd_init_keychain: CmdInitKeychain) -> XResult<()> { let key_name = &cmd_init_keychain.key_name; let keychain_key = KeychainKey::from_default_keychain(service_name, key_name); - let public_key = match keychain_key.get_password()? { - Some(static_x25519) => { - warning!("Key already exists: {}.{}", service_name, key_name); - let x25519_static_secret = X25519StaticSecret::parse_bytes(static_x25519.as_ref())?; - x25519_static_secret.to_public_key()? - } - None => { - let (keychain_key_bytes, public_key) = util_keychainstatic::generate_static_x25519_secret(); - debugging!("Keychain key : {}", keychain_key_bytes); - opt_result!( - keychain_key.set_password(keychain_key_bytes.as_bytes()), - "Write static x25519 failed: {}" - ); - public_key + let mut envelop_type = match &cmd_init_keychain.algorithm { + None => TinyEncryptEnvelopType::StaticX25519, + Some(algorithm) => { + let a_lower = algorithm.to_lowercase(); + if &a_lower == "kyber" || &a_lower == "kyber1024" { + TinyEncryptEnvelopType::StaticKyber1024 + } else if &a_lower == "25519" || &a_lower == "x25519" || &a_lower == "cv25519" || &a_lower == "curve25519" { + TinyEncryptEnvelopType::StaticX25519 + } else { + return simple_error!("Unknown algorithm: {}", algorithm); + } + } + }; + + let public_key_hex = match keychain_key.get_password()? { + Some(static_key) => { + warning!("Key already exists: {}.{}", service_name, key_name); + let keychain_static_secret = KeychainStaticSecret::parse_bytes(static_key.as_ref())?; + match keychain_static_secret.algo { + KeychainStaticSecretAlgorithm::X25519 => { + envelop_type = TinyEncryptEnvelopType::StaticX25519; + } + KeychainStaticSecretAlgorithm::Kyber1024 => { + envelop_type = TinyEncryptEnvelopType::StaticKyber1024; + } + } + match keychain_static_secret.algo { + KeychainStaticSecretAlgorithm::X25519 => { + let public_key = keychain_static_secret.to_x25519_public_key()?; + hex::encode(public_key.as_bytes()) + } + KeychainStaticSecretAlgorithm::Kyber1024 => { + let (_, public_key) = keychain_static_secret.to_kyber1204_static_secret()?; + hex::encode(public_key.as_bytes()) + } + } + } + None => { + let (keychain_key_bytes, public_key_hex) = match envelop_type { + TinyEncryptEnvelopType::StaticX25519 => { + let (keychain_key_bytes, public_key) = util_keychainstatic::generate_static_x25519_secret(); + (keychain_key_bytes, hex::encode(public_key.as_bytes())) + } + TinyEncryptEnvelopType::StaticKyber1024 => { + let (keychain_key_bytes, public_key) = util_keychainstatic::generate_static_kyber1024_secret(); + (keychain_key_bytes, hex::encode(public_key.as_bytes())) + } + _ => unreachable!(), + }; + debugging!("Keychain key : {}", keychain_key_bytes); + opt_result!( + keychain_key.set_password(keychain_key_bytes.as_bytes()), + "Write static key failed: {}" + ); + public_key_hex } }; - let public_key_hex = hex::encode(public_key.as_bytes()); success!("Keychain name: {}", &key_name); success!("Public key : {}", &public_key_hex); + let kid_part2 = if public_key_hex.len() <= 64 { + public_key_hex.clone() + } else { + public_key_hex.chars().take(64).collect() + }; + let config_envelop = TinyEncryptConfigEnvelop { - r#type: TinyEncryptEnvelopType::StaticX25519, + r#type: envelop_type, sid: Some(key_name.clone()), - kid: format!("keychain:{}", &public_key_hex), + kid: format!("keychain:{}", &kid_part2), desc: Some("Keychain static".to_string()), args: Some(vec![keychain_key.to_str()]), public_part: public_key_hex, diff --git a/src/config.rs b/src/config.rs index bd05ac1..04ae965 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use std::{env, fs}; use std::cmp::Ordering; use std::collections::HashMap; +use std::path::Path; use std::path::PathBuf; use rust_util::{debugging, opt_result, simple_error, warning, XResult}; @@ -100,7 +101,7 @@ impl TinyEncryptConfig { Ok(config) } - pub fn resolve_path_namespace(&self, path: &PathBuf, append_te: bool) -> PathBuf { + pub fn resolve_path_namespace(&self, path: &Path, append_te: bool) -> PathBuf { if let Some(path_str) = path.to_str() { if path_str.starts_with(':') { let namespace = path_str.chars().skip(1) @@ -117,7 +118,7 @@ impl TinyEncryptConfig { } } } - path.clone() + path.to_path_buf() } pub fn find_namespace(&self, prefix: &str) -> Option<&String> { @@ -208,9 +209,9 @@ impl TinyEncryptConfig { } } -pub fn resolve_path_namespace(config: &Option, path: &PathBuf, append_te: bool) -> PathBuf { +pub fn resolve_path_namespace(config: &Option, path: &Path, append_te: bool) -> PathBuf { match config { - None => path.clone(), + None => path.to_path_buf(), Some(config) => config.resolve_path_namespace(path, append_te), } } diff --git a/src/consts.rs b/src/consts.rs index 105ecda..d5faa26 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -2,9 +2,11 @@ pub const ENC_AES256_GCM_P256: &str = "aes256-gcm-p256"; pub const ENC_AES256_GCM_P384: &str = "aes256-gcm-p384"; pub const ENC_AES256_GCM_X25519: &str = "aes256-gcm-x25519"; +pub const ENC_AES256_GCM_KYBER1204: &str = "aes256-gcm-kyber1204"; pub const ENC_CHACHA20_POLY1305_P256: &str = "chacha20-poly1305-p256"; pub const ENC_CHACHA20_POLY1305_P384: &str = "chacha20-poly1305-p384"; pub const ENC_CHACHA20_POLY1305_X25519: &str = "chacha20-poly1305-x25519"; +pub const ENC_CHACHA20_POLY1305_KYBER1204: &str = "chacha20-poly1305-kyber1204"; // Extend and config file pub const TINY_ENC_FILE_EXT: &str = ".tinyenc"; diff --git a/src/spec.rs b/src/spec.rs index 1bfbfaf..af433d4 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -76,6 +76,9 @@ pub enum TinyEncryptEnvelopType { // Keychain Static X25519 (less secure) #[serde(rename = "static-x25519")] StaticX25519, + // Keychain Static Kyber1024 (less secure) + #[serde(rename = "static-kyber1024")] + StaticKyber1024, // Secure Enclave ECDH P256 #[serde(rename = "key-p256")] KeyP256, @@ -106,6 +109,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::PgpRsa => "pgp-rsa", TinyEncryptEnvelopType::PgpX25519 => "pgp-x25519", TinyEncryptEnvelopType::StaticX25519 => "static-x25519", + TinyEncryptEnvelopType::StaticKyber1024 => "static-kyber1024", TinyEncryptEnvelopType::KeyP256 => "key-p256", TinyEncryptEnvelopType::Age => "age", TinyEncryptEnvelopType::PivP256 => "piv-p256", @@ -120,6 +124,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::PgpRsa => false, TinyEncryptEnvelopType::PgpX25519 => false, TinyEncryptEnvelopType::StaticX25519 => true, + TinyEncryptEnvelopType::StaticKyber1024 => true, TinyEncryptEnvelopType::KeyP256 => true, TinyEncryptEnvelopType::Age => false, TinyEncryptEnvelopType::PivP256 => false, diff --git a/src/util_ecdh.rs b/src/util_ecdh.rs index 67f5587..7284222 100644 --- a/src/util_ecdh.rs +++ b/src/util_ecdh.rs @@ -61,3 +61,18 @@ pub mod ecdh_x25519 { Ok((shared_secret.as_bytes().to_vec(), ephemeral_public.as_bytes().to_vec())) } } + +pub mod ecdh_kyber1024 { + use pqcrypto_kyber::kyber1024; + use pqcrypto_kyber::kyber1024::PublicKey as Kyber1024PublicKey; + use pqcrypto_traits::kem::{Ciphertext, PublicKey, SharedSecret}; + use rust_util::{opt_result, XResult}; + + pub fn compute_kyber1024_shared_secret(public_key_point_hex: &str) -> XResult<(Vec, Vec)> { + let public_key_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse Kyber1024 public key hex failed: {}"); + let public_key = opt_result!(Kyber1024PublicKey::from_bytes(&public_key_bytes), "Parse Kyber1024 public key failed: {}"); + let (shared_secret, ciphertext) = kyber1024::encapsulate(&public_key); + + Ok((shared_secret.as_bytes().to_vec(), ciphertext.as_bytes().to_vec())) + } +} diff --git a/src/util_keychainstatic.rs b/src/util_keychainstatic.rs index c9c05c1..4905042 100644 --- a/src/util_keychainstatic.rs +++ b/src/util_keychainstatic.rs @@ -1,9 +1,14 @@ +use pqcrypto_kyber::kyber1024; +use pqcrypto_kyber::kyber1024::Ciphertext as Kyber1024Ciphertext; +use pqcrypto_kyber::kyber1024::PublicKey as Kyber1024PublicKey; +use pqcrypto_kyber::kyber1024::SecretKey as Kyber1024SecretKey; use rust_util::{debugging, opt_result, opt_value_result, simple_error, XResult}; use security_framework::os::macos::keychain::SecKeychain; use x25519_dalek::{PublicKey, StaticSecret}; use zeroize::Zeroize; const X2559_PLAIN_PREFIX: &str = "x25519-plain:"; +const KYBER1024_PLAIN_PREFIX: &str = "kyber1024-plain:"; const KEYCHAIN_KEY_PREFIX: &str = "keychain:"; pub struct KeychainKey { @@ -12,48 +17,112 @@ pub struct KeychainKey { pub key_name: String, } - -pub struct X25519StaticSecret { - pub secret: Vec, +pub enum KeychainStaticSecretAlgorithm { + X25519, + Kyber1024, } -impl Zeroize for X25519StaticSecret { +impl KeychainStaticSecretAlgorithm { + pub fn prefix(&self) -> &'static str { + match self { + Self::X25519 => X2559_PLAIN_PREFIX, + Self::Kyber1024 => KYBER1024_PLAIN_PREFIX, + } + } + pub fn from_prefix(str: &str) -> Option { + if str.starts_with(X2559_PLAIN_PREFIX) { + Some(Self::X25519) + } else if str.starts_with(KYBER1024_PLAIN_PREFIX) { + Some(Self::Kyber1024) + } else { + None + } + } +} + +pub struct KeychainStaticSecret { + pub algo: KeychainStaticSecretAlgorithm, + pub secret: Vec, + pub public: Option>, +} + +impl Zeroize for KeychainStaticSecret { fn zeroize(&mut self) { self.secret.zeroize(); } } -impl X25519StaticSecret { +impl KeychainStaticSecret { pub fn parse_bytes(bs: &[u8]) -> XResult { - let key_str = opt_result!(String::from_utf8(bs.to_vec()), "Parse static x25519 failed: {}"); + let key_str = opt_result!(String::from_utf8(bs.to_vec()), "Parse static secret failed: {}"); Self::parse(&key_str) } pub fn parse(key: &str) -> XResult { - if !key.starts_with(X2559_PLAIN_PREFIX) { - return simple_error!("Not X25519 plain key"); - } - let extract_key_hex = &key[X2559_PLAIN_PREFIX.len()..]; - let extract_key = opt_result!(hex::decode(extract_key_hex), "Decode X25519 plain key failed: {}"); + let algo = opt_value_result!( + KeychainStaticSecretAlgorithm::from_prefix(key), "Unknown static secret: {}", key); + let extract_key_hex = &key[algo.prefix().len()..]; + let extract_key = opt_result!(hex::decode(extract_key_hex), "Decode static secret plain key failed: {}"); + let (secret, public) = match algo { + KeychainStaticSecretAlgorithm::X25519 => { + (extract_key, None) + } + KeychainStaticSecretAlgorithm::Kyber1024 => { + // pub const PQCLEAN_KYBER1024_CLEAN_CRYPTO_SECRETKEYBYTES: usize = 3168; + // pub const PQCLEAN_KYBER1024_CLEAN_CRYPTO_PUBLICKEYBYTES: usize = 1568; + let secret_key_bytes_len = 3168; + let public_key_bytes_len = 1568; + if extract_key.len() != secret_key_bytes_len + public_key_bytes_len { + return simple_error!("Bad kyber1024 secret and public keys."); + } + (extract_key[0..secret_key_bytes_len].to_vec(), Some(extract_key[secret_key_bytes_len..].to_vec())) + } + }; Ok(Self { - secret: extract_key, + algo, + secret, + public, }) } pub fn to_str(&self) -> String { let mut v = String::new(); - v.push_str(X2559_PLAIN_PREFIX); + v.push_str(self.algo.prefix()); v.push_str(&hex::encode(&self.secret)); + if let Some(public) = &self.public { + v.push_str(&hex::encode(public)); + } v } - pub fn from_bytes(bytes: &[u8]) -> Self { + pub fn from_x25519_bytes(secret: &[u8]) -> Self { + Self::from_bytes(KeychainStaticSecretAlgorithm::X25519, secret, None) + } + + pub fn from_kyber1024_bytes(secret: &[u8], public: &[u8]) -> Self { + Self::from_bytes(KeychainStaticSecretAlgorithm::Kyber1024, secret, Some(public)) + } + + pub fn from_bytes(algo: KeychainStaticSecretAlgorithm, secret: &[u8], public: Option<&[u8]>) -> Self { Self { - secret: bytes.to_vec(), + algo, + secret: secret.to_vec(), + public: public.map(|p| p.to_vec()), } } - pub fn to_static_secret(&self) -> XResult { + pub fn to_kyber1204_static_secret(&self) -> XResult<(Kyber1024SecretKey, Kyber1024PublicKey)> { + use pqcrypto_traits::kem::{PublicKey, SecretKey}; + let secret_key = opt_result!(Kyber1024SecretKey::from_bytes(&self.secret), + "Parse kyber1204 private key failed: {}"); + let public_key = opt_result!(match &self.public { + None => return simple_error!("Kyber1204 public key not found."), + Some(public) => Kyber1024PublicKey::from_bytes(public), + }, "Parse kyber1204 public key failed: {}"); + Ok((secret_key, public_key)) + } + + pub fn to_x25519_static_secret(&self) -> XResult { let secret_slice = self.secret.as_slice(); let mut inner_secret: [u8; 32] = opt_result!(secret_slice.try_into(), "X25519 secret key error: {}"); let static_secret = StaticSecret::from(inner_secret); @@ -61,8 +130,8 @@ impl X25519StaticSecret { Ok(static_secret) } - pub fn to_public_key(&self) -> XResult { - let static_secret = self.to_static_secret()?; + pub fn to_x25519_public_key(&self) -> XResult { + let static_secret = self.to_x25519_static_secret()?; let public_key: PublicKey = (&static_secret).into(); Ok(public_key) } @@ -141,11 +210,11 @@ impl KeychainKey { } } -pub fn decrypt_data(keychain_key: &KeychainKey, ephemeral_public_key_bytes: &[u8]) -> XResult> { +pub fn decrypt_x25519_data(keychain_key: &KeychainKey, ephemeral_public_key_bytes: &[u8]) -> XResult> { let static_x25519 = opt_value_result!(keychain_key.get_password()?, "Static X25519 not found: {}", &keychain_key.to_str()); - let x25519_static_secret = X25519StaticSecret::parse_bytes(&static_x25519)?; - let static_secret = x25519_static_secret.to_static_secret()?; + let x25519_static_secret = KeychainStaticSecret::parse_bytes(&static_x25519)?; + let static_secret = x25519_static_secret.to_x25519_static_secret()?; let inner_ephemeral_public_key: [u8; 32] = opt_result!( ephemeral_public_key_bytes.try_into(), "X25519 public key error: {}"); let ephemeral_public_key = PublicKey::from(inner_ephemeral_public_key); @@ -154,10 +223,33 @@ pub fn decrypt_data(keychain_key: &KeychainKey, ephemeral_public_key_bytes: &[u8 Ok(shared_secret.as_bytes().to_vec()) } +pub fn decrypt_kyber1204_data(keychain_key: &KeychainKey, ephemeral_public_key_bytes: &[u8]) -> XResult> { + use pqcrypto_traits::kem::{Ciphertext, SharedSecret}; + let static_kyber1204 = opt_value_result!(keychain_key.get_password()?, "Static Kyber1204 not found: {}", &keychain_key.to_str()); + + let kyber1204_static_secret = KeychainStaticSecret::parse_bytes(&static_kyber1204)?; + let (static_secret, _) = kyber1204_static_secret.to_kyber1204_static_secret()?; + let ciphertext = opt_result!( + Kyber1024Ciphertext::from_bytes(ephemeral_public_key_bytes), "Parse kyber1204 ciphertext failed: {}"); + let shared_secret = kyber1024::decapsulate(&ciphertext, &static_secret); + + Ok(shared_secret.as_bytes().to_vec()) +} + pub fn generate_static_x25519_secret() -> (String, PublicKey) { let static_secret = StaticSecret::random(); let public_key: PublicKey = (&static_secret).into(); let static_secret_bytes = static_secret.as_bytes(); - let x25519_static_secret = X25519StaticSecret::from_bytes(static_secret_bytes); + let x25519_static_secret = KeychainStaticSecret::from_x25519_bytes(static_secret_bytes); (x25519_static_secret.to_str(), public_key) +} + +pub fn generate_static_kyber1024_secret() -> (String, Kyber1024PublicKey) { + use pqcrypto_traits::kem::{PublicKey, SecretKey}; + let (public_key, private_key) = kyber1024::keypair(); + let static_secret_bytes = private_key.as_bytes(); + let static_public_bytes = public_key.as_bytes(); + let kyber1024_static_secret = + KeychainStaticSecret::from_kyber1024_bytes(static_secret_bytes, static_public_bytes); + (kyber1024_static_secret.to_str(), public_key) } \ No newline at end of file