소스 검색

bin/ircd: implement encrypted DMs

Dastan-glitch 3 년 전
부모
커밋
1552aa26d3
5개의 변경된 파일152개의 추가작업 그리고 46개의 파일을 삭제
  1. 13 7
      bin/ircd/ircd_config.toml
  2. 18 10
      bin/ircd/src/crypto.rs
  3. 35 3
      bin/ircd/src/main.rs
  4. 30 11
      bin/ircd/src/server.rs
  5. 56 15
      bin/ircd/src/settings.rs

+ 13 - 7
bin/ircd/ircd_config.toml

@@ -65,10 +65,16 @@ seeds = ["tls://lilith0.dark.fi:25551", "tls://lilith1.dark.fi:25551"]
 [channel."#dev"]
 topic = "DarkFi Development HQ"
 
-## Contacts list
-# Shared secrets that encrypt direct communication between two nicknames on
-# the network.
-# These are in the form of secret:[nick0,nick1], which means that the same
-# shared secret will be used for all the nicknames in the list.
-[contact."7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"]
-nicks = ["sneed", "chuck"]
+### Contacts list
+## private key used to decrypt direct messages to you along with
+## contact_pubkey.
+## !!SHOULD NEVER SHARE THIS!!
+#[private_key."955Dfa83pU7RCevT2rMrGfhza4kcy6FShSNE6AdR4Q7A"]
+
+## Shared Pubkey that encrypt direct communication between two nicknames
+## on the network.
+## These are in the form of [Contact_nick, My_nick, Contact_pubkey]
+## contact nickname (this is not the irc nickname)
+#[contact."narodnik"]
+## contact public key
+#contact_pubkey = "C9vC6HNDfGQofWCapZfQK5MkV1JR8Cct839RDUCqbDGK"

+ 18 - 10
bin/ircd/src/crypto.rs

@@ -5,7 +5,11 @@ use crypto_box::{
 use fxhash::FxHashMap;
 use rand::rngs::OsRng;
 
-use crate::{privmsg::Privmsg, settings::ChannelInfo, MAXIMUM_LENGTH_OF_NICKNAME};
+use crate::{
+    privmsg::Privmsg,
+    settings::{ChannelInfo, ContactInfo},
+    MAXIMUM_LENGTH_OF_NICKNAME,
+};
 
 /// Try decrypting a message given a NaCl box and a base58 string.
 /// The format we're using is nonce+ciphertext, where nonce is 24 bytes.
@@ -50,9 +54,10 @@ pub fn encrypt_message(salt_box: &SalsaBox, plaintext: &str) -> String {
 
 /// Decrypt PrivMsg target
 pub fn decrypt_target(
+    contact: &mut String,
     privmsg: &mut Privmsg,
     configured_chans: FxHashMap<String, ChannelInfo>,
-    configured_contacts: FxHashMap<String, SalsaBox>,
+    configured_contacts: FxHashMap<String, ContactInfo>,
 ) {
     for chan_name in configured_chans.keys() {
         let chan_info = configured_chans.get(chan_name).unwrap();
@@ -76,16 +81,19 @@ pub fn decrypt_target(
         }
     }
 
-    for contact in configured_contacts.keys() {
-        let salt_box = configured_contacts.get(contact).unwrap();
-        let decrypted_target = try_decrypt_message(salt_box, &privmsg.target);
-        if decrypted_target.is_none() {
-            continue
-        }
+    for cnt_name in configured_contacts.keys() {
+        let cnt_info = configured_contacts.get(cnt_name).unwrap();
+
+        let salt_box = cnt_info.salt_box.clone();
+        if let Some(salt_box) = salt_box {
+            let decrypted_target = try_decrypt_message(&salt_box, &privmsg.target);
+            if decrypted_target.is_none() {
+                continue
+            }
 
-        let target = decrypted_target.unwrap();
-        if *contact == target {
+            let target = decrypted_target.unwrap();
             privmsg.target = target;
+            *contact = cnt_name.into();
             return
         }
     }

+ 35 - 3
bin/ircd/src/main.rs

@@ -2,7 +2,8 @@ use async_std::{
     net::TcpListener,
     sync::{Arc, Mutex},
 };
-use std::{fs::File, net::SocketAddr};
+use settings::ContactInfo;
+use std::{fmt, fs::File, net::SocketAddr};
 
 use async_channel::Receiver;
 use async_executor::Executor;
@@ -21,6 +22,7 @@ use darkfi::{
     util::{
         cli::{get_log_config, get_log_level, spawn_config},
         expand_path,
+        file::save_json_file,
         path::get_config_path,
     },
     Error, Result,
@@ -49,6 +51,18 @@ pub const SIZE_OF_MSGS_BUFFER: usize = 4096;
 pub const MAXIMUM_LENGTH_OF_MESSAGE: usize = 1024;
 pub const MAXIMUM_LENGTH_OF_NICKNAME: usize = 32;
 
+#[derive(serde::Serialize)]
+struct KeyPair {
+    private_key: String,
+    public_key: String,
+}
+
+impl fmt::Display for KeyPair {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Public key: {}\nPrivate key: {}", self.public_key, self.private_key)
+    }
+}
+
 struct Ircd {
     // msgs
     seen_msg_ids: SeenMsgIds,
@@ -56,7 +70,7 @@ struct Ircd {
     // channels
     autojoin_chans: Vec<String>,
     configured_chans: FxHashMap<String, ChannelInfo>,
-    configured_contacts: FxHashMap<String, crypto_box::SalsaBox>,
+    configured_contacts: FxHashMap<String, ContactInfo>,
     // p2p
     p2p: net::P2pPtr,
     senders: SubscriberPtr<Privmsg>,
@@ -70,7 +84,7 @@ impl Ircd {
         autojoin_chans: Vec<String>,
         password: String,
         configured_chans: FxHashMap<String, ChannelInfo>,
-        configured_contacts: FxHashMap<String, crypto_box::SalsaBox>,
+        configured_contacts: FxHashMap<String, ContactInfo>,
         p2p: net::P2pPtr,
     ) -> Self {
         let senders = Subscriber::new();
@@ -177,6 +191,24 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
         return Ok(())
     }
 
+    if settings.gen_keypair {
+        let secret_key = crypto_box::SecretKey::generate(&mut OsRng);
+        let pub_key = secret_key.public_key();
+        let prv_encoded = bs58::encode(secret_key.as_bytes()).into_string();
+        let pub_encoded = bs58::encode(pub_key.as_bytes()).into_string();
+
+        let kp = KeyPair { private_key: prv_encoded, public_key: pub_encoded };
+
+        if settings.output.is_some() {
+            let datastore = expand_path(&settings.output.unwrap())?;
+            save_json_file(&datastore, &kp)?;
+        } else {
+            println!("Generated KeyPair:\n{}", kp);
+        }
+
+        return Ok(())
+    }
+
     let password = settings.password.unwrap_or_default();
 
     // Pick up channel settings from the TOML configuration

+ 30 - 11
bin/ircd/src/server.rs

@@ -11,6 +11,7 @@ use darkfi::{net::P2pPtr, system::SubscriberPtr, util::Timestamp, Error, Result}
 use crate::{
     crypto::{decrypt_privmsg, decrypt_target, encrypt_privmsg},
     privmsg::{ArcPrivmsgsBuffer, Privmsg, SeenMsgIds},
+    settings::ContactInfo,
     ChannelInfo, MAXIMUM_LENGTH_OF_MESSAGE, MAXIMUM_LENGTH_OF_NICKNAME,
 };
 
@@ -35,7 +36,7 @@ pub struct IrcServerConnection<C: AsyncRead + AsyncWrite + Send + Unpin + 'stati
     nickname: String,
     auto_channels: Vec<String>,
     pub configured_chans: FxHashMap<String, ChannelInfo>,
-    pub configured_contacts: FxHashMap<String, crypto_box::SalsaBox>,
+    pub configured_contacts: FxHashMap<String, ContactInfo>,
     capabilities: FxHashMap<String, bool>,
     // p2p
     p2p: P2pPtr,
@@ -54,7 +55,7 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcServerConnection<C>
         auto_channels: Vec<String>,
         password: String,
         configured_chans: FxHashMap<String, ChannelInfo>,
-        configured_contacts: FxHashMap<String, crypto_box::SalsaBox>,
+        configured_contacts: FxHashMap<String, ContactInfo>,
         p2p: P2pPtr,
         senders: SubscriberPtr<Privmsg>,
         subscriber_id: u64,
@@ -236,8 +237,12 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcServerConnection<C>
                         info!("(Encrypted) PRIVMSG: {:?}", privmsg);
                     }
                 } else {
-                    // If we have a configured secret for this nick, we encrypt the message.
-                    if let Some(salt_box) = self.configured_contacts.get(target) {
+                    if !self.configured_contacts.contains_key(target) {
+                        return Ok(())
+                    }
+
+                    let contact_info = self.configured_contacts.get(target).unwrap();
+                    if let Some(salt_box) = &contact_info.salt_box {
                         encrypt_privmsg(salt_box, &mut privmsg);
                         info!("(Encrypted) PRIVMSG: {:?}", privmsg);
                     }
@@ -344,10 +349,10 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcServerConnection<C>
                 return Ok(())
             }
 
+            println!("sending dms in the buffer");
             for msg in self.privmsgs_buffer.lock().await.to_vec() {
-                if msg.target == self.nickname ||
-                    (msg.nickname == self.nickname && !msg.target.starts_with('#'))
-                {
+                if !msg.target.starts_with('#') {
+                    println!("if statement is ok in dms buffer thing");
                     self.senders.notify_by_id(msg, self.subscriber_id).await;
                 }
             }
@@ -446,7 +451,14 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcServerConnection<C>
         info!("Received msg from P2p network: {:?}", msg);
 
         let mut msg = msg.clone();
-        decrypt_target(&mut msg, self.configured_chans.clone(), self.configured_contacts.clone());
+
+        let mut contact = String::new();
+        decrypt_target(
+            &mut contact,
+            &mut msg,
+            self.configured_chans.clone(),
+            self.configured_contacts.clone(),
+        );
 
         if msg.target.starts_with('#') {
             // Try to potentially decrypt the incoming message.
@@ -471,14 +483,21 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcServerConnection<C>
 
             self.reply(&msg.to_irc_msg()).await?;
             return Ok(())
-        } else if self.is_cap_end && self.is_nick_init && self.nickname == msg.target {
-            if self.configured_contacts.contains_key(&msg.target) {
-                let salt_box = self.configured_contacts.get(&msg.target).unwrap();
+        } else if self.is_cap_end && self.is_nick_init {
+            if !self.configured_contacts.contains_key(&contact) {
+                return Ok(())
+            }
+
+            let contact_info = self.configured_contacts.get(&contact).unwrap();
+            if let Some(salt_box) = &contact_info.salt_box {
                 decrypt_privmsg(salt_box, &mut msg);
+                // This is for /query
+                msg.nickname = contact;
                 info!("Decrypted received message: {:?}", msg);
             }
 
             self.reply(&msg.to_irc_msg()).await?;
+            return Ok(())
         }
 
         Ok(())

+ 56 - 15
bin/ircd/src/settings.rs

@@ -39,6 +39,14 @@ pub struct Args {
     #[structopt(long)]
     pub gen_secret: bool,
 
+    /// Generate a new NaCl keypair and exit
+    #[structopt(long)]
+    pub gen_keypair: bool,
+
+    /// Path to save keypair in
+    #[structopt(short)]
+    pub output: Option<String>,
+
     /// Autojoin channels
     #[structopt(long)]
     pub autojoin: Vec<String>,
@@ -55,6 +63,18 @@ pub struct Args {
     pub verbose: u8,
 }
 
+#[derive(Clone)]
+pub struct ContactInfo {
+    /// Optional NaCl box for the channel, used for {en,de}cryption.
+    pub salt_box: Option<SalsaBox>,
+}
+
+impl ContactInfo {
+    pub fn new() -> Result<Self> {
+        Ok(Self { salt_box: None })
+    }
+}
+
 /// This struct holds information about preconfigured channels.
 /// In the TOML configuration file, we can configure channels as such:
 /// ```toml
@@ -93,34 +113,55 @@ fn salt_box_from_shared_secret(s: &str) -> Result<SalsaBox> {
     Ok(SalsaBox::new(&public, &secret))
 }
 
+fn parse_priv_key(data: &str) -> Result<String> {
+    let mut pk = String::new();
+    if let Value::Table(map) = toml::from_str(data)? {
+        if map.contains_key("private_key") && map["private_key"].is_table() {
+            for prv_key in map["private_key"].as_table().unwrap() {
+                pk = prv_key.0.into();
+            }
+        }
+    };
+
+    Ok(pk)
+}
+
 /// Parse a TOML string for any configured contact list and return
 /// a map containing said configurations.
 ///
 /// ```toml
-/// [contact."7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"]
-/// nicks = ["sneed", "chuck"]
+/// [contact."nick"]
+/// contact_pubkey = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
 /// ```
-pub fn parse_configured_contacts(data: &str) -> Result<FxHashMap<String, SalsaBox>> {
+pub fn parse_configured_contacts(data: &str) -> Result<FxHashMap<String, ContactInfo>> {
     let mut ret = FxHashMap::default();
 
     if let Value::Table(map) = toml::from_str(data)? {
         if map.contains_key("contact") && map["contact"].is_table() {
-            for contact in map["contact"].as_table().unwrap() {
-                // (secret, nicks = [nick0, nick1])
-                if contact.1.as_table().unwrap().contains_key("nicks") {
-                    if let Some(nicks) = contact.1["nicks"].as_array() {
-                        let salt_box = salt_box_from_shared_secret(contact.0.as_str())?;
-                        for nick in nicks {
-                            if let Some(n) = nick.as_str() {
-                                info!("Instantiated salt box for {}", n);
-                                ret.insert(n.to_string(), salt_box.clone());
-                            }
-                        }
+            for cnt in map["contact"].as_table().unwrap() {
+                info!("Found configuration for contact {}", cnt.0);
+
+                let mut contact_info = ContactInfo::new()?;
+
+                if cnt.1.as_table().unwrap().contains_key("contact_pubkey") {
+                    // Build the NaCl box
+                    if let Some(p) = cnt.1["contact_pubkey"].as_str() {
+                        let bytes: [u8; 32] = bs58::decode(p).into_vec()?.try_into().unwrap();
+                        let public = crypto_box::PublicKey::from(bytes);
+
+                        let bytes: [u8; 32] =
+                            bs58::decode(parse_priv_key(data)?).into_vec()?.try_into().unwrap();
+                        let secret = crypto_box::SecretKey::from(bytes);
+
+                        contact_info.salt_box = Some(SalsaBox::new(&public, &secret));
+
+                        ret.insert(cnt.0.to_string(), contact_info);
+                        info!("Instantiated NaCl box for contact {}", cnt.0);
                     }
                 }
             }
         }
-    }
+    };
 
     Ok(ret)
 }