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

This commit is contained in:
ghassmo
2022-06-15 17:03:12 +03:00
parent 0f875e8719
commit 385d447757
6 changed files with 654 additions and 4 deletions

View File

@@ -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]

View File

@@ -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()
}
},
}
}

View File

@@ -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)
}

View File

@@ -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(())
}
}

View File

@@ -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)
}

View File

@@ -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(())
}
}