From 33b7dc5d8e76a6e8e762b003157e7dfe6062ec09 Mon Sep 17 00:00:00 2001 From: waalge Date: Sun, 26 Jan 2025 19:03:02 +0000 Subject: [PATCH] janky but runs --- Cargo.toml | 14 +-- README.md | 18 ++++ app/{cli.rs => admin.rs} | 24 +++-- app/client.rs | 159 +++++++++++++++++++++++++++++++++ app/server.rs | 184 ++++++++++++++++++--------------------- src/arr.rs | 8 ++ src/db.rs | 50 ++++++++++- src/keys.rs | 16 ++++ src/lib.rs | 4 +- src/protocol.rs | 42 +++++++++ 10 files changed, 400 insertions(+), 119 deletions(-) rename app/{cli.rs => admin.rs} (89%) create mode 100644 app/client.rs create mode 100644 src/arr.rs create mode 100644 src/protocol.rs diff --git a/Cargo.toml b/Cargo.toml index ae092aa..cb323cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,17 @@ publish = false [package.metadata.release] release = false -[[bin]] # Bin to run the server +[[bin]] # Admin +name = "admin" +path = "app/admin.rs" + +[[bin]] # Server name = "server" path = "app/server.rs" -# -[[bin]] # Bin to run the server -name = "cli" -path = "app/cli.rs" + +[[bin]] # Client +name = "client" +path = "app/client.rs" [dependencies] anyhow = "1.0.95" diff --git a/README.md b/README.md index 0605a2d..00be3ad 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,24 @@ establish our key dependencies. This repo use nix flakes with a shell available. Otherwise ymmv. +Build: + +```sh +cargo build +``` + +Run binary `x`: + +```sh +cargo run --bin +``` + +There are three binaries: + +- `admin` - admin controls: create, add, revoke ephemeral keys +- `server` - run the signing server +- `client` - run a client + ## Context CL depends on the use on signing/verification key pair cryptography. The signing diff --git a/app/cli.rs b/app/admin.rs similarity index 89% rename from app/cli.rs rename to app/admin.rs index 2d088be..52ead95 100644 --- a/app/cli.rs +++ b/app/admin.rs @@ -1,10 +1,10 @@ -use std::env; - use clap::{Parser, Subcommand}; use ed25519_dalek::{Signature, SigningKey}; -use sqlx::sqlite::SqlitePool; -use cll2v0::{db, keys}; +use cll2v0::{ + db, + keys::{self}, +}; /// cll2v0 is a playground for rust libraries. /// This is signing service. @@ -46,15 +46,7 @@ enum Command { async fn main() -> anyhow::Result<()> { let args = Args::parse(); - let keys_path = env::var("CLL2V0_KEYS").expect("Expect `CLL2V0_KEYS` to be set"); - let keychain = keys::mk_keychain(&keys_path); - - let db_url = env::var("DATABASE_URL").expect("Expect `DATABASE_URL` to be set"); - let pool = SqlitePool::connect(&db_url).await?; - for vkey_bytes in keychain.keys() { - let vkey = keys::from_bytes(vkey_bytes.into())?; - let _ = db::add_persistent_key(&pool, &vkey).await; - } + let (_, pool) = db::start_db().await?; match args.cmd { Some(Command::Add { @@ -78,7 +70,11 @@ async fn main() -> anyhow::Result<()> { let res = db::list_persistent_keys(&pool).await?; println!("Persistent keys:"); res.into_iter() - .for_each(|key| println!("{}", keys::to_hex(&key))) + .for_each(|key| println!("{}", keys::to_hex(&key))); + let res = db::list_ephemeral_keys(&pool).await?; + println!("ephemeral keys:"); + res.into_iter() + .for_each(|(e, p, t)| println!("{} {} {t}", keys::to_hex(&e), keys::to_hex(&p))) } Some(Command::Gen { seed }) => { let ekey = SigningKey::from_bytes(&[ diff --git a/app/client.rs b/app/client.rs new file mode 100644 index 0000000..937ead9 --- /dev/null +++ b/app/client.rs @@ -0,0 +1,159 @@ +use ed25519_dalek::ed25519::signature::SignerMut; +use std::error::Error; +use tokio::{io, io::AsyncBufReadExt, select}; + +use futures::prelude::*; +use libp2p::{ + mdns, + request_response::{self, ProtocolSupport}, + swarm::SwarmEvent, + Multiaddr, +}; +use tracing_subscriber::EnvFilter; + +use cll2v0::{ + keys, + messages::MyRequest, + protocol::{mk_swarm, MyBehaviourEvent}, +}; + +use clap::{Parser, Subcommand}; + +/// cll2v0 is a playground for rust libraries. +/// This is signing service. +#[derive(Parser)] +#[command(arg_required_else_help(true), version, about)] +struct Args { + #[command(subcommand)] + cmd: Option, +} + +#[derive(Subcommand)] +enum Command { + /// Start server + Start(ClientParams), +} + +#[derive(clap::Args, Debug)] +pub struct ClientParams { + /// Root of the multiaddr + #[arg( + long, + default_value = "/ip4/0.0.0.0/tcp/52321/p2p/12D3KooWPADMrTD3njNGBwyiqFEaDSarki8bQJdiFtofo8sGXs1o" + )] + pub server_addr: String, + // pub listen_on : String, + /// If used in prod, this should be an envvar. + #[arg( + long, + default_value = "0000000000000000000000000000000000000000000000000000000000000000" + )] + pub skey: String, + // FIXME :: rust does not like this option /// If turn trace on + // pub trace: bool, +} + +fn main() -> Result<(), Box> { + match Args::parse().cmd { + Some(Command::Start(sp)) => tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(start(sp)), + _ => { + panic!("oops") + } + } +} + +pub async fn start(params: ClientParams) -> Result<(), Box> { + println!("params {:?}", params); + let ClientParams { + server_addr, + skey: skey_hex, + } = params; + if true { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + } + + let key = keys::signing_key_from_hex(&skey_hex)?; + println!("VERIFYING : {}", keys::to_hex(&key.verifying_key())); + + let keypair = keys::libp2p_kp_from_hex(skey_hex)?; + + let mut swarm = mk_swarm(&keypair.clone().into(), ProtocolSupport::Outbound)?; + swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + + // Read full lines from stdin + let mut stdin = io::BufReader::new(io::stdin()).lines(); + + let server_addr: libp2p::Multiaddr = server_addr.parse()?; + let server_peer_id = match server_addr.clone().pop() { + Some(libp2p::multiaddr::Protocol::P2p(peer_id)) => peer_id, + _ => panic!("must end in peer"), + }; + println!("server addr : {:?}, {:?}", server_addr, server_peer_id); + let _res = swarm.dial(server_addr)?; + + loop { + select! { + Ok(Some(line)) = stdin.next_line() => { + println!("MESSAGE: {}", line); + let bytes = line.into_bytes(); + let req = MyRequest { + key: keypair.clone().public().to_bytes(), + body: bytes.clone(), + sig: keys::sign(&mut key.clone(), bytes).to_vec(), + }; + println!("REQ : {:?}", req); + swarm.behaviour_mut().req_res.send_request( + &server_peer_id, + req, + ); + } + event = swarm.select_next_some() => match event { + // MDNS BEHAVIOR + SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Discovered(list))) => { + for (peer_id, multiaddr) in list { + println!("add peer {:?}, {:?}", peer_id, multiaddr.clone()); + // swarm.dial(multiaddr)?; + } + } + // SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Expired(list))) => { + // for (peer_id, _multiaddr) in list { + // println!("mDNS discover peer has expired: {peer_id}"); + // // swarm.behaviour_mut().gossipsub.remove_explicit_peer(&peer_id); + // } + // } + // SwarmEvent::Behaviour(MyBehaviourEvent::ReqRes(request_response::Event::Message { + // peer: _peer, + // message: + // libp2p::request_response::Message::Request { + // request, channel, .. + // }, + // })) => { + // // println!("Req : {:?} {:?} {:?}", peer, channel, hex::encode(request.body.clone()), ); + // println!("REQSIG : {}", hex::encode(request.sig.clone()),); + // let _ = swarm.behaviour_mut().req_res.send_response( + // channel, + // MyResponse { + // sig: keypair.sign(&request.body), + // }, + // ); + // } + SwarmEvent::Behaviour(MyBehaviourEvent::ReqRes(request_response::Event::Message { + peer: _peer, + message: libp2p::request_response::Message::Response { response, .. }, + })) => { + // println!("response : {:?} {:?} {:?}", peer, request_id, "" ); + println!("RESSIG : {}", hex::encode(response.sig.clone()),); + } + e => { + println!("OTHER {:?}", e) + } + } + } + } +} diff --git a/app/server.rs b/app/server.rs index ed275b7..028ec31 100644 --- a/app/server.rs +++ b/app/server.rs @@ -1,93 +1,85 @@ -use std::{error::Error, time::Duration}; +use std::error::Error; -use cll2v0::messages::{MyRequest, MyResponse}; +use ed25519_dalek::{ed25519::signature::SignerMut, Signature}; use futures::prelude::*; use libp2p::{ - mdns, noise, + mdns, request_response::{self, ProtocolSupport}, - swarm::{NetworkBehaviour, SwarmEvent}, - tcp, yamux, StreamProtocol, Swarm, + swarm::SwarmEvent, }; -use libp2p_identity::ed25519::Keypair; use tracing_subscriber::EnvFilter; -// We create a custom network behaviour +use cll2v0::{ + db, keys, + messages::{MyRequest, MyResponse}, + protocol::{mk_swarm, MyBehaviourEvent}, +}; -#[derive(NetworkBehaviour)] -struct MyBehaviour { - mdns: mdns::tokio::Behaviour, - req_res: request_response::cbor::Behaviour, +use clap::{Parser, Subcommand}; + +/// cll2v0 is a playground for rust libraries. +/// This is signing service. +#[derive(Parser)] +#[command(arg_required_else_help(true), version, about)] +struct Args { + #[command(subcommand)] + cmd: Option, } -fn sign(kp: &Keypair, msg: &Vec) -> String { - hex::encode(kp.sign(msg)) +#[derive(Subcommand)] +enum Command { + /// Start server + Start(ServerParams), } -fn mk_swarm( - kp: &Keypair, - protocol_support: ProtocolSupport, -) -> Result, Box> { - let swarm = libp2p::SwarmBuilder::with_existing_identity(kp.clone().into()) - .with_tokio() - .with_tcp( - tcp::Config::default(), - noise::Config::new, - yamux::Config::default, - )? - .with_behaviour(|key| { - let mdns = - mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id())?; - let protocol = [(StreamProtocol::new("/sign-me/1"), protocol_support)]; - let config = request_response::Config::default(); - let req_res = - request_response::cbor::Behaviour::::new(protocol, config); - Ok(MyBehaviour { req_res, mdns }) - })? - .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) - .build(); - Ok(swarm) +#[derive(clap::Args, Debug)] +pub struct ServerParams { + /// Server listening address + #[arg(long, default_value = "/ip4/0.0.0.0/tcp/52321")] + pub listen_on: String, + /// Signing key of server libp2p part + #[arg( + long, + default_value = "deadbeef00000000000000000000000000000000000000000000000000000000" + )] + pub skey: String, } -#[tokio::main] -pub async fn main() -> Result<(), Box> { - let body: Vec = vec![0, 0, 0, 0, 0, 0, 0, 0]; - - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - - let keypair = libp2p_identity::ed25519::Keypair::generate(); - let key = keypair.public().to_bytes(); - - let is_client = match std::env::args().nth(1) { - Some(arg) => { - println!("Arg {}", arg); - true +fn main() -> Result<(), Box> { + match Args::parse().cmd { + Some(Command::Start(params)) => tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(start(params)), + _ => { + panic!("oops") } - _ => false, - }; + } +} - let protocol_support = if is_client { - ProtocolSupport::Outbound - } else { - ProtocolSupport::Inbound - }; - let mut swarm = mk_swarm(&keypair, protocol_support)?; - // Tell the swarm to listen on all interfaces and a random, OS-assigned - // port. - swarm.listen_on("/ip4/192.168.1.51/tcp/0".parse()?)?; - println!("ORISIG : {}", sign(&keypair, &body)); +pub async fn start(params: ServerParams) -> Result<(), Box> { + let ServerParams { + skey: skey_hex, + listen_on, + } = params; + if true { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + } + let keypair = keys::libp2p_kp_from_hex(skey_hex)?; + let peer_id = libp2p_identity::PublicKey::from(keypair.public()).to_peer_id(); + println!("PEER_ID : {}", peer_id); + + let (keychain, pool) = db::start_db().await?; + + let mut swarm = mk_swarm(&keypair.clone().into(), ProtocolSupport::Inbound)?; + swarm.listen_on(listen_on.parse()?)?; loop { match swarm.select_next_some().await { - SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Discovered(list))) => { - for (peer_id, multiaddr) in list { - println!("add peer {:?}, {:?}", peer_id, multiaddr.clone()); - if is_client { - let _ = swarm.dial(multiaddr); - } - } - } + // MDNS BEHAVIOR SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Expired(list))) => { for (peer_id, _multiaddr) in list { println!("mDNS discover peer has expired: {peer_id}"); @@ -103,32 +95,30 @@ pub async fn main() -> Result<(), Box> { })) => { // println!("Req : {:?} {:?} {:?}", peer, channel, hex::encode(request.body.clone()), ); println!("REQSIG : {}", hex::encode(request.sig.clone()),); - let _ = swarm.behaviour_mut().req_res.send_response( - channel, - MyResponse { - sig: keypair.sign(&request.body), - }, - ); - } - SwarmEvent::Behaviour(MyBehaviourEvent::ReqRes(request_response::Event::Message { - peer: _peer, - message: libp2p::request_response::Message::Response { response, .. }, - })) => { - // println!("response : {:?} {:?} {:?}", peer, request_id, "" ); - println!("RESSIG : {}", hex::encode(response.sig.clone()),); - } - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - //connection_id, endpoint, num_established, concurrent_dial_errors, established_in } => { - println!("ConnectionEstablisted {}", peer_id); - if is_client { - swarm.behaviour_mut().req_res.send_request( - &peer_id, - MyRequest { - key: key.clone(), - body: body.clone(), - sig: keypair.sign(&body), - }, - ); + let MyRequest { key, body, sig } = request; + + // FIXME :: MAP ERROR. + let sig_arr: [u8; 64] = sig.clone().try_into().unwrap(); + + if let Ok(ekey) = keys::from_bytes(key.into()) { + if let Ok(_) = ekey.verify_strict(&body, &Signature::from_bytes(&sig_arr)) { + if let Ok(pkey) = db::get_persistent_key(&pool, &ekey).await { + if let Some(skey) = keychain.get(&pkey.to_bytes()) { + let _ = swarm.behaviour_mut().req_res.send_response( + channel, + MyResponse { + sig: skey.clone().sign(&body).to_bytes().into(), + }, + ); + } else { + println!("err0") + }; + } else { + println!("err1"); + } + } else { + println!("err2"); + } } } e => { diff --git a/src/arr.rs b/src/arr.rs new file mode 100644 index 0000000..952a8c3 --- /dev/null +++ b/src/arr.rs @@ -0,0 +1,8 @@ +pub fn arr32(b: Vec) -> anyhow::Result<[u8; 32]> { + println!("{}", b.len()); + b.try_into().map_err(|_| anyhow::anyhow!("bad input")) +} + +pub fn arr64(b: Vec) -> anyhow::Result<[u8; 64]> { + b.try_into().map_err(|_| anyhow::anyhow!("bad input")) +} diff --git a/src/db.rs b/src/db.rs index eb06bff..053765f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,7 +1,22 @@ +use std::env; + use ed25519_dalek::VerifyingKey; use sqlx::sqlite::SqlitePool; -use crate::keys; +use crate::keys::{self, Keychain}; + +pub async fn start_db() -> Result<(Keychain, SqlitePool), anyhow::Error> { + let keys_path = env::var("CLL2V0_KEYS").expect("Expect `CLL2V0_KEYS` to be set"); + let keychain = keys::mk_keychain(&keys_path); + + let db_url = env::var("DATABASE_URL").expect("Expect `DATABASE_URL` to be set"); + let pool = SqlitePool::connect(&db_url).await?; + for vkey_bytes in keychain.keys() { + let vkey = keys::from_bytes(vkey_bytes.into())?; + let _ = add_persistent_key(&pool, &vkey).await; + } + Ok((keychain, pool)) +} pub async fn add_persistent_key(pool: &SqlitePool, vkey: &VerifyingKey) -> anyhow::Result { let mut conn = pool.acquire().await?; @@ -53,7 +68,8 @@ pub async fn get_persistent_key( ) -> anyhow::Result { let b = keys::to_bytes(ephemeral_key); let rec = sqlx::query!( - r#" SELECT persistent_key FROM ephemeral_keys WHERE id = ? AND expires_at > date(); "#, + r#" SELECT persistent_key FROM ephemeral_keys WHERE id = ?;"#, + // r#" SELECT persistent_key FROM ephemeral_keys WHERE id = ? AND expires_at > date(); "#, b, ) .fetch_one(pool) @@ -72,3 +88,33 @@ pub async fn list_persistent_keys(pool: &SqlitePool) -> anyhow::Result>(); Ok(keys) } + +pub async fn list_ephemeral_keys( + pool: &SqlitePool, +) -> anyhow::Result> { + let recs = + sqlx::query!(r#" SELECT id, persistent_key, expires_at FROM ephemeral_keys ORDER BY id "#) + .fetch_all(pool) + .await?; + let now: i64 = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + .try_into() + .unwrap(); + let keys = recs + .into_iter() + .map(|b| { + ( + keys::from_bytes(b.id).expect("illegal"), + keys::from_bytes(b.persistent_key).expect("illegal"), + if b.expires_at > now { + ::try_from(b.expires_at - now).unwrap() + } else { + 0 - ::try_from(now - b.expires_at).unwrap() + }, + ) + }) + .collect::>(); + Ok(keys) +} diff --git a/src/keys.rs b/src/keys.rs index b11371c..92dc41c 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -2,7 +2,10 @@ use std::{collections::HashMap, fs::read_to_string}; use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, VerifyingKey}; +use crate::arr::arr32; + pub fn from_bytes(bytes: Vec) -> Result { + println!("{}", bytes.len()); let arr: [u8; 32] = TryInto::try_into(bytes).expect(""); Ok(VerifyingKey::from_bytes(&arr)?) } @@ -54,3 +57,16 @@ pub fn mk_keychain(keys_path: &str) -> Keychain { } keychain } + +pub fn libp2p_kp_from_hex(s: String) -> Result { + let skey_arr = hex::decode(s)?; // .expect("Cannot fromdecode hex"); + let skey = libp2p_identity::ed25519::SecretKey::try_from_bytes(skey_arr)?; + Ok(libp2p_identity::ed25519::Keypair::from(skey)) +} + +pub fn libp2p_pubkey_from_hex( + s: &String, +) -> Result { + let pk = libp2p_identity::ed25519::PublicKey::try_from_bytes(&arr32(hex::decode(s)?)?)?; + Ok(pk) +} diff --git a/src/lib.rs b/src/lib.rs index c333936..7338f89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ +pub mod arr; pub mod db; pub mod hash; pub mod keys; pub mod messages; -pub mod protobuf; +//pub mod protobuf; +pub mod protocol; diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..bb8b12d --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,42 @@ +use std::{error::Error, time::Duration}; + +use libp2p::{ + mdns, noise, + request_response::{self, ProtocolSupport}, + swarm::NetworkBehaviour, + tcp, yamux, StreamProtocol, Swarm, +}; +use libp2p_identity::ed25519::Keypair; + +use crate::messages::{MyRequest, MyResponse}; + +#[derive(NetworkBehaviour)] +pub struct MyBehaviour { + pub mdns: mdns::tokio::Behaviour, + pub req_res: request_response::cbor::Behaviour, +} + +pub fn mk_swarm( + kp: &Keypair, + protocol_support: ProtocolSupport, +) -> Result, Box> { + let swarm = libp2p::SwarmBuilder::with_existing_identity(kp.clone().into()) + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| { + let mdns = + mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id())?; + let protocol = [(StreamProtocol::new("/sign-me/1"), protocol_support)]; + let config = request_response::Config::default(); + let req_res = + request_response::cbor::Behaviour::::new(protocol, config); + Ok(MyBehaviour { req_res, mdns }) + })? + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) + .build(); + Ok(swarm) +}