/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::{collections::HashMap, fmt};
use crypto_box::{
aead::{Aead, AeadCore},
SalsaBox,
};
use rand::rngs::OsRng;
use crate::{
privmsg::PrivMsgEvent,
settings::{ChannelInfo, ContactInfo, MAXIMUM_LENGTH_OF_NICK_CHAN_CNT},
};
#[derive(serde::Serialize)]
pub struct KeyPair {
pub private_key: String,
pub 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)
}
}
/// The format we're using is nonce+ciphertext, where nonce is 24 bytes.
fn try_decrypt(salt_box: &SalsaBox, ciphertext: &str) -> Option {
let bytes = match bs58::decode(ciphertext).into_vec() {
Ok(v) => v,
Err(_) => return None,
};
if bytes.len() < 25 {
return None
}
// Try extracting the nonce
let nonce = match bytes[0..24].try_into() {
Ok(v) => v,
Err(_) => return None,
};
// Take the remaining ciphertext
let message = &bytes[24..];
// Try decrypting the message
match salt_box.decrypt(nonce, message) {
Ok(v) => Some(String::from_utf8_lossy(&v).to_string()),
Err(_) => None,
}
}
/// The format we're using is nonce+ciphertext, where nonce is 24 bytes.
pub fn encrypt(salt_box: &SalsaBox, plaintext: &[u8]) -> String {
let nonce = SalsaBox::generate_nonce(&mut OsRng);
let mut ciphertext = salt_box.encrypt(&nonce, plaintext).unwrap();
let mut concat = vec![];
concat.append(&mut nonce.as_slice().to_vec());
concat.append(&mut ciphertext);
bs58::encode(concat).into_string()
}
pub fn decrypt_target(
contact: &mut String,
privmsg: &mut PrivMsgEvent,
configured_chans: HashMap,
configured_contacts: HashMap,
) {
for chan_name in configured_chans.keys() {
let chan_info = configured_chans.get(chan_name).unwrap();
if !chan_info.joined {
continue
}
let salt_box = chan_info.salt_box.clone();
if let Some(salt_box) = salt_box {
let decrypted_target = try_decrypt(&salt_box, &privmsg.target);
if decrypted_target.is_none() {
continue
}
let target =
String::from_utf8_lossy(&unpad(decrypted_target.unwrap().into())).to_string();
if *chan_name == target {
privmsg.target = target;
return
}
}
}
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(&salt_box, &privmsg.target);
if decrypted_target.is_none() {
continue
}
let target =
String::from_utf8_lossy(&unpad(decrypted_target.unwrap().into())).to_string();
privmsg.target = target;
*contact = cnt_name.into();
return
}
}
}
/// Decrypt PrivMsg nickname and message
pub fn decrypt_privmsg(salt_box: &SalsaBox, privmsg: &mut PrivMsgEvent) {
let decrypted_nick = try_decrypt(salt_box, &privmsg.nick);
let decrypted_msg = try_decrypt(salt_box, &privmsg.msg);
if decrypted_nick.is_none() && decrypted_msg.is_none() {
return
}
privmsg.nick = String::from_utf8_lossy(&unpad(decrypted_nick.unwrap().into())).to_string();
privmsg.msg = decrypted_msg.unwrap();
}
/// Encrypt PrivMsg
pub fn encrypt_privmsg(salt_box: &SalsaBox, privmsg: &mut PrivMsgEvent) {
privmsg.nick = encrypt(salt_box, &pad(privmsg.nick.clone().into()));
privmsg.target = encrypt(salt_box, &pad(privmsg.target.clone().into()));
privmsg.msg = encrypt(salt_box, privmsg.msg.as_bytes());
}
fn pad(data: Vec) -> Vec {
if data.len() == MAXIMUM_LENGTH_OF_NICK_CHAN_CNT {
return data
}
assert!(data.len() < MAXIMUM_LENGTH_OF_NICK_CHAN_CNT);
let padding = vec![0u8; MAXIMUM_LENGTH_OF_NICK_CHAN_CNT - data.len()];
let mut data = data;
data.extend_from_slice(&padding);
data
}
fn unpad(data: Vec) -> Vec {
assert!(data.len() == MAXIMUM_LENGTH_OF_NICK_CHAN_CNT);
match data.iter().position(|&x| x == 0u8) {
Some(idx) => data[..idx].to_vec(),
None => data,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pad_unpad() {
let nick = String::from("terry-davis");
let padded = pad(nick.clone().into());
assert!(padded.len() == 32);
assert_eq!(nick, String::from_utf8_lossy(&unpad(padded)));
let nick = String::from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let padded = pad(nick.clone().into());
assert_eq!(nick, String::from_utf8_lossy(&padded));
assert_eq!(nick, String::from_utf8_lossy(&unpad(padded)));
}
}