Refactored key-management a lot.
This commit is contained in:
@@ -12,6 +12,7 @@ rust-crypto = "^0.2"
|
||||
num_cpus = "1.13.0"
|
||||
byteorder = "1.3.2"
|
||||
web-view = { version = "0.7.2", features = [] }
|
||||
tinyfiledialogs = "3.3.10"
|
||||
serde = { version = "1.0.102", features = ["derive"] }
|
||||
serde_json = "1.0.42"
|
||||
num-bigint = "0.2"
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::event::Event;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
pub struct Context {
|
||||
pub settings: Settings,
|
||||
|
||||
@@ -4,6 +4,9 @@ pub enum Event {
|
||||
MinerStopped,
|
||||
KeyGeneratorStarted,
|
||||
KeyGeneratorStopped,
|
||||
KeyCreated {path: String, public: String},
|
||||
KeyLoaded {path: String, public: String},
|
||||
KeySaved {path: String, public: String},
|
||||
NewBlockReceived,
|
||||
BlockchainChanged,
|
||||
ActionStopMining,
|
||||
|
||||
+19
-5
@@ -21,6 +21,8 @@ pub struct Keystore {
|
||||
private_key: Bytes,
|
||||
public_key: Bytes,
|
||||
#[serde(skip)]
|
||||
path: String,
|
||||
#[serde(skip)]
|
||||
seed: Vec<u8>
|
||||
}
|
||||
|
||||
@@ -30,18 +32,21 @@ impl Keystore {
|
||||
let mut rng = thread_rng();
|
||||
rng.fill(&mut buf);
|
||||
let (private, public) = keypair(&buf);
|
||||
Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), seed: Vec::from(&buf[..])}
|
||||
Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(&buf[..])}
|
||||
}
|
||||
|
||||
pub fn from_bytes(seed: &[u8]) -> Self {
|
||||
let (private, public) = keypair(&seed);
|
||||
Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), seed: Vec::from(seed)}
|
||||
Keystore {private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(seed)}
|
||||
}
|
||||
|
||||
pub fn from_file(filename: &str, _password: &str) -> Option<Self> {
|
||||
match fs::read(&Path::new(filename)) {
|
||||
let path = Path::new(filename);
|
||||
match fs::read(&path) {
|
||||
Ok(key) => {
|
||||
Some(Self::from_bytes(key.as_slice()))
|
||||
let mut keystore = Self::from_bytes(key.as_slice());
|
||||
keystore.path = path.to_str().unwrap().to_owned();
|
||||
Some(keystore)
|
||||
},
|
||||
Err(_) => {
|
||||
None
|
||||
@@ -50,11 +55,12 @@ impl Keystore {
|
||||
}
|
||||
|
||||
//TODO Implement error conditions
|
||||
pub fn save(&self, filename: &str, _password: &str) {
|
||||
pub fn save(&mut self, filename: &str, _password: &str) {
|
||||
match File::create(Path::new(filename)) {
|
||||
Ok(mut f) => {
|
||||
//TODO implement key encryption
|
||||
f.write_all(&self.seed).expect("Error saving keystore");
|
||||
self.path = filename.to_owned();
|
||||
}
|
||||
Err(_) => { println!("Error saving key file!"); }
|
||||
}
|
||||
@@ -68,6 +74,10 @@ impl Keystore {
|
||||
self.private_key.clone()
|
||||
}
|
||||
|
||||
pub fn get_path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn sign(&self, message: &[u8]) -> [u8; 64] {
|
||||
signature(message, self.private_key.data.as_slice())
|
||||
}
|
||||
@@ -120,6 +130,10 @@ impl Bytes {
|
||||
self.data.as_slice()
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
crate::utils::to_hex(&self.data)
|
||||
}
|
||||
|
||||
pub fn zero32() -> Self {
|
||||
Bytes { data: [0u8; 32].to_vec() }
|
||||
}
|
||||
|
||||
+61
-22
@@ -1,8 +1,9 @@
|
||||
#![windows_subsystem = "windows"]
|
||||
extern crate web_view;
|
||||
extern crate tinyfiledialogs as tfd;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, Ordering, AtomicUsize};
|
||||
use std::thread;
|
||||
|
||||
use rand::RngCore;
|
||||
@@ -27,7 +28,10 @@ fn main() {
|
||||
println!("ALFIS 0.1.0");
|
||||
let settings = Settings::load(SETTINGS_FILENAME).expect("Error loading settings");
|
||||
let keystore: Keystore = match Keystore::from_file(&settings.key_file, "") {
|
||||
None => { generate_key(KEYSTORE_DIFFICULTY, Arc::new(AtomicBool::new(true))).expect("Could not load or generate keypair") }
|
||||
None => {
|
||||
println!("Generated temporary keystore. Please, generate full-privileged keys.");
|
||||
Keystore::new()
|
||||
}
|
||||
Some(keystore) => { keystore }
|
||||
};
|
||||
let blockchain: Blockchain = Blockchain::new(&settings);
|
||||
@@ -81,36 +85,66 @@ fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
|
||||
Loaded => {
|
||||
web_view.eval("showMiningIndicator(false);").expect("Error evaluating!");
|
||||
let handle = web_view.handle();
|
||||
let context_copy = context.clone();
|
||||
let mut c = context.lock().unwrap();
|
||||
c.bus.register(move |_uuid, e| {
|
||||
println!("Got event from bus {:?}", &e);
|
||||
let visible = match e {
|
||||
Event::MinerStarted => { true }
|
||||
Event::KeyGeneratorStarted => { true }
|
||||
Event::MinerStopped => { false }
|
||||
Event::KeyGeneratorStopped => { false }
|
||||
_ => { false }
|
||||
let eval = match e {
|
||||
Event::KeyCreated { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) }
|
||||
Event::KeyLoaded { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) }
|
||||
Event::KeySaved { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) }
|
||||
Event::MinerStarted => { format!("showMiningIndicator({});", true) }
|
||||
Event::KeyGeneratorStarted => { format!("showMiningIndicator({});", true) }
|
||||
Event::MinerStopped => { format!("showMiningIndicator({});", false) }
|
||||
Event::KeyGeneratorStopped => { format!("showMiningIndicator({});", false) }
|
||||
_ => { String::new() }
|
||||
};
|
||||
|
||||
if !eval.is_empty() {
|
||||
println!("Evaluating {}", &eval);
|
||||
handle.dispatch(move |web_view| {
|
||||
web_view.eval(&format!("showMiningIndicator({});", visible)).expect("Error evaluating!");
|
||||
web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!");
|
||||
return WVResult::Ok(());
|
||||
}).expect("Error dispatching!");
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
LoadKey { name, pass } => {
|
||||
match Keystore::from_file(&name, &pass) {
|
||||
LoadKey {} => {
|
||||
let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key"], "*.key")));
|
||||
match result {
|
||||
None => {}
|
||||
Some(file_name) => {
|
||||
match Keystore::from_file(&file_name, "") {
|
||||
None => {
|
||||
println!("Error loading keystore '{}'!", &name);
|
||||
println!("Error loading keystore '{}'!", &file_name);
|
||||
},
|
||||
Some(keystore) => {
|
||||
println!("Loaded keystore with key: {:?}", &keystore.get_public());
|
||||
let mut c = context.lock().unwrap();
|
||||
c.bus.post(Event::KeyLoaded {path: keystore.get_path().to_owned(), public: keystore.get_public().to_string()});
|
||||
c.set_keystore(keystore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
CreateKey { name, pass } => {
|
||||
create_key(context.clone(), &name, &pass);
|
||||
CreateKey {} => {
|
||||
create_key(context.clone());
|
||||
}
|
||||
SaveKey {} => {
|
||||
let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)");
|
||||
match result {
|
||||
None => {}
|
||||
Some(new_path) => {
|
||||
let mut c = context.lock().unwrap();
|
||||
let path = new_path.clone();
|
||||
let public = c.keystore.get_public().to_string();
|
||||
c.keystore.save(&new_path, "");
|
||||
println!("Key file saved to {}", &path);
|
||||
c.bus.post(Event::KeySaved {path, public });
|
||||
}
|
||||
}
|
||||
}
|
||||
CheckDomain { name} => {
|
||||
let c = context.lock().unwrap();
|
||||
@@ -170,28 +204,32 @@ fn create_transaction<S: Into<String>>(keystore: &Keystore, name: S, method: S,
|
||||
transaction
|
||||
}
|
||||
|
||||
fn create_key(context: Arc<Mutex<Context>>, filename: &str, password: &str) {
|
||||
fn create_key(context: Arc<Mutex<Context>>) {
|
||||
let mining = Arc::new(AtomicBool::new(true));
|
||||
let miners_count = Arc::new(AtomicUsize::new(0));
|
||||
{ context.lock().unwrap().bus.post(Event::KeyGeneratorStarted); }
|
||||
for _ in 0..num_cpus::get() {
|
||||
let context = context.clone();
|
||||
let filename= filename.to_owned();
|
||||
let password= password.to_owned();
|
||||
let mining = mining.clone();
|
||||
let miners_count = miners_count.clone();
|
||||
thread::spawn(move || {
|
||||
miners_count.fetch_add(1, Ordering::Relaxed);
|
||||
match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) {
|
||||
None => {
|
||||
println!("Keystore mining finished");
|
||||
context.lock().unwrap().bus.post(Event::KeyGeneratorStopped);
|
||||
}
|
||||
Some(keystore) => {
|
||||
println!("Key mined successfully: {:?}", &keystore.get_public());
|
||||
let mut c = context.lock().unwrap();
|
||||
mining.store(false,Ordering::Relaxed);
|
||||
keystore.save(&filename, &password);
|
||||
c.bus.post(Event::KeyCreated {path: keystore.get_path().to_owned(), public: keystore.get_public().to_string()});
|
||||
c.set_keystore(keystore);
|
||||
c.bus.post(Event::KeyGeneratorStopped);
|
||||
}
|
||||
}
|
||||
let miners = miners_count.fetch_sub(1, Ordering::Relaxed) - 1;
|
||||
if miners == 0 {
|
||||
context.lock().unwrap().bus.post(Event::KeyGeneratorStopped);
|
||||
}
|
||||
});
|
||||
}
|
||||
context.lock().unwrap().bus.register(move |_uuid, e| {
|
||||
@@ -222,8 +260,9 @@ fn generate_key(difficulty: usize, mining: Arc<AtomicBool>) -> Option<Keystore>
|
||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
||||
pub enum Cmd {
|
||||
Loaded,
|
||||
LoadKey{name: String, pass: String},
|
||||
CreateKey{name: String, pass: String},
|
||||
LoadKey{},
|
||||
CreateKey{},
|
||||
SaveKey{},
|
||||
CheckDomain{name: String},
|
||||
CreateDomain{name: String, records: String, tags: String},
|
||||
ChangeDomain{name: String, records: String, tags: String},
|
||||
|
||||
@@ -10,6 +10,7 @@ use num_cpus;
|
||||
|
||||
use crate::{Block, Bytes, Context, hash_is_good, Transaction};
|
||||
use crate::event::Event;
|
||||
use std::ops::DerefMut;
|
||||
|
||||
pub struct Miner {
|
||||
context: Arc<Mutex<Context>>,
|
||||
|
||||
@@ -13,6 +13,7 @@ use mio::net::{TcpListener, TcpStream};
|
||||
|
||||
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers};
|
||||
use std::net::{SocketAddr, IpAddr, SocketAddrV4, Shutdown};
|
||||
use std::ops::DerefMut;
|
||||
|
||||
const SERVER: Token = Token(0);
|
||||
const POLL_TIMEOUT: Option<Duration> = Some(Duration::from_millis(3000));
|
||||
|
||||
+15
-77
@@ -37,9 +37,7 @@
|
||||
</ul>
|
||||
<p class="menu-label">Key management</p>
|
||||
<ul class="menu-list">
|
||||
<li><a onclick="openTab(this, 'key_load')">Load key</a></li>
|
||||
<li><a onclick="openTab(this, 'key_create')">Create key</a></li>
|
||||
<li><a onclick="openTab(this, 'key_passwd')">Change password</a></li>
|
||||
<li><a onclick="openTab(this, 'key_load')">Manage keys</a></li>
|
||||
</ul>
|
||||
<p class="menu-label">Domain management</p>
|
||||
<ul class="menu-list">
|
||||
@@ -60,93 +58,33 @@
|
||||
<div class="content is-hidden" id="key_load">
|
||||
<form action="#">
|
||||
<div class="field">
|
||||
<label class="label">Key file name</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="default.key" id="load_key_name">
|
||||
</div>
|
||||
<label class="label">Key path</label>
|
||||
<p id="key_file_name">Key not saved</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Key password</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" placeholder="123456" id="load_key_password">
|
||||
</div>
|
||||
<label class="label">Key public key</label>
|
||||
<p id="key_public_key">00000050C9516C42946D38EDD6A2DB1279F6E73E9C6D2EA6769A5C2BA3E381D6</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link" onclick="loadKey();">Load</button>
|
||||
<button class="button is-success" onclick="loadKey();">Load key</button>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button class="button is-warning" onclick="createKey();">Mine new key</button>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button class="button is-primary" onclick="saveKey();">Save key</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="content is-hidden" id="key_create">
|
||||
<form action="#">
|
||||
<div class="field">
|
||||
<label class="label">Key file name</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="default.key" id="create_key_name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Key password</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" placeholder="123456" id="create_key_password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link" onclick="createKey();">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="content is-hidden" id="key_passwd">
|
||||
<form action="#">
|
||||
<div class="field">
|
||||
<label class="label">Key file name</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="default.key" id="change_key_name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Old key password</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" placeholder="123456" id="change_key_password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">New key password</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" placeholder="123456" id="change_key_password_new">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Repeat key password</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" placeholder="123456" id="change_key_password_repeat">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link">Change password</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="content is-hidden" id="dom_new">
|
||||
<form action="#">
|
||||
<div class="field">
|
||||
|
||||
+16
-6
@@ -24,15 +24,15 @@ function openTab(element, tabName) {
|
||||
}
|
||||
|
||||
function loadKey() {
|
||||
key_name = document.getElementById("load_key_name").value;
|
||||
key_pass = document.getElementById("load_key_password").value;
|
||||
external.invoke(JSON.stringify({cmd: 'loadKey', name: key_name, pass: key_pass}));
|
||||
external.invoke(JSON.stringify({cmd: 'loadKey'}));
|
||||
}
|
||||
|
||||
function createKey() {
|
||||
key_name = document.getElementById("create_key_name").value;
|
||||
key_pass = document.getElementById("create_key_password").value;
|
||||
external.invoke(JSON.stringify({cmd: 'createKey', name: key_name, pass: key_pass}));
|
||||
external.invoke(JSON.stringify({cmd: 'createKey'}));
|
||||
}
|
||||
|
||||
function saveKey() {
|
||||
external.invoke(JSON.stringify({cmd: 'saveKey'}));
|
||||
}
|
||||
|
||||
function createDomain() {
|
||||
@@ -116,3 +116,13 @@ function miningIndicatorClick(element) {
|
||||
external.invoke(JSON.stringify({cmd: 'stopMining'}));
|
||||
});
|
||||
}
|
||||
|
||||
function keystoreChanged(path, pub_key) {
|
||||
if (path == '') {
|
||||
path = "In memory";
|
||||
}
|
||||
key_file_name = document.getElementById("key_file_name");
|
||||
key_file_name.innerHTML = path;
|
||||
key_file_key = document.getElementById("key_public_key");
|
||||
key_file_key.innerHTML = pub_key;
|
||||
}
|
||||
Reference in New Issue
Block a user