janky but runs

This commit is contained in:
waalge 2025-01-26 19:03:02 +00:00
parent 7f151868e1
commit 33b7dc5d8e
10 changed files with 400 additions and 119 deletions

View File

@ -8,13 +8,17 @@ publish = false
[package.metadata.release] [package.metadata.release]
release = false release = false
[[bin]] # Bin to run the server [[bin]] # Admin
name = "admin"
path = "app/admin.rs"
[[bin]] # Server
name = "server" name = "server"
path = "app/server.rs" path = "app/server.rs"
#
[[bin]] # Bin to run the server [[bin]] # Client
name = "cli" name = "client"
path = "app/cli.rs" path = "app/client.rs"
[dependencies] [dependencies]
anyhow = "1.0.95" anyhow = "1.0.95"

View File

@ -9,6 +9,24 @@ establish our key dependencies.
This repo use nix flakes with a shell available. Otherwise ymmv. This repo use nix flakes with a shell available. Otherwise ymmv.
Build:
```sh
cargo build
```
Run binary `x`:
```sh
cargo run --bin <x>
```
There are three binaries:
- `admin` - admin controls: create, add, revoke ephemeral keys
- `server` - run the signing server
- `client` - run a client
## Context ## Context
CL depends on the use on signing/verification key pair cryptography. The signing CL depends on the use on signing/verification key pair cryptography. The signing

View File

@ -1,10 +1,10 @@
use std::env;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use ed25519_dalek::{Signature, SigningKey}; 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. /// cll2v0 is a playground for rust libraries.
/// This is signing service. /// This is signing service.
@ -46,15 +46,7 @@ enum Command {
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let args = Args::parse(); let args = Args::parse();
let keys_path = env::var("CLL2V0_KEYS").expect("Expect `CLL2V0_KEYS` to be set"); let (_, pool) = db::start_db().await?;
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;
}
match args.cmd { match args.cmd {
Some(Command::Add { Some(Command::Add {
@ -78,7 +70,11 @@ async fn main() -> anyhow::Result<()> {
let res = db::list_persistent_keys(&pool).await?; let res = db::list_persistent_keys(&pool).await?;
println!("Persistent keys:"); println!("Persistent keys:");
res.into_iter() 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 }) => { Some(Command::Gen { seed }) => {
let ekey = SigningKey::from_bytes(&[ let ekey = SigningKey::from_bytes(&[

159
app/client.rs Normal file
View File

@ -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<Command>,
}
#[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<dyn Error>> {
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<dyn Error>> {
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)
}
}
}
}
}

View File

@ -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 futures::prelude::*;
use libp2p::{ use libp2p::{
mdns, noise, mdns,
request_response::{self, ProtocolSupport}, request_response::{self, ProtocolSupport},
swarm::{NetworkBehaviour, SwarmEvent}, swarm::SwarmEvent,
tcp, yamux, StreamProtocol, Swarm,
}; };
use libp2p_identity::ed25519::Keypair;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
// We create a custom network behaviour use cll2v0::{
db, keys,
messages::{MyRequest, MyResponse},
protocol::{mk_swarm, MyBehaviourEvent},
};
#[derive(NetworkBehaviour)] use clap::{Parser, Subcommand};
struct MyBehaviour {
mdns: mdns::tokio::Behaviour, /// cll2v0 is a playground for rust libraries.
req_res: request_response::cbor::Behaviour<MyRequest, MyResponse>, /// This is signing service.
#[derive(Parser)]
#[command(arg_required_else_help(true), version, about)]
struct Args {
#[command(subcommand)]
cmd: Option<Command>,
} }
fn sign(kp: &Keypair, msg: &Vec<u8>) -> String { #[derive(Subcommand)]
hex::encode(kp.sign(msg)) enum Command {
/// Start server
Start(ServerParams),
} }
fn mk_swarm( #[derive(clap::Args, Debug)]
kp: &Keypair, pub struct ServerParams {
protocol_support: ProtocolSupport, /// Server listening address
) -> Result<Swarm<MyBehaviour>, Box<dyn Error>> { #[arg(long, default_value = "/ip4/0.0.0.0/tcp/52321")]
let swarm = libp2p::SwarmBuilder::with_existing_identity(kp.clone().into()) pub listen_on: String,
.with_tokio() /// Signing key of server libp2p part
.with_tcp( #[arg(
tcp::Config::default(), long,
noise::Config::new, default_value = "deadbeef00000000000000000000000000000000000000000000000000000000"
yamux::Config::default, )]
)? pub skey: String,
.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::<MyRequest, MyResponse>::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)
} }
#[tokio::main] fn main() -> Result<(), Box<dyn Error>> {
pub async fn main() -> Result<(), Box<dyn Error>> { match Args::parse().cmd {
let body: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0]; Some(Command::Start(params)) => tokio::runtime::Builder::new_multi_thread()
.enable_all()
let _ = tracing_subscriber::fmt() .build()
.with_env_filter(EnvFilter::from_default_env()) .unwrap()
.try_init(); .block_on(start(params)),
_ => {
let keypair = libp2p_identity::ed25519::Keypair::generate(); panic!("oops")
let key = keypair.public().to_bytes();
let is_client = match std::env::args().nth(1) {
Some(arg) => {
println!("Arg {}", arg);
true
} }
_ => false, }
}; }
let protocol_support = if is_client { pub async fn start(params: ServerParams) -> Result<(), Box<dyn Error>> {
ProtocolSupport::Outbound let ServerParams {
} else { skey: skey_hex,
ProtocolSupport::Inbound listen_on,
}; } = params;
let mut swarm = mk_swarm(&keypair, protocol_support)?; if true {
// Tell the swarm to listen on all interfaces and a random, OS-assigned let _ = tracing_subscriber::fmt()
// port. .with_env_filter(EnvFilter::from_default_env())
swarm.listen_on("/ip4/192.168.1.51/tcp/0".parse()?)?; .try_init();
println!("ORISIG : {}", sign(&keypair, &body)); }
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 { loop {
match swarm.select_next_some().await { match swarm.select_next_some().await {
SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Discovered(list))) => { // MDNS BEHAVIOR
for (peer_id, multiaddr) in list {
println!("add peer {:?}, {:?}", peer_id, multiaddr.clone());
if is_client {
let _ = swarm.dial(multiaddr);
}
}
}
SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Expired(list))) => { SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Expired(list))) => {
for (peer_id, _multiaddr) in list { for (peer_id, _multiaddr) in list {
println!("mDNS discover peer has expired: {peer_id}"); println!("mDNS discover peer has expired: {peer_id}");
@ -103,32 +95,30 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
})) => { })) => {
// println!("Req : {:?} {:?} {:?}", peer, channel, hex::encode(request.body.clone()), ); // println!("Req : {:?} {:?} {:?}", peer, channel, hex::encode(request.body.clone()), );
println!("REQSIG : {}", hex::encode(request.sig.clone()),); println!("REQSIG : {}", hex::encode(request.sig.clone()),);
let _ = swarm.behaviour_mut().req_res.send_response( let MyRequest { key, body, sig } = request;
channel,
MyResponse { // FIXME :: MAP ERROR.
sig: keypair.sign(&request.body), 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)) {
SwarmEvent::Behaviour(MyBehaviourEvent::ReqRes(request_response::Event::Message { if let Ok(pkey) = db::get_persistent_key(&pool, &ekey).await {
peer: _peer, if let Some(skey) = keychain.get(&pkey.to_bytes()) {
message: libp2p::request_response::Message::Response { response, .. }, let _ = swarm.behaviour_mut().req_res.send_response(
})) => { channel,
// println!("response : {:?} {:?} {:?}", peer, request_id, "" ); MyResponse {
println!("RESSIG : {}", hex::encode(response.sig.clone()),); sig: skey.clone().sign(&body).to_bytes().into(),
} },
SwarmEvent::ConnectionEstablished { peer_id, .. } => { );
//connection_id, endpoint, num_established, concurrent_dial_errors, established_in } => { } else {
println!("ConnectionEstablisted {}", peer_id); println!("err0")
if is_client { };
swarm.behaviour_mut().req_res.send_request( } else {
&peer_id, println!("err1");
MyRequest { }
key: key.clone(), } else {
body: body.clone(), println!("err2");
sig: keypair.sign(&body), }
},
);
} }
} }
e => { e => {

8
src/arr.rs Normal file
View File

@ -0,0 +1,8 @@
pub fn arr32(b: Vec<u8>) -> anyhow::Result<[u8; 32]> {
println!("{}", b.len());
b.try_into().map_err(|_| anyhow::anyhow!("bad input"))
}
pub fn arr64(b: Vec<u8>) -> anyhow::Result<[u8; 64]> {
b.try_into().map_err(|_| anyhow::anyhow!("bad input"))
}

View File

@ -1,7 +1,22 @@
use std::env;
use ed25519_dalek::VerifyingKey; use ed25519_dalek::VerifyingKey;
use sqlx::sqlite::SqlitePool; 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<i64> { pub async fn add_persistent_key(pool: &SqlitePool, vkey: &VerifyingKey) -> anyhow::Result<i64> {
let mut conn = pool.acquire().await?; let mut conn = pool.acquire().await?;
@ -53,7 +68,8 @@ pub async fn get_persistent_key(
) -> anyhow::Result<VerifyingKey> { ) -> anyhow::Result<VerifyingKey> {
let b = keys::to_bytes(ephemeral_key); let b = keys::to_bytes(ephemeral_key);
let rec = sqlx::query!( 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, b,
) )
.fetch_one(pool) .fetch_one(pool)
@ -72,3 +88,33 @@ pub async fn list_persistent_keys(pool: &SqlitePool) -> anyhow::Result<Vec<Verif
.collect::<Vec<VerifyingKey>>(); .collect::<Vec<VerifyingKey>>();
Ok(keys) Ok(keys)
} }
pub async fn list_ephemeral_keys(
pool: &SqlitePool,
) -> anyhow::Result<Vec<(VerifyingKey, VerifyingKey, i64)>> {
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 {
<i64>::try_from(b.expires_at - now).unwrap()
} else {
0 - <i64>::try_from(now - b.expires_at).unwrap()
},
)
})
.collect::<Vec<(VerifyingKey, VerifyingKey, i64)>>();
Ok(keys)
}

View File

@ -2,7 +2,10 @@ use std::{collections::HashMap, fs::read_to_string};
use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, VerifyingKey}; use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, VerifyingKey};
use crate::arr::arr32;
pub fn from_bytes(bytes: Vec<u8>) -> Result<VerifyingKey, anyhow::Error> { pub fn from_bytes(bytes: Vec<u8>) -> Result<VerifyingKey, anyhow::Error> {
println!("{}", bytes.len());
let arr: [u8; 32] = TryInto::try_into(bytes).expect(""); let arr: [u8; 32] = TryInto::try_into(bytes).expect("");
Ok(VerifyingKey::from_bytes(&arr)?) Ok(VerifyingKey::from_bytes(&arr)?)
} }
@ -54,3 +57,16 @@ pub fn mk_keychain(keys_path: &str) -> Keychain {
} }
keychain keychain
} }
pub fn libp2p_kp_from_hex(s: String) -> Result<libp2p_identity::ed25519::Keypair, anyhow::Error> {
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<libp2p_identity::ed25519::PublicKey, anyhow::Error> {
let pk = libp2p_identity::ed25519::PublicKey::try_from_bytes(&arr32(hex::decode(s)?)?)?;
Ok(pk)
}

View File

@ -1,5 +1,7 @@
pub mod arr;
pub mod db; pub mod db;
pub mod hash; pub mod hash;
pub mod keys; pub mod keys;
pub mod messages; pub mod messages;
pub mod protobuf; //pub mod protobuf;
pub mod protocol;

42
src/protocol.rs Normal file
View File

@ -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<MyRequest, MyResponse>,
}
pub fn mk_swarm(
kp: &Keypair,
protocol_support: ProtocolSupport,
) -> Result<Swarm<MyBehaviour>, Box<dyn Error>> {
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::<MyRequest, MyResponse>::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)
}