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

research/raft-tool: decrypt tasks in tau datastore using secret key

ghassmo 4 лет назад
Родитель
Сommit
385d447757

+ 13 - 0
script/research/raft-tool/Cargo.toml

@@ -12,4 +12,17 @@ features = ["raft"]
 async-std = "1.11.0"
 sled = "0.34.7"
 
+# for taud
+crypto_box = {version = "0.7.2", features = ["std"]}                             
+hex = "0.4.3"
+log = "0.4.17"                                                                   
+simplelog = "0.12.0"                                                             
+rand = "0.8.5"                                                                   
+chrono = "0.4.19"                                                                
+url = "2.2.2"                                                                    
+serde = {version = "1.0.137", features = ["derive"]}                             
+serde_json = "1.0.81" 
+thiserror = "1.0.31"                                                             
+
+
 [workspace]

+ 45 - 0
script/research/raft-tool/src/error.rs

@@ -0,0 +1,45 @@
+use serde_json::Value;
+
+use darkfi::rpc::jsonrpc::{ErrorCode, JsonError, JsonResponse, JsonResult};
+
+#[derive(Debug, thiserror::Error)]
+pub enum TaudError {
+    #[error("Due timestamp invalid")]
+    InvalidDueTime,
+    #[error("Invalid Id")]
+    InvalidId,
+    #[error("Invalid Data/Params: `{0}` ")]
+    InvalidData(String),
+    #[error("InternalError")]
+    Darkfi(#[from] darkfi::error::Error),
+    #[error("Json serialization error: `{0}`")]
+    SerdeJsonError(String),
+}
+
+pub type TaudResult<T> = std::result::Result<T, TaudError>;
+
+impl From<serde_json::Error> for TaudError {
+    fn from(err: serde_json::Error) -> TaudError {
+        TaudError::SerdeJsonError(err.to_string())
+    }
+}
+
+pub fn to_json_result(res: TaudResult<Value>, id: Value) -> JsonResult {
+    match res {
+        Ok(v) => JsonResponse::new(v, id).into(),
+        Err(err) => match err {
+            TaudError::InvalidId => {
+                JsonError::new(ErrorCode::InvalidParams, Some("invalid task id".into()), id).into()
+            }
+            TaudError::InvalidData(e) | TaudError::SerdeJsonError(e) => {
+                JsonError::new(ErrorCode::InvalidParams, Some(e), id).into()
+            }
+            TaudError::InvalidDueTime => {
+                JsonError::new(ErrorCode::InvalidParams, Some("invalid due time".into()), id).into()
+            }
+            TaudError::Darkfi(e) => {
+                JsonError::new(ErrorCode::InternalError, Some(e.to_string()), id).into()
+            }
+        },
+    }
+}

+ 50 - 4
script/research/raft-tool/src/main.rs

@@ -1,5 +1,7 @@
 use std::{fs::File, io::Write};
 
+use crypto_box::{aead::Aead, Box, SecretKey, KEY_SIZE};
+
 use darkfi::{
     raft::DataStore,
     util::{
@@ -9,6 +11,13 @@ use darkfi::{
     Result,
 };
 
+mod error;
+mod month_tasks;
+mod task_info;
+mod util;
+
+use crate::{task_info::TaskInfo, util::load};
+
 #[derive(Debug)]
 pub struct Info<T> {
     pub logs: Vec<Log<T>>,
@@ -32,6 +41,19 @@ struct EncryptedTask {
     payload: Vec<u8>,
 }
 
+fn decrypt_task(encrypt_task: &EncryptedTask, secret_key: &SecretKey) -> Option<TaskInfo> {
+    let public_key = secret_key.public_key();
+    let msg_box = Box::new(&public_key, secret_key);
+
+    let nonce = encrypt_task.nonce.as_slice();
+    let decrypted_task = match msg_box.decrypt(nonce.into(), &encrypt_task.payload[..]) {
+        Ok(m) => m,
+        Err(_) => return None,
+    };
+
+    deserialize(&decrypted_task).ok()
+}
+
 type PrivmsgId = u32;
 
 #[derive(Debug, SerialEncodable, SerialDecodable)]
@@ -46,6 +68,24 @@ fn extract_taud() -> Result<String> {
     let db_path = expand_path(&"~/.config/darkfi/tau/tau.db").unwrap();
     let datastore = DataStore::<EncryptedTask>::new(&db_path.to_str().unwrap())?;
 
+    let sk_path = expand_path(&"~/.config/darkfi/tau/secret_key").unwrap();
+
+    let sk = {
+        let loaded_key = load::<String>(&sk_path);
+
+        if loaded_key.is_err() {
+            log::error!(
+                "Could not load secret key from file, \
+                  please run \"taud --help\" for more information"
+            );
+            return Ok("Load secret_key error".into())
+        }
+
+        let sk_bytes = hex::decode(loaded_key.unwrap())?;
+        let sk_bytes: [u8; KEY_SIZE] = sk_bytes.as_slice().try_into()?;
+        SecretKey::try_from(sk_bytes)?
+    };
+
     println!("Extracting db from: {:?}", db_path);
 
     // Retrieve all data trees
@@ -60,14 +100,20 @@ fn extract_taud() -> Result<String> {
     // Logs
     let mut logs = vec![];
     for log in sled_logs {
-        logs.push(Log { term: log.term, msg: deserialize(&log.msg)? });
+        let encrypt_task: EncryptedTask = deserialize(&log.msg)?;
+        let task_info = decrypt_task(&encrypt_task, &sk).unwrap();
+        logs.push(Log { term: log.term, msg: task_info });
     }
 
     // Commits
     let mut commits = vec![];
     for commit in &sled_commits {
-        commits
-            .push(EncryptedTask { nonce: commit.nonce.clone(), payload: commit.payload.clone() });
+        let task_info = decrypt_task(
+            &EncryptedTask { nonce: commit.nonce.clone(), payload: commit.payload.clone() },
+            &sk,
+        )
+        .unwrap();
+        commits.push(task_info);
     }
 
     // Voted for
@@ -85,7 +131,7 @@ fn extract_taud() -> Result<String> {
         terms.push(term);
     }
 
-    let info = Info::<EncryptedTask> { logs, commits, voted_for, terms };
+    let info = Info::<TaskInfo> { logs, commits, voted_for, terms };
     let info_string = format!("{:#?}", info);
     Ok(info_string)
 }

+ 213 - 0
script/research/raft-tool/src/month_tasks.rs

@@ -0,0 +1,213 @@
+use std::{
+    fs, io,
+    path::{Path, PathBuf},
+};
+
+use chrono::{TimeZone, Utc};
+use log::debug;
+use serde::{Deserialize, Serialize};
+
+use darkfi::util::Timestamp;
+
+use crate::{
+    error::{TaudError, TaudResult},
+    task_info::TaskInfo,
+    util::{load, save},
+};
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct MonthTasks {
+    created_at: Timestamp,
+    task_tks: Vec<String>,
+}
+
+impl MonthTasks {
+    pub fn new(task_tks: &[String]) -> Self {
+        Self { created_at: Timestamp::current_time(), task_tks: task_tks.to_owned() }
+    }
+
+    pub fn add(&mut self, ref_id: &str) {
+        debug!(target: "tau", "MonthTasks::add()");
+        if !self.task_tks.contains(&ref_id.into()) {
+            self.task_tks.push(ref_id.into());
+        }
+    }
+
+    pub fn objects(&self, dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
+        debug!(target: "tau", "MonthTasks::objects()");
+        let mut tks: Vec<TaskInfo> = vec![];
+
+        for ref_id in self.task_tks.iter() {
+            tks.push(TaskInfo::load(ref_id, dataset_path)?);
+        }
+
+        Ok(tks)
+    }
+
+    pub fn remove(&mut self, ref_id: &str) {
+        debug!(target: "tau", "MonthTasks::remove()");
+        if let Some(index) = self.task_tks.iter().position(|t| *t == ref_id) {
+            self.task_tks.remove(index);
+        }
+    }
+
+    pub fn set_date(&mut self, date: &Timestamp) {
+        debug!(target: "tau", "MonthTasks::set_date()");
+        self.created_at = *date;
+    }
+
+    fn get_path(date: &Timestamp, dataset_path: &Path) -> PathBuf {
+        debug!(target: "tau", "MonthTasks::get_path()");
+        dataset_path.join("month").join(Utc.timestamp(date.0, 0).format("%m%y").to_string())
+    }
+
+    pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
+        debug!(target: "tau", "MonthTasks::save()");
+        save::<Self>(&Self::get_path(&self.created_at, dataset_path), self)
+            .map_err(TaudError::Darkfi)
+    }
+
+    fn get_all(dataset_path: &Path) -> io::Result<Vec<PathBuf>> {
+        debug!(target: "tau", "MonthTasks::get_all()");
+
+        let mut entries = fs::read_dir(dataset_path.join("month"))?
+            .map(|res| res.map(|e| e.path()))
+            .collect::<Result<Vec<_>, io::Error>>()?;
+
+        entries.sort();
+
+        Ok(entries)
+    }
+
+    fn create(date: &Timestamp, dataset_path: &Path) -> TaudResult<Self> {
+        debug!(target: "tau", "MonthTasks::create()");
+
+        let mut mt = Self::new(&[]);
+        mt.set_date(date);
+        mt.save(dataset_path)?;
+        Ok(mt)
+    }
+
+    pub fn load_or_create(date: Option<&Timestamp>, dataset_path: &Path) -> TaudResult<Self> {
+        debug!(target: "tau", "MonthTasks::load_or_create()");
+
+        // if a date is given we load that date's month tasks
+        // if not, we load tasks from all months
+        match date {
+            Some(date) => match load::<Self>(&Self::get_path(date, dataset_path)) {
+                Ok(mt) => Ok(mt),
+                Err(_) => Self::create(date, dataset_path),
+            },
+            None => {
+                let path_all = match Self::get_all(dataset_path) {
+                    Ok(t) => t,
+                    Err(_) => vec![],
+                };
+
+                let mut loaded_mt = Self::new(&[]);
+
+                for path in path_all {
+                    let mt = load::<Self>(&path)?;
+                    loaded_mt.created_at = mt.created_at;
+                    for tks in mt.task_tks {
+                        if !loaded_mt.task_tks.contains(&tks) {
+                            loaded_mt.task_tks.push(tks)
+                        }
+                    }
+                }
+                Ok(loaded_mt)
+            }
+        }
+    }
+
+    pub fn load_current_open_tasks(dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
+        let mt = Self::load_or_create(None, dataset_path)?;
+        Ok(mt.objects(dataset_path)?.into_iter().filter(|t| t.get_state() != "stop").collect())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::{
+        fs::{create_dir_all, remove_dir_all},
+        path::PathBuf,
+    };
+
+    use super::*;
+    use darkfi::Result;
+
+    const TEST_DATA_PATH: &str = "/tmp/test_tau_data";
+
+    fn get_path() -> Result<PathBuf> {
+        remove_dir_all(TEST_DATA_PATH).ok();
+
+        let path = PathBuf::from(TEST_DATA_PATH);
+
+        // mkdir dataset_path if not exists
+        create_dir_all(path.join("month"))?;
+        create_dir_all(path.join("task"))?;
+        Ok(path)
+    }
+
+    #[test]
+    fn load_and_save_tasks() -> TaudResult<()> {
+        let dataset_path = get_path()?;
+
+        // load and save TaskInfo
+        ///////////////////////
+
+        let mut task =
+            TaskInfo::new("test_title", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
+
+        task.save(&dataset_path)?;
+
+        let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
+
+        assert_eq!(task, t_load);
+
+        task.set_title("test_title_2");
+
+        task.save(&dataset_path)?;
+
+        let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
+
+        assert_eq!(task, t_load);
+
+        // load and save MonthTasks
+        ///////////////////////
+
+        let task_tks = vec![];
+
+        let mut mt = MonthTasks::new(&task_tks);
+
+        mt.save(&dataset_path)?;
+
+        let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
+
+        assert_eq!(mt, mt_load);
+
+        mt.add(&task.ref_id);
+
+        mt.save(&dataset_path)?;
+
+        let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
+
+        assert_eq!(mt, mt_load);
+
+        // activate task
+        ///////////////////////
+
+        let task =
+            TaskInfo::new("test_title_3", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
+
+        task.save(&dataset_path)?;
+
+        let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
+
+        assert!(mt_load.task_tks.contains(&task.ref_id));
+
+        remove_dir_all(TEST_DATA_PATH).ok();
+
+        Ok(())
+    }
+}

+ 274 - 0
script/research/raft-tool/src/task_info.rs

@@ -0,0 +1,274 @@
+use std::{
+    io,
+    path::{Path, PathBuf},
+};
+
+use log::debug;
+use serde::{Deserialize, Serialize};
+
+use darkfi::util::{
+    serial::{Decodable, Encodable, SerialDecodable, SerialEncodable, VarInt},
+    Timestamp,
+};
+
+use crate::{
+    error::{TaudError, TaudResult},
+    month_tasks::MonthTasks,
+    util::{find_free_id, load, random_ref_id, save},
+};
+
+#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
+struct TaskEvent {
+    action: String,
+    timestamp: Timestamp,
+}
+
+impl TaskEvent {
+    fn new(action: String) -> Self {
+        Self { action, timestamp: Timestamp::current_time() }
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, SerialDecodable, SerialEncodable, PartialEq)]
+pub struct Comment {
+    content: String,
+    author: String,
+    timestamp: Timestamp,
+}
+
+impl Comment {
+    pub fn new(content: &str, author: &str) -> Self {
+        Self {
+            content: content.into(),
+            author: author.into(),
+            timestamp: Timestamp::current_time(),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TaskEvents(Vec<TaskEvent>);
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TaskComments(Vec<Comment>);
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TaskProjects(Vec<String>);
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct TaskAssigns(Vec<String>);
+
+#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
+pub struct TaskInfo {
+    pub(crate) ref_id: String,
+    id: u32,
+    title: String,
+    desc: String,
+    owner: String,
+    assign: TaskAssigns,
+    project: TaskProjects,
+    due: Option<Timestamp>,
+    rank: f32,
+    created_at: Timestamp,
+    events: TaskEvents,
+    comments: TaskComments,
+}
+
+impl TaskInfo {
+    pub fn new(
+        title: &str,
+        desc: &str,
+        owner: &str,
+        due: Option<Timestamp>,
+        rank: f32,
+        dataset_path: &Path,
+    ) -> TaudResult<Self> {
+        // generate ref_id
+        let ref_id = random_ref_id();
+
+        let created_at = Timestamp::current_time();
+
+        let task_ids: Vec<u32> =
+            MonthTasks::load_current_open_tasks(dataset_path)?.into_iter().map(|t| t.id).collect();
+
+        let id: u32 = find_free_id(&task_ids);
+
+        if let Some(d) = &due {
+            if *d < Timestamp::current_time() {
+                return Err(TaudError::InvalidDueTime)
+            }
+        }
+
+        Ok(Self {
+            ref_id,
+            id,
+            title: title.into(),
+            desc: desc.into(),
+            owner: owner.into(),
+            assign: TaskAssigns(vec![]),
+            project: TaskProjects(vec![]),
+            due,
+            rank,
+            created_at,
+            comments: TaskComments(vec![]),
+            events: TaskEvents(vec![]),
+        })
+    }
+
+    pub fn load(ref_id: &str, dataset_path: &Path) -> TaudResult<Self> {
+        debug!(target: "tau", "TaskInfo::load()");
+        let task = load::<Self>(&Self::get_path(ref_id, dataset_path))?;
+        Ok(task)
+    }
+
+    pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
+        debug!(target: "tau", "TaskInfo::save()");
+        save::<Self>(&Self::get_path(&self.ref_id, dataset_path), self)
+            .map_err(TaudError::Darkfi)?;
+
+        if self.get_state() == "stop" {
+            self.deactivate(dataset_path)?;
+        } else {
+            self.activate(dataset_path)?;
+        }
+
+        Ok(())
+    }
+
+    pub fn activate(&self, path: &Path) -> TaudResult<()> {
+        debug!(target: "tau", "TaskInfo::activate()");
+        let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?;
+        mt.add(&self.ref_id);
+        mt.save(path)
+    }
+
+    pub fn deactivate(&self, path: &Path) -> TaudResult<()> {
+        debug!(target: "tau", "TaskInfo::deactivate()");
+        let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?;
+        mt.remove(&self.ref_id);
+        mt.save(path)
+    }
+
+    pub fn get_state(&self) -> String {
+        debug!(target: "tau", "TaskInfo::get_state()");
+        if let Some(ev) = self.events.0.last() {
+            ev.action.clone()
+        } else {
+            "open".into()
+        }
+    }
+
+    fn get_path(ref_id: &str, dataset_path: &Path) -> PathBuf {
+        debug!(target: "tau", "TaskInfo::get_path()");
+        dataset_path.join("task").join(ref_id)
+    }
+
+    pub fn get_id(&self) -> u32 {
+        debug!(target: "tau", "TaskInfo::get_id()");
+        self.id
+    }
+
+    pub fn set_title(&mut self, title: &str) {
+        debug!(target: "tau", "TaskInfo::set_title()");
+        self.title = title.into();
+    }
+
+    pub fn set_desc(&mut self, desc: &str) {
+        debug!(target: "tau", "TaskInfo::set_desc()");
+        self.desc = desc.into();
+    }
+
+    pub fn set_assign(&mut self, assign: &[String]) {
+        debug!(target: "tau", "TaskInfo::set_assign()");
+        self.assign = TaskAssigns(assign.to_owned());
+    }
+
+    pub fn set_project(&mut self, project: &[String]) {
+        debug!(target: "tau", "TaskInfo::set_project()");
+        self.project = TaskProjects(project.to_owned());
+    }
+
+    pub fn set_comment(&mut self, c: Comment) {
+        debug!(target: "tau", "TaskInfo::set_comment()");
+        self.comments.0.push(c);
+    }
+
+    pub fn set_rank(&mut self, r: f32) {
+        debug!(target: "tau", "TaskInfo::set_rank()");
+        self.rank = r;
+    }
+
+    pub fn set_due(&mut self, d: Option<Timestamp>) {
+        debug!(target: "tau", "TaskInfo::set_due()");
+        self.due = d;
+    }
+
+    pub fn set_state(&mut self, action: &str) {
+        debug!(target: "tau", "TaskInfo::set_state()");
+        if self.get_state() == action {
+            return
+        }
+        self.events.0.push(TaskEvent::new(action.into()));
+    }
+}
+
+impl Encodable for TaskEvents {
+    fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
+        encode_vec(&self.0, s)
+    }
+}
+
+impl Decodable for TaskEvents {
+    fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
+        Ok(Self(decode_vec(d)?))
+    }
+}
+impl Encodable for TaskComments {
+    fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
+        encode_vec(&self.0, s)
+    }
+}
+
+impl Decodable for TaskComments {
+    fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
+        Ok(Self(decode_vec(d)?))
+    }
+}
+impl Encodable for TaskProjects {
+    fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
+        encode_vec(&self.0, s)
+    }
+}
+
+impl Decodable for TaskProjects {
+    fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
+        Ok(Self(decode_vec(d)?))
+    }
+}
+
+impl Encodable for TaskAssigns {
+    fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
+        encode_vec(&self.0, s)
+    }
+}
+
+impl Decodable for TaskAssigns {
+    fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
+        Ok(Self(decode_vec(d)?))
+    }
+}
+
+fn encode_vec<T: Encodable, S: io::Write>(vec: &[T], mut s: S) -> darkfi::Result<usize> {
+    let mut len = 0;
+    len += VarInt(vec.len() as u64).encode(&mut s)?;
+    for c in vec.iter() {
+        len += c.encode(&mut s)?;
+    }
+    Ok(len)
+}
+
+fn decode_vec<T: Decodable, D: io::Read>(mut d: D) -> darkfi::Result<Vec<T>> {
+    let len = VarInt::decode(&mut d)?.0;
+    let mut ret = Vec::with_capacity(len as usize);
+    for _ in 0..len {
+        ret.push(Decodable::decode(&mut d)?);
+    }
+    Ok(ret)
+}

+ 59 - 0
script/research/raft-tool/src/util.rs

@@ -0,0 +1,59 @@
+use std::{fs::File, io::BufReader, path::Path};
+
+use rand::{distributions::Alphanumeric, thread_rng, Rng};
+use serde::{de::DeserializeOwned, Serialize};
+
+use darkfi::Result;
+
+pub fn random_ref_id() -> String {
+    thread_rng().sample_iter(&Alphanumeric).take(30).map(char::from).collect()
+}
+
+pub fn find_free_id(task_ids: &[u32]) -> u32 {
+    for i in 1.. {
+        if !task_ids.contains(&i) {
+            return i
+        }
+    }
+    1
+}
+
+pub fn load<T: DeserializeOwned>(path: &Path) -> Result<T> {
+    let file = File::open(path)?;
+    let reader = BufReader::new(file);
+
+    let value: T = serde_json::from_reader(reader)?;
+    Ok(value)
+}
+
+pub fn save<T: Serialize>(path: &Path, value: &T) -> Result<()> {
+    let file = File::create(path)?;
+    serde_json::to_writer_pretty(file, value)?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn find_free_id_test() -> Result<()> {
+        let mut ids: Vec<u32> = vec![1, 3, 8, 9, 10, 3];
+        let ids_empty: Vec<u32> = vec![];
+        let ids_duplicate: Vec<u32> = vec![1; 100];
+
+        let find_id = find_free_id(&ids);
+
+        assert_eq!(find_id, 2);
+
+        ids.push(find_id);
+
+        assert_eq!(find_free_id(&ids), 4);
+
+        assert_eq!(find_free_id(&ids_empty), 1);
+
+        assert_eq!(find_free_id(&ids_duplicate), 2);
+
+        Ok(())
+    }
+}