Просмотр исходного кода

darkirc: multiple chacha keys support

oars 1 год назад
Родитель
Сommit
2b865bcd59

+ 6 - 2
bin/darkirc/darkirc_config.toml

@@ -158,16 +158,20 @@ topic = "LunarDAO talk"
 ## You can generate a keypair with: darkirc --gen-chacha-keypair
 ## You can generate a keypair with: darkirc --gen-chacha-keypair
 ## and replace the secret key below with the generated one.
 ## and replace the secret key below with the generated one.
 ## **You should never share this secret key with anyone**
 ## **You should never share this secret key with anyone**
-#[crypto]
-#dm_chacha_secret = "AKfyoKxnHb8smqP2zt9BVvXkcN7pm9GnqqyuYRmxmWtR"
 
 
 ## This is where you put other people's public keys. The format is:
 ## This is where you put other people's public keys. The format is:
 ## [contact."nickname"]. "nickname" can be anything you want.
 ## [contact."nickname"]. "nickname" can be anything you want.
 ## This is how they will appear in your IRC client when they send you a DM.
 ## This is how they will appear in your IRC client when they send you a DM.
+## set their public key to dm_chacha_public and
+## set your secret key to dm_chacha_secret
+## you can set a separate secret key for each contact
+
 ##
 ##
 ## Example (set as many as you want):
 ## Example (set as many as you want):
 #[contact."satoshi"]
 #[contact."satoshi"]
 #dm_chacha_public = "C9vC6HNDfGQofWCapZfQK5MkV1JR8Cct839RDUCqbDGK"
 #dm_chacha_public = "C9vC6HNDfGQofWCapZfQK5MkV1JR8Cct839RDUCqbDGK"
+#dm_chacha_secret = "A3mLrq4aW9UkFVY4zCfR2aLdEEWVUdH4u8v4o2dgi4kC"
 #
 #
 #[contact."anon"]
 #[contact."anon"]
 #dm_chacha_public = "7iTddcopP2pkvszFjbFUr7MwTcMSKZkYP6zUan22pxfX"
 #dm_chacha_public = "7iTddcopP2pkvszFjbFUr7MwTcMSKZkYP6zUan22pxfX"
+#dm_chacha_secret = "E229CzXev335cxhHiJyuzSapz7HMfNzf6ipbginFTvtr"

+ 3 - 1
bin/darkirc/src/irc/mod.rs

@@ -136,5 +136,7 @@ pub struct IrcChannel {
 /// IRC contact definition
 /// IRC contact definition
 #[derive(Clone)]
 #[derive(Clone)]
 pub struct IrcContact {
 pub struct IrcContact {
-    pub saltbox: Option<Arc<ChaChaBox>>,
+    /// the first one is the saltbox created with our contact pub key
+    /// the second one is the saltbox created with our own pub key
+    pub saltboxes: (Arc<ChaChaBox>, Arc<ChaChaBox>),
 }
 }

+ 14 - 29
bin/darkirc/src/irc/server.rs

@@ -49,7 +49,7 @@ use smol::{
 };
 };
 use url::Url;
 use url::Url;
 
 
-use super::{client::Client, ChaChaBox, IrcChannel, IrcContact, Priv, Privmsg};
+use super::{client::Client, IrcChannel, IrcContact, Priv, Privmsg};
 use crate::{
 use crate::{
     crypto::{
     crypto::{
         rln::{RlnIdentity, RLN2_SIGNAL_ZKBIN, RLN2_SLASH_ZKBIN},
         rln::{RlnIdentity, RLN2_SIGNAL_ZKBIN, RLN2_SLASH_ZKBIN},
@@ -86,8 +86,6 @@ pub struct IrcServer {
     pub contacts: RwLock<HashMap<String, IrcContact>>,
     pub contacts: RwLock<HashMap<String, IrcContact>>,
     /// Configured RLN identity
     /// Configured RLN identity
     pub rln_identity: RwLock<Option<RlnIdentity>>,
     pub rln_identity: RwLock<Option<RlnIdentity>>,
-    /// Saltbox used to encrypt our nick in direct messages
-    saltbox: RwLock<Option<Arc<ChaChaBox>>>,
     /// Active client connections
     /// Active client connections
     clients: Mutex<HashMap<u16, StoppableTaskPtr>>,
     clients: Mutex<HashMap<u16, StoppableTaskPtr>>,
     /// IRC server Password
     /// IRC server Password
@@ -218,7 +216,6 @@ impl IrcServer {
             autojoin: RwLock::new(Vec::new()),
             autojoin: RwLock::new(Vec::new()),
             channels: RwLock::new(HashMap::new()),
             channels: RwLock::new(HashMap::new()),
             contacts: RwLock::new(HashMap::new()),
             contacts: RwLock::new(HashMap::new()),
-            saltbox: RwLock::new(None),
             rln_identity: RwLock::new(None),
             rln_identity: RwLock::new(None),
             clients: Mutex::new(HashMap::new()),
             clients: Mutex::new(HashMap::new()),
             password,
             password,
@@ -251,7 +248,7 @@ impl IrcServer {
         let configured_channels = parse_configured_channels(&contents)?;
         let configured_channels = parse_configured_channels(&contents)?;
 
 
         // Parse configured contacts
         // Parse configured contacts
-        let (contacts, saltbox) = parse_configured_contacts(&contents)?;
+        let contacts = parse_configured_contacts(&contents)?;
 
 
         // Parse RLN identity
         // Parse RLN identity
         let rln_identity = parse_rln_identity(&contents)?;
         let rln_identity = parse_rln_identity(&contents)?;
@@ -270,7 +267,6 @@ impl IrcServer {
         *self.autojoin.write().await = autojoin;
         *self.autojoin.write().await = autojoin;
         *self.channels.write().await = channels;
         *self.channels.write().await = channels;
         *self.contacts.write().await = contacts;
         *self.contacts.write().await = contacts;
-        *self.saltbox.write().await = saltbox;
         *self.rln_identity.write().await = rln_identity;
         *self.rln_identity.write().await = rln_identity;
 
 
         Ok(())
         Ok(())
@@ -401,20 +397,15 @@ impl IrcServer {
         };
         };
 
 
         if let Some((name, contact)) = self.contacts.read().await.get_key_value(privmsg.channel()) {
         if let Some((name, contact)) = self.contacts.read().await.get_key_value(privmsg.channel()) {
-            if let Some(saltbox) = &contact.saltbox {
-                // We will use dummy channel and nick values of MAX_NICK_LEN,
-                // since they are not used, so all encrypted messages look the same.
-                *privmsg.channel() = saltbox::encrypt(saltbox, &[0x00; MAX_NICK_LEN]);
-                // We will encrypt the dummy nick value using our own self saltbox,
-                // so we can identify our messages. We can safely unwrap here since
-                // we know that if contacts exist, our self saltbox does as well.
-                *privmsg.nick() = saltbox::encrypt(
-                    self.saltbox.read().await.as_ref().unwrap(),
-                    &[0x00; MAX_NICK_LEN],
-                );
-                *privmsg.msg() = saltbox::encrypt(saltbox, privmsg.msg().as_bytes());
-                debug!("Successfully encrypted message for {}", name);
-            }
+            let (saltbox, self_saltbox) = &contact.saltboxes;
+            // We will use dummy channel and nick values of MAX_NICK_LEN,
+            // since they are not used, so all encrypted messages look the same.
+            *privmsg.channel() = saltbox::encrypt(saltbox, &[0x00; MAX_NICK_LEN]);
+            // We will encrypt the dummy nick value using our own self saltbox,
+            // so we can identify our messages.
+            *privmsg.nick() = saltbox::encrypt(self_saltbox, &[0x00; MAX_NICK_LEN]);
+            *privmsg.msg() = saltbox::encrypt(saltbox, privmsg.msg().as_bytes());
+            debug!("Successfully encrypted message for {}", name);
         };
         };
     }
     }
 
 
@@ -466,21 +457,15 @@ impl IrcServer {
         }
         }
 
 
         for (name, contact) in self.contacts.read().await.iter() {
         for (name, contact) in self.contacts.read().await.iter() {
-            let Some(saltbox) = &contact.saltbox else { continue };
+            let (saltbox, self_saltbox) = &contact.saltboxes;
 
 
             if saltbox::try_decrypt(saltbox, &channel_ciphertext).is_none() {
             if saltbox::try_decrypt(saltbox, &channel_ciphertext).is_none() {
                 continue
                 continue
             };
             };
 
 
             // Since everyone encrypts the dummy nick value with their self saltbox,
             // Since everyone encrypts the dummy nick value with their self saltbox,
-            // we try to decrypt using our, to identify our messages. We can safely
-            // unwrap here since we know that if contacts exist, our self saltbox does as well.
-            let nick = if saltbox::try_decrypt(
-                self.saltbox.read().await.as_ref().unwrap(),
-                &nick_ciphertext,
-            )
-            .is_some()
-            {
+            // we try to decrypt using our, to identify our messages.
+            let nick = if saltbox::try_decrypt(self_saltbox, &nick_ciphertext).is_some() {
                 String::from(self_nickname)
                 String::from(self_nickname)
             } else {
             } else {
                 name.to_string()
                 name.to_string()

+ 8 - 4
bin/darkirc/src/main.rs

@@ -191,10 +191,14 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
         let public = secret.public_key();
         let public = secret.public_key();
         let secret = bs58::encode(secret.to_bytes()).into_string();
         let secret = bs58::encode(secret.to_bytes()).into_string();
         let public = bs58::encode(public.to_bytes()).into_string();
         let public = bs58::encode(public.to_bytes()).into_string();
-        println!("Place this in your config file:\n");
-        println!("[crypto]");
-        println!("#dm_chacha_public = \"{}\"", public);
+
+        println!(
+            "Place this in your config file under your contact, you can reuse this key for multiple contacts\n"
+        );
+        println!("[contact.\"satoshi\"]");
+        println!("dm_chacha_public = \"YOUR_CONTACT_PUBLIC_KEY\"");
         println!("dm_chacha_secret = \"{}\"", secret);
         println!("dm_chacha_secret = \"{}\"", secret);
+        println!("#my_dm_chacha_public = \"{}\"", public);
         return Ok(())
         return Ok(())
     }
     }
 
 
@@ -251,7 +255,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
         // Parse configured contacts
         // Parse configured contacts
         let contacts = list_configured_contacts(&contents)?;
         let contacts = list_configured_contacts(&contents)?;
 
 
-        for (name, public_key) in contacts {
+        for (name, (public_key, _)) in contacts {
             println!("{}: {}", name, bs58::encode(public_key.as_bytes()).into_string())
             println!("{}: {}", name, bs58::encode(public_key.as_bytes()).into_string())
         }
         }
         return Ok(())
         return Ok(())

+ 39 - 48
bin/darkirc/src/settings.rs

@@ -22,7 +22,7 @@ use std::{
     time::UNIX_EPOCH,
     time::UNIX_EPOCH,
 };
 };
 
 
-use crypto_box::{ChaChaBox, PublicKey};
+use crypto_box::PublicKey;
 use darkfi::{Error::ParseFailed, Result};
 use darkfi::{Error::ParseFailed, Result};
 use darkfi_sdk::{crypto::pasta_prelude::PrimeField, pasta::pallas};
 use darkfi_sdk::{crypto::pasta_prelude::PrimeField, pasta::pallas};
 use log::info;
 use log::info;
@@ -64,39 +64,9 @@ pub fn parse_autojoin_channels(data: &toml::Value) -> Result<Vec<String>> {
     Ok(ret)
     Ok(ret)
 }
 }
 
 
-/// Parse a DM secret key from a TOML map.
-///
-/// ```toml
-/// [crypto]
-/// dm_chacha_secret = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
-/// ```
-fn parse_dm_chacha_secret(data: &toml::Value) -> Result<Option<crypto_box::SecretKey>> {
-    let Some(table) = data.as_table() else { return Err(ParseFailed("TOML not a map")) };
-    let Some(crypto) = table.get("crypto") else { return Ok(None) };
-    let Some(crypto) = crypto.as_table() else { return Err(ParseFailed("`crypto` not a map")) };
-
-    if !crypto.contains_key("dm_chacha_secret") {
-        return Ok(None)
-    }
-
-    let Some(secret_str) = crypto["dm_chacha_secret"].as_str() else {
-        return Err(ParseFailed("dm_chacha_secret not a string"))
-    };
-
-    let Ok(secret_bytes) = bs58::decode(secret_str).into_vec() else {
-        return Err(ParseFailed("dm_chacha_secret not valid base58"))
-    };
-
-    if secret_bytes.len() != 32 {
-        return Err(ParseFailed("dm_chacha_secret not 32 bytes long"))
-    }
-
-    let secret_bytes: [u8; 32] = secret_bytes.try_into().unwrap();
-
-    Ok(Some(crypto_box::SecretKey::from(secret_bytes)))
-}
-
-pub fn list_configured_contacts(data: &toml::Value) -> Result<HashMap<String, PublicKey>> {
+pub fn list_configured_contacts(
+    data: &toml::Value,
+) -> Result<HashMap<String, (PublicKey, crypto_box::SecretKey)>> {
     let mut ret = HashMap::new();
     let mut ret = HashMap::new();
 
 
     let Some(table) = data.as_table() else { return Err(ParseFailed("TOML not a map")) };
     let Some(table) = data.as_table() else { return Err(ParseFailed("TOML not a map")) };
@@ -107,11 +77,11 @@ pub fn list_configured_contacts(data: &toml::Value) -> Result<HashMap<String, Pu
 
 
     for (name, items) in contacts {
     for (name, items) in contacts {
         let Some(public_str) = items.get("dm_chacha_public") else {
         let Some(public_str) = items.get("dm_chacha_public") else {
-            return Err(ParseFailed("Invalid contact configuration"))
+            return Err(ParseFailed("Invalid contact configuration dm_chacha_public missing"))
         };
         };
 
 
         let Some(public_str) = public_str.as_str() else {
         let Some(public_str) = public_str.as_str() else {
-            return Err(ParseFailed("Invalid contact configuration"))
+            return Err(ParseFailed("dm_chacha_public not a string"))
         };
         };
 
 
         let Ok(public_bytes) = bs58::decode(public_str).into_vec() else {
         let Ok(public_bytes) = bs58::decode(public_str).into_vec() else {
@@ -130,7 +100,29 @@ pub fn list_configured_contacts(data: &toml::Value) -> Result<HashMap<String, Pu
             return Err(ParseFailed("Duplicate contact found"))
             return Err(ParseFailed("Duplicate contact found"))
         }
         }
 
 
-        ret.insert(name.to_string(), public);
+        // parse the secret key for that specific contact
+        let Some(contact_secret) = items.get("dm_chacha_secret") else {
+            return Err(ParseFailed("Invalid contact configuration dm_chacha_secret missing. \
+            You can generate a keypair with: 'darkirc --gen-chacha-keypair' and then add that keypair to your config toml file."))
+        };
+
+        let Some(contact_secret_str) = contact_secret.as_str() else {
+            return Err(ParseFailed("dm_chacha_secret not a string"))
+        };
+
+        let Ok(contact_secret_bytes) = bs58::decode(contact_secret_str).into_vec() else {
+            return Err(ParseFailed("dm_chacha_secret not valid base58"))
+        };
+
+        if contact_secret_bytes.len() != 32 {
+            return Err(ParseFailed("dm_chacha_secret not 32 bytes long"))
+        }
+
+        let contact_secret_bytes: [u8; 32] = contact_secret_bytes.try_into().unwrap();
+
+        let contact_secret = crypto_box::SecretKey::from(contact_secret_bytes);
+
+        ret.insert(name.to_string(), (public, contact_secret));
     }
     }
 
 
     Ok(ret)
     Ok(ret)
@@ -144,30 +136,29 @@ pub fn list_configured_contacts(data: &toml::Value) -> Result<HashMap<String, Pu
 /// dm_chacha_public = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
 /// dm_chacha_public = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
 /// ```
 /// ```
 #[allow(clippy::type_complexity)]
 #[allow(clippy::type_complexity)]
-pub fn parse_configured_contacts(
-    data: &toml::Value,
-) -> Result<(HashMap<String, IrcContact>, Option<Arc<ChaChaBox>>)> {
+pub fn parse_configured_contacts(data: &toml::Value) -> Result<HashMap<String, IrcContact>> {
     let mut ret = HashMap::new();
     let mut ret = HashMap::new();
 
 
     let contacts = list_configured_contacts(data)?;
     let contacts = list_configured_contacts(data)?;
     if contacts.is_empty() {
     if contacts.is_empty() {
-        return Ok((ret, None));
+        return Ok(ret);
     }
     }
-    let Some(secret) = parse_dm_chacha_secret(data)? else {
-        return Err(ParseFailed("You have specified some contacts but you did not set up a valid chacha secret for yourself.  You can generate a keypair with: 'darkirc --gen-chacha-keypair' and then add that keypair to your config toml file."))
-    };
-    for (name, public) in contacts {
-        let saltbox = Some(Arc::new(crypto_box::ChaChaBox::new(&public, &secret)));
+
+    for (name, (public, contact_secret)) in contacts {
+        let saltbox: Arc<crypto_box::ChaChaBox> =
+            Arc::new(crypto_box::ChaChaBox::new(&public, &contact_secret));
+        let self_saltbox: Arc<crypto_box::ChaChaBox> =
+            Arc::new(crypto_box::ChaChaBox::new(&contact_secret.public_key(), &contact_secret));
 
 
         if ret.contains_key(&name) {
         if ret.contains_key(&name) {
             return Err(ParseFailed("Duplicate contact found"))
             return Err(ParseFailed("Duplicate contact found"))
         }
         }
 
 
         info!("Instantiated ChaChaBox for contact \"{}\"", name);
         info!("Instantiated ChaChaBox for contact \"{}\"", name);
-        ret.insert(name.to_string(), IrcContact { saltbox });
+        ret.insert(name.to_string(), IrcContact { saltboxes: (saltbox, self_saltbox) });
     }
     }
 
 
-    Ok((ret, Some(Arc::new(crypto_box::ChaChaBox::new(&secret.public_key(), &secret)))))
+    Ok(ret)
 }
 }
 
 
 /// Parse configured RLN identity from a TOML map.
 /// Parse configured RLN identity from a TOML map.