From f16572787888aa8b0b18a47bd9dec2314e43b515 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 13 May 2024 11:39:51 +0800 Subject: [PATCH] implement decryption --- src/encrypt.rs | 71 ++++++++++++++++++++++++++++++++++++++++---------- src/lib.rs | 2 +- src/main.rs | 50 +++++++++++++++++++++++++++-------- src/public.rs | 5 +++- src/runtime.rs | 4 ++- src/secret.rs | 4 +-- 6 files changed, 106 insertions(+), 30 deletions(-) diff --git a/src/encrypt.rs b/src/encrypt.rs index 17ca745..beb1df6 100644 --- a/src/encrypt.rs +++ b/src/encrypt.rs @@ -24,15 +24,15 @@ use std::str::FromStr; use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit}; use aes::{Aes256, Block}; use amplify::confinement::{Confined, SmallOrdMap, U64 as U64MAX}; -use amplify::Bytes32; +use amplify::{Bytes32, Wrapper}; use armor::{ArmorHeader, ArmorParseError, AsciiArmor}; -use ec25519::{edwards25519, Error}; +use ec25519::edwards25519; use rand::random; use sha2::digest::generic_array::GenericArray; use sha2::{Digest, Sha256}; use strict_encoding::{StrictDeserialize, StrictSerialize}; -use crate::{Algo, InvalidPubkey, SsiPub, LIB_NAME_SSI}; +use crate::{Algo, InvalidPubkey, SsiPair, SsiPub, LIB_NAME_SSI}; #[derive(Copy, Clone, Debug, Display, Error)] pub enum EncryptionError { @@ -42,10 +42,22 @@ pub enum EncryptionError { InvalidPubkey(SsiPub), } +#[derive(Copy, Clone, Debug, Display, Error)] +pub enum DecryptionError { + #[display("the message can't be decrypted using key {0}")] + KeyMismatch(SsiPub), + #[display("invalid public key {0}")] + InvalidPubkey(SsiPub), +} + #[derive(Clone, Debug, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_SSI)] -pub struct SymmetricKey(Bytes32); +pub struct SymmetricKey( + #[from] + #[from([u8; 32])] + Bytes32, +); impl AsRef<[u8]> for SymmetricKey { fn as_ref(&self) -> &[u8] { self.0.as_ref() } @@ -107,7 +119,7 @@ impl Encrypted { for pk in receivers { keys.insert( pk, - pk.encrypt(&key) + pk.encrypt_key(&key) .map_err(|_| EncryptionError::InvalidPubkey(pk))?, ); } @@ -117,28 +129,59 @@ impl Encrypted { data: Confined::from_collection_unsafe(msg), }) } + + pub fn decrypt(&self, pair: SsiPair) -> Result, DecryptionError> { + let key = self + .keys + .iter() + .find(|(pk, _)| *pk == &pair.pk) + .map(|(_, secret)| secret) + .ok_or(DecryptionError::KeyMismatch(pair.pk))? + .copy(); + let key = pair + .decrypt_key(key) + .map_err(|_| DecryptionError::InvalidPubkey(pair.pk))?; + Ok(decrypt(self.data.to_inner(), key)) + } } impl SsiPub { - pub fn encrypt(&self, key: &SymmetricKey) -> Result { + pub fn encrypt_key(&self, key: &SymmetricKey) -> Result { match self.algo() { - Algo::Ed25519 => self.encrypt_ed25519(key), + Algo::Ed25519 => self.encrypt_key_ed25519(key), Algo::Bip340 | Algo::Other(_) => Err(InvalidPubkey), } } - pub fn encrypt_ed25519(&self, key: &SymmetricKey) -> Result { - let pk = ec25519::PublicKey::try_from(*self)?; - let ge = edwards25519::GeP3::from_bytes_vartime(&pk).ok_or(InvalidPubkey)?; + pub fn encrypt_key_ed25519(&self, key: &SymmetricKey) -> Result { + let ge = + edwards25519::GeP3::from_bytes_vartime(&self.to_byte_array()).ok_or(InvalidPubkey)?; + + Ok(edwards25519::ge_scalarmult(key.as_ref(), &ge) + .to_bytes() + .into()) + } +} + +impl SsiPair { + pub fn decrypt_key(&self, key: Bytes32) -> Result { + match self.pk.algo() { + Algo::Ed25519 => self.decrypt_key_ed25519(key), + Algo::Bip340 | Algo::Other(_) => Err(InvalidPubkey), + } + } + pub fn decrypt_key_ed25519(&self, key: Bytes32) -> Result { + let ge = edwards25519::GeP3::from_bytes_negate_vartime(&self.pk.to_byte_array()) + .ok_or(InvalidPubkey)?; Ok(edwards25519::ge_scalarmult(key.as_ref(), &ge) .to_bytes() .into()) } } -pub fn encrypt(mut source: Vec, passwd: impl AsRef<[u8]>) -> Vec { - let key = Sha256::digest(passwd.as_ref()); +pub fn encrypt(mut source: Vec, key: impl AsRef<[u8]>) -> Vec { + let key = Sha256::digest(key.as_ref()); let key = GenericArray::from_slice(key.as_slice()); let cipher = Aes256::new(key); @@ -149,8 +192,8 @@ pub fn encrypt(mut source: Vec, passwd: impl AsRef<[u8]>) -> Vec { source } -pub fn decrypt(mut source: Vec, passwd: impl AsRef<[u8]>) -> Vec { - let key = Sha256::digest(passwd.as_ref()); +pub fn decrypt(mut source: Vec, key: impl AsRef<[u8]>) -> Vec { + let key = Sha256::digest(key.as_ref()); let key = GenericArray::from_slice(key.as_slice()); let cipher = Aes256::new(key); diff --git a/src/lib.rs b/src/lib.rs index 85a91aa..6f66957 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ mod runtime; pub use bip340::Bip340Secret; pub use ed25519::Ed25519Secret; -pub use encrypt::{decrypt, encrypt, Encrypted, EncryptionError, SymmetricKey}; +pub use encrypt::{decrypt, encrypt, DecryptionError, Encrypted, EncryptionError, SymmetricKey}; pub use identity::{Ssi, SsiParseError, Uid}; pub use public::{ Algo, CertParseError, Chain, Fingerprint, InvalidPubkey, InvalidSig, SsiCert, SsiPub, SsiQuery, diff --git a/src/main.rs b/src/main.rs index 365bea8..0d12377 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,8 +19,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[macro_use] -extern crate amplify; #[macro_use] extern crate clap; @@ -29,12 +27,10 @@ use std::io::{stdin, Read}; use std::path::PathBuf; use std::str::FromStr; +use armor::AsciiArmor; use chrono::{DateTime, Utc}; use clap::Parser; -use rand::random; -use ssi::{ - encrypt, Algo, Chain, Encrypted, InvalidSig, Ssi, SsiCert, SsiQuery, SsiRuntime, SsiSecret, Uid, -}; +use ssi::{Algo, Chain, Encrypted, InvalidSig, Ssi, SsiCert, SsiQuery, SsiRuntime, SsiSecret, Uid}; #[derive(Parser, Clone, Debug)] pub struct Args { @@ -107,7 +103,8 @@ pub enum Command { /// Signature certificate to verify signature: SsiCert, }, - + // Recover, + /// Encrypt a message for receiver(s) Encrypt { /// Identities which must be able to decrypt #[clap(short, long, required = true)] @@ -121,7 +118,21 @@ pub enum Command { #[clap(short, long)] file: Option, }, - // Recover, + + /// Decrypt a message using one of your private keys + Decrypt { + /// Private key to use for decryption + #[clap(short, long)] + key: Option, + + /// Text message to decrypt + #[clap(short, long, conflicts_with = "file")] + text: Option, + + /// File to decrypt + #[clap(short, long)] + file: Option, + }, } fn main() { @@ -191,7 +202,7 @@ fn main() { println!("{ssi}"); if !passwd.is_empty() { - secret.encrypt(passwd); + secret.conceal(passwd); } runtime.secrets.insert(secret); @@ -208,7 +219,7 @@ fn main() { } => { eprintln!("Signing with {ssi} ..."); - let passwd = rpassword::prompt_password("Password for private key encryption: ") + let passwd = rpassword::prompt_password("Password for the private key: ") .expect("unable to read password"); let msg = get_message(text, file); let signer = runtime @@ -235,7 +246,7 @@ fn main() { Err(err) => eprintln!("invalid: {err}"), } println!(); - } /* */ + } //Command::Recover => { //use std::collections::HashSet; //let passwd = rpassword::prompt_password("Password for private key encryption: ") @@ -264,6 +275,23 @@ fn main() { let encrypted = Encrypted::encrypt(msg, receivers).expect("unable to encrypt"); println!("{encrypted}"); } + Command::Decrypt { key, text, file } => { + let key = key.unwrap_or_default(); + eprintln!("Decrypting with {key} ..."); + + let passwd = rpassword::prompt_password("Password for the private key: ") + .expect("unable to read password"); + let pair = runtime + .find_signer(key, &passwd) + .expect("unknown private key"); + eprintln!("Using key {pair}"); + + let s = String::from_utf8(get_message(text, file)) + .expect("the provided message is not ASCII armored"); + let encrypted = Encrypted::from_ascii_armored_str(&s).expect("invalid ASCII armor"); + let msg = encrypted.decrypt(pair).expect("can't decrypt the message"); + println!("{}", String::from_utf8_lossy(&msg)); + } } } diff --git a/src/public.rs b/src/public.rs index 9556399..aefd243 100644 --- a/src/public.rs +++ b/src/public.rs @@ -309,9 +309,12 @@ pub enum InvalidSig { UnsupportedAlgo(u8), } -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug, Display, From)] #[display(inner)] pub enum SsiQuery { + #[default] + #[display("default key")] + Default, #[from] Pub(SsiPub), #[from] diff --git a/src/runtime.rs b/src/runtime.rs index de1e692..5d88ade 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -121,6 +121,8 @@ impl SsiRuntime { pub fn find_identity(&self, query: impl Into) -> Option<&Ssi> { let query = query.into(); self.identities.iter().find(|ssi| match query { + // TODO: Support custom default keys + SsiQuery::Default => true, SsiQuery::Pub(pk) => ssi.pk == pk, SsiQuery::Fp(fp) => ssi.pk.fingerprint() == fp, SsiQuery::Id(ref id) => ssi.uids.iter().any(|uid| { @@ -137,7 +139,7 @@ impl SsiRuntime { let sk = self.secrets.iter().find_map(|s| { let mut s = (*s).clone(); if !passwd.is_empty() { - s.decrypt(passwd); + s.reveal(passwd); } if s.to_public() == ssi.pk { Some(s) diff --git a/src/secret.rs b/src/secret.rs index 44f90fb..bf3e266 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -142,11 +142,11 @@ impl SsiSecret { } } - pub fn encrypt(&mut self, passwd: impl AsRef) { + pub fn conceal(&mut self, passwd: impl AsRef) { self.replace(&encrypt(self.to_vec(), passwd.as_ref())); } - pub fn decrypt(&mut self, passwd: impl AsRef) { + pub fn reveal(&mut self, passwd: impl AsRef) { self.replace(&decrypt(self.to_vec(), passwd.as_ref())); }