diff --git a/Cargo.toml b/Cargo.toml index 87c0389..161ad3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kuska-ssb" -version = "0.1.2" +version = "0.1.3" authors = ["Dhole ", "Adria Massanet "] edition = "2018" @@ -9,7 +9,6 @@ name = "kuska_ssb" [dependencies] kuska-handshake = { git = "https://github.com/Kuska-ssb/kuska-handshake", branch = "master" , features=["sync","async_std"] } - sodiumoxide = { git = "https://github.com/Dhole/sodiumoxidez", branch = "extra" } base64 = "0.11.0" hex = "0.4.0" @@ -22,4 +21,10 @@ serde_json = { version = "1.0.48", features=["preserve_order","arbitrary_precisi dirs = "2.0" futures = "0.3.4" lazy_static = "1.4.0" -rand = "0.7.3" \ No newline at end of file +rand = "0.7.3" + +[[example]] +name = "ssb-cli" + +[dev-dependencies] +structopt = "0.3.9" diff --git a/examples/ssb-cli.rs b/examples/ssb-cli.rs index 6df35f2..87b4f5f 100644 --- a/examples/ssb-cli.rs +++ b/examples/ssb-cli.rs @@ -3,8 +3,10 @@ extern crate kuska_ssb; extern crate base64; extern crate crossbeam; +extern crate structopt; use std::fmt::Debug; +use structopt::StructOpt; use async_std::io::{Read, Write}; use async_std::net::TcpStream; @@ -13,6 +15,7 @@ use kuska_handshake::async_std::{handshake_client, BoxStream}; use kuska_ssb::api::{ ApiHelper, CreateHistoryStreamArgs, CreateStreamArgs, LatestUserMessage, WhoAmI, }; +use kuska_ssb::crypto::ToSodiumObject; use kuska_ssb::discovery::ssb_net_id; use kuska_ssb::feed::{is_privatebox, privatebox_decipher, Feed, Message}; use kuska_ssb::keystore::from_patchwork_local; @@ -21,6 +24,15 @@ use kuska_ssb::rpc::{RecvMsg, RequestNo, RpcStream}; type AnyResult = std::result::Result>; +#[derive(Debug, StructOpt)] +#[structopt(name = "example", about = "An example of StructOpt usage.")] +struct Opt { + /// Connect to server + // format is: server:port: + #[structopt(short, long)] + connect: String, +} + pub fn whoami_res_parse(body: &[u8]) -> AnyResult { Ok(serde_json::from_slice(body)?) } @@ -120,10 +132,19 @@ async fn main() -> AnyResult<()> { env_logger::init(); log::set_max_level(log::LevelFilter::max()); - let OwnedIdentity { pk, sk, .. } = from_patchwork_local().expect("read local secret"); + let OwnedIdentity { pk, sk, id } = from_patchwork_local().await.expect("read local secret"); + println!("connecting with identity {}", id); - let mut socket = TcpStream::connect("127.0.0.1:8080").await?; - let handshake = handshake_client(&mut socket, ssb_net_id(), pk, sk.clone(), pk).await?; + let opt = Opt::from_args(); + let connect: Vec<_> = opt.connect.split(":").collect(); + if connect.len() != 3 { + panic!("connection string should be server:port:id"); + } + let server_pk = connect[2][1..].to_ed25519_pk()?; + + let mut socket = TcpStream::connect(format!("{}:{}", connect[0], connect[1])).await?; + + let handshake = handshake_client(&mut socket, ssb_net_id(), pk, sk.clone(), server_pk).await?; println!("💃 handshake complete"); diff --git a/src/crypto/sodium.rs b/src/crypto/sodium.rs index 4202e50..590d22f 100644 --- a/src/crypto/sodium.rs +++ b/src/crypto/sodium.rs @@ -26,6 +26,12 @@ impl<'a> ToSsbId for ed25519::PublicKey { } } +impl<'a> ToSsbId for ed25519::SecretKey { + fn to_ssb_id(&self) -> String { + format!("{}{}", base64::encode(self), CURVE_ED25519_SUFFIX) + } +} + impl ToSodiumObject for str { fn to_ed25519_pk(self: &str) -> Result { if !self.ends_with(CURVE_ED25519_SUFFIX) { diff --git a/src/keystore/error.rs b/src/keystore/error.rs index 7f71ead..4566904 100644 --- a/src/keystore/error.rs +++ b/src/keystore/error.rs @@ -2,8 +2,9 @@ pub enum Error { HomeNotFound, InvalidConfig, + Serde(serde_json::Error), CryptoFormat(crate::crypto::Error), - Io(std::io::Error), + SyncIo(std::io::Error), } impl From for Error { fn from(err: crate::crypto::Error) -> Self { @@ -13,7 +14,13 @@ impl From for Error { impl From for Error { fn from(err: std::io::Error) -> Self { - Error::Io(err) + Error::SyncIo(err) + } +} + +impl From for Error { + fn from(err: serde_json::Error) -> Self { + Error::Serde(err) } } diff --git a/src/keystore/mod.rs b/src/keystore/mod.rs index 6d3629f..82269ee 100644 --- a/src/keystore/mod.rs +++ b/src/keystore/mod.rs @@ -3,4 +3,4 @@ mod identity; pub mod patchwork; pub use identity::OwnedIdentity; -pub use patchwork::{from_patchwork_config, from_patchwork_local}; +pub use patchwork::{from_patchwork_local, read_patchwork_config, write_patchwork_config}; diff --git a/src/keystore/patchwork.rs b/src/keystore/patchwork.rs index 51cac70..780bf17 100644 --- a/src/keystore/patchwork.rs +++ b/src/keystore/patchwork.rs @@ -1,14 +1,16 @@ -use std::io; -use std::string::ToString; +use async_std::io::{Read, Write}; +use async_std::prelude::*; -use crate::crypto::ToSodiumObject; +use std::string::ToString; use super::error::{Error, Result}; use super::OwnedIdentity; +use crate::crypto::{ToSodiumObject, ToSsbId}; +use serde_json::to_vec_pretty; pub const CURVE_ED25519: &str = "ed25519"; -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] struct JsonSSBSecret { id: String, curve: String, @@ -16,28 +18,29 @@ struct JsonSSBSecret { private: String, } -fn to_ioerr(err: T) -> io::Error { - io::Error::new(io::ErrorKind::Other, err.to_string()) +fn to_io_error(err: T) -> async_std::io::Error { + async_std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) } -pub fn from_patchwork_local() -> Result { +pub async fn from_patchwork_local() -> Result { let home_dir = dirs::home_dir().ok_or(Error::HomeNotFound)?; let local_key_file = format!("{}/.ssb/secret", home_dir.to_string_lossy()); - let content = std::fs::read_to_string(local_key_file)?; - Ok(from_patchwork_config(content)?) + let mut file = async_std::fs::File::open(local_key_file).await?; + read_patchwork_config(&mut file).await } -pub fn from_patchwork_config>(config: T) -> Result { - // strip all comments - let json = config - .as_ref() +pub async fn read_patchwork_config(reader: &mut R) -> Result { + let mut buf = String::new(); + reader.read_to_string(&mut buf).await?; + + let json = buf .lines() .filter(|line| !line.starts_with('#')) .collect::>() .join(""); // parse json - let secret: JsonSSBSecret = serde_json::from_str(json.as_ref()).map_err(to_ioerr)?; + let secret: JsonSSBSecret = serde_json::from_str(json.as_ref()).map_err(to_io_error)?; if secret.curve != CURVE_ED25519 { return Err(Error::InvalidConfig); @@ -49,3 +52,17 @@ pub fn from_patchwork_config>(config: T) -> Result sk: secret.private.to_ed25519_sk()?, }) } + +pub async fn write_patchwork_config( + id: &OwnedIdentity, + writer: &mut W, +) -> Result<()> { + let json = JsonSSBSecret { + id: id.id.clone(), + curve: CURVE_ED25519.to_owned(), + public: id.pk.to_ssb_id(), + private: id.sk.to_ssb_id(), + }; + let encoded = to_vec_pretty(&json)?; + Ok(writer.write_all(&encoded).await?) +}