diff --git a/Cargo.lock b/Cargo.lock index f67caa6d6598d38319dd92cabdb8032d63789e12..fd66718b3c6b1c8b84d9672f2c93da7e54f9c698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,6 +133,7 @@ dependencies = [ "cryptoxide", "reqwest", "scrypt", + "serde", "serde_json", ] @@ -774,6 +775,20 @@ name = "serde" version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" diff --git a/Cargo.toml b/Cargo.toml index 9ffe0e7031305eba4e2b7dd66053db28c6316c11..217a318bb415c075c88f981437b7901631c0974c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ base64 = "0.13.0" bs58 = "0.4.0" cryptoxide = "0.3.3" reqwest = { version = "0.11.4", features = ["json", "blocking"] } +serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.67" \ No newline at end of file diff --git a/src/bma/blockchain/current.rs b/src/bma/blockchain/current.rs new file mode 100644 index 0000000000000000000000000000000000000000..39e1a70d2db5cfc17b7e2fd3f9ca4a8764788c49 --- /dev/null +++ b/src/bma/blockchain/current.rs @@ -0,0 +1,33 @@ +use serde_json::Value; + +use crate::bma::BmaNode; + +pub struct Block<> { + pub version: u64, + pub currency: String, + pub number: u64, + pub hash: String +} + +impl Block { + + pub fn from_json(json: &Value) -> Block { + Block { + currency: json["currency"].as_str().unwrap().into(), + version: json["version"].as_u64().unwrap(), + number: json["number"].as_u64().unwrap(), + hash: json["hash"].as_str().unwrap().into() + } + } + + pub fn blockstamp(&self) -> String { + format!("{}-{}", self.number, self.hash) + } +} + +pub fn current(node: &BmaNode) -> Block { + let address = node.get_address(); + let resp = reqwest::blocking::get(format!("{}/blockchain/current", address)).expect("Could not fetch /blockchain/current from distant node"); + let json = &resp.json().expect("Could not parse /blockchain/current JSON response"); + Block::from_json(&json) +} \ No newline at end of file diff --git a/src/bma/blockchain/mod.rs b/src/bma/blockchain/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..30e81422593ce10a3c85b854914782ac9ff723f4 --- /dev/null +++ b/src/bma/blockchain/mod.rs @@ -0,0 +1,3 @@ +mod current; + +pub use current::current; \ No newline at end of file diff --git a/src/bma/lookup_identity.rs b/src/bma/lookup_identity.rs new file mode 100644 index 0000000000000000000000000000000000000000..c775a02835d55d702fead745870a74987e529e73 --- /dev/null +++ b/src/bma/lookup_identity.rs @@ -0,0 +1,51 @@ +use crate::bma::{BmaNode}; +use serde_json::Value; + +pub struct LookupResult<'a>(pub(crate) &'a serde_json::Value); + +pub struct LookupIdentity { + pub public: String, + pub uid: String, + pub blockstamp: String, + pub signature: String, + pub revoked: bool +} + +impl LookupIdentity { + pub fn new(public: &str, uid: &str, blockstamp: &str, signature: &str, revoked: bool) -> LookupIdentity { + LookupIdentity { + public: String::from(public), + uid: String::from(uid), + blockstamp: String::from(blockstamp), + signature: String::from(signature), + revoked + } + } +} + +pub fn lookup(node: BmaNode, uid_or_pub: String) -> () { + let address = node.get_address(); + let resp = reqwest::blocking::get(format!("{}/wot/lookup/{}", address, uid_or_pub)).expect("Could not fetch lookup data from distant node"); + lookup_print(&LookupResult(&resp.json().expect("Could not get JSON result from distant node"))); +} + +pub fn lookup_print(lookup_result: &LookupResult) -> () { + lookup2identities(lookup_result).iter().for_each(|i| println!("{} {} {}", i.public, i.uid, if i.revoked { "revoked" } else { "valid" })) +} + +pub fn lookup2identities(lookup_result: &LookupResult) -> Vec<LookupIdentity> { + let mut identities: Vec<LookupIdentity> = vec![]; + for result in lookup_result.0["results"].as_array().unwrap() { + let pubkey = result["pubkey"].as_str().unwrap(); + let empty: &Vec<Value> = &Vec::new(); + let uids: &Vec<Value> = result["uids"].as_array().unwrap_or(empty); + for anUid in uids.iter() { + let uid = anUid["uid"].as_str().unwrap(); + let blockstamp = anUid["meta"]["timestamp"].as_str().unwrap(); + let signature = anUid["self"].as_str().unwrap(); + let revoked = anUid["revoked"].as_bool().unwrap(); + identities.push(LookupIdentity::new(pubkey, uid, blockstamp, signature,revoked)); + } + } + identities +} \ No newline at end of file diff --git a/src/bma/mod.rs b/src/bma/mod.rs index 806b855d2e106989c71b9532e64ab63172eb9cb7..4f89f3a1b885dfeef663d382e6272db74986d135 100644 --- a/src/bma/mod.rs +++ b/src/bma/mod.rs @@ -3,21 +3,11 @@ use reqwest; use std::fmt::Display; pub use crate::bma::node::BmaNode; use serde_json::Value; +use reqwest::blocking::Response; +use crate::bma::lookup_identity::{LookupIdentity, lookup2identities, lookup_print, LookupResult}; +use std::process::id; mod node; - -pub fn lookup(node: BmaNode, uid_or_pub: String) -> () { - let address = node.get_address(); - let resp = reqwest::blocking::get(format!("{}/wot/lookup/{}", address, uid_or_pub)).expect("Could not fetch lookup data from distant node"); - let res_json: serde_json::Value = resp.json().expect("Could not get JSON result from distant node"); - for result in res_json["results"].as_array().unwrap() { - let pubkey = result["pubkey"].as_str().unwrap(); - let empty: &Vec<Value> = &Vec::new(); - let uids: &Vec<Value> = result["uids"].as_array().unwrap_or(empty); - for anUid in uids.iter() { - let uid = anUid["uid"].as_str().unwrap(); - let revoked = anUid["revoked"].as_bool().unwrap(); - println!("{} {} {}", pubkey, uid, if revoked { "revoked" } else { "valid" }) - } - } -} \ No newline at end of file +pub mod lookup_identity; +pub mod blockchain; +pub mod wot; \ No newline at end of file diff --git a/src/bma/wot/mod.rs b/src/bma/wot/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a5ec04101fd005d78083ec64ea039c2ccaf525d --- /dev/null +++ b/src/bma/wot/mod.rs @@ -0,0 +1,33 @@ +use std::fmt::{Display, Formatter}; + +use reqwest::{blocking, StatusCode}; +use serde::{Deserialize, Serialize}; + +use crate::bma::BmaNode; +use crate::bma::lookup_identity::LookupIdentity; + +#[derive(Serialize, Deserialize)] +struct BmaCertification { + cert: String +} + +impl Display for BmaCertification { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.cert.as_str()) + } +} + +pub fn certify(node: &BmaNode, idty: &String, sig: &String) -> Result<(), String> { + let mut cert = BmaCertification { cert: format!("{}{}\n", idty, sig) }; + let address = node.get_address(); + let client = reqwest::blocking::Client::new(); + let resp = client.post(format!("{}/wot/certify", address)) + .json(&cert) + .send() + .expect("Error during POST /wot/certify"); + + if resp.status() != 200 { + return Err(format!("KO status : {}, message: {}", resp.status(), resp.text().unwrap())); + } + Ok(()) +} \ No newline at end of file diff --git a/src/cli/certify.rs b/src/cli/certify.rs new file mode 100644 index 0000000000000000000000000000000000000000..c5ac251a032f501b47792fb838bd092294f741b9 --- /dev/null +++ b/src/cli/certify.rs @@ -0,0 +1,54 @@ +use crate::bma::BmaNode; +use crate::bma::lookup_identity::{LookupResult, lookup2identities, lookup_print}; +use duniter_mini_client::crypto::scrypt_duniter_key::ScryptDuniterKey; +use duniter_mini_client::crypto::duniter_key::ToDuniterKey; +use std::process::id; + +pub fn certify(node: BmaNode, uid_or_pub: String) -> Result<(), String> { + let address = node.get_address(); + println!("Fetching identity using \"{}\" pattern...", uid_or_pub); + let resp = reqwest::blocking::get(format!("{}/wot/lookup/{}", address, uid_or_pub)).expect("Could not fetch lookup data from distant node"); + let res_json = &resp.json().expect("Could not get JSON result from distant node"); + let res_json: LookupResult = LookupResult(res_json); + let results = lookup2identities(&res_json); + let idty = match results.len() { + 0 => return Err(String::from("No matching identity found")), + 1 => &results[0], + _ => { + lookup_print(&res_json); + return Err(format!("Too much identities found ({})", results.len())) + }, + }; + + if idty.revoked { + return Err(format!("Identity {} {} is revoked and cannot be certified anymore.", idty.public, idty.uid)) + } + + println!("Deriving key for signature..."); + let keyring = ScryptDuniterKey::new(String::from("fakesalt"), String::from("fakepasswd")).derive(); // TODO: get salt/passwd from file/cli + println!("Get current block for blockstamp..."); + let current = crate::bma::blockchain::current(&node); + let currency = ¤t.currency; + let issuer = keyring.get_public_base58(); + let current_blockstamp = ¤t.blockstamp(); + + let certification = format!("Version: 10 +Type: Certification +Currency: {} +Issuer: {} +IdtyIssuer: {} +IdtyUniqueID: {} +IdtyTimestamp: {} +IdtySignature: {} +CertTimestamp: {} +", currency, issuer, idty.public, idty.uid, idty.blockstamp, idty.signature, current_blockstamp); + println!("{}", certification); + println!("Sign and submit?"); + let signature = keyring.sign(&certification); + println!("Signature: {}", signature); + + println!("Submitting..."); + // crate::bma::wot::certify(&node, &idty, &issuer, ¤t_blockstamp, &signature.to_string()); + crate::bma::wot::certify(&node, &certification, &signature.to_string()); + Ok(()) +} \ No newline at end of file diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 98956430c4bbe6f8aa35e26b2b111635a12cd20d..cff0cae62074813d0abb92d9645a46fe621c8ad6 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -4,6 +4,8 @@ use crate::cli::Command::*; use crate::bma::BmaNode; use std::env; +pub(crate) mod certify; + const DEFAULT_NODE: &str = "https://g1-test.duniter.org"; pub enum Command { @@ -15,6 +17,8 @@ pub enum Command { KEYRING(String, String), /// Search an identity on a Duniter node (BMA API) LOOKUP(BmaNode, String), + /// Search an identity on a Duniter node (BMA API) and certify it + CERTIFY(BmaNode, String), /// Unknown command UNKNOWN(String) } @@ -48,6 +52,12 @@ impl Command { let bma_node = BmaNode::new(env::var("DUNITER_NODE").unwrap_or(default).as_str()); LOOKUP(bma_node, uid_or_pub) }, + "cert" => { + let uid_or_pub = args.next().expect("UID or pubkey must be provided"); + let default = String::from(DEFAULT_NODE); + let bma_node = BmaNode::new(env::var("DUNITER_NODE").unwrap_or(default).as_str()); + CERTIFY(bma_node, uid_or_pub) + }, _ => UNKNOWN(String::from(command)), } } diff --git a/src/main.rs b/src/main.rs index 5436f90797f6d73965dc896b8ea4c32e5a39f876..c8733333f5de4ae466eba3362f806352b5484c5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use duniter_mini_client::{compute_pub, compute_sec, compute_key}; use crate::cli::Command; pub use crate::cli::Command::{PUB, UNKNOWN}; -use crate::cli::Command::{SEC, KEYRING, LOOKUP}; +use crate::cli::Command::{SEC, KEYRING, LOOKUP, CERTIFY}; use duniter_mini_client::crypto::duniter_key::ScryptDuniterKey; mod cli; @@ -14,11 +14,14 @@ mod bma; fn main() { let command = Command::from(env::args()); + // TODO: Result<(), String> + match command { PUB(salt, passwd) => println!("{}", compute_pub(ScryptDuniterKey::new(salt, passwd))), SEC(salt, passwd) => println!("{}", compute_sec(ScryptDuniterKey::new(salt, passwd))), KEYRING(salt, passwd) => println!("{}", compute_key(ScryptDuniterKey::new(salt, passwd))), - LOOKUP(bma_node, uid_or_pub) => bma::lookup(bma_node, uid_or_pub), + LOOKUP(bma_node, uid_or_pub) => bma::lookup_identity::lookup(bma_node, uid_or_pub), + CERTIFY(bma_node, uid_or_pub) => cli::certify::certify(bma_node, uid_or_pub).unwrap_or_else(|e| eprintln!("{}", e)), UNKNOWN(cmd) => eprintln!("Unknown command {}", cmd), }; } \ No newline at end of file