mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 07:08:05 -05:00
bin/ircd: implement encrypted DMs
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 target = decrypted_target.unwrap();
|
||||
if *contact == target {
|
||||
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();
|
||||
privmsg.target = target;
|
||||
*contact = cnt_name.into();
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user