server: misc cleanups (#407)

This commit is contained in:
Conrado Gouvea 2024-12-26 19:03:16 -03:00 committed by GitHub
parent 3385036c50
commit d7b6ab7644
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 49 additions and 59 deletions

View File

@ -70,7 +70,7 @@ pub struct Args {
/// Port to bind to, if using socket comms.
/// Port to connect to, if using HTTP mode.
#[arg(short, long, default_value_t = 2744)]
#[arg(short, long, default_value_t = 443)]
pub port: u16,
}

View File

@ -267,7 +267,6 @@ pub struct HTTPComms<C: Ciphersuite> {
host_port: String,
session_id: Option<Uuid>,
access_token: Option<String>,
num_signers: u16,
args: ProcessedArgs<C>,
state: SessionState<C>,
pubkeys: HashMap<Vec<u8>, Identifier<C>>,
@ -286,7 +285,6 @@ impl<C: Ciphersuite> HTTPComms<C> {
host_port: format!("https://{}:{}", args.ip, args.port),
session_id: None,
access_token: None,
num_signers: 0,
args: args.clone(),
state: SessionState::new(args.messages.len(), args.num_signers as usize),
pubkeys: Default::default(),
@ -340,7 +338,7 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
_input: &mut dyn BufRead,
_output: &mut dyn Write,
_pub_key_package: &PublicKeyPackage<C>,
num_signers: u16,
_num_signers: u16,
) -> Result<BTreeMap<Identifier<C>, SigningCommitments<C>>, Box<dyn Error>> {
let mut rng = thread_rng();
let challenge = self
@ -368,7 +366,7 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
self.client
.post(format!("{}/login", self.host_port))
.json(&server::KeyLoginArgs {
uuid: challenge,
challenge,
pubkey: self
.args
.comm_pubkey
@ -390,7 +388,6 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
.bearer_auth(self.access_token.as_ref().expect("was just set"))
.json(&server::CreateNewSessionArgs {
pubkeys: self.args.signers.iter().cloned().map(PublicKey).collect(),
num_signers,
message_count: 1,
})
.send()
@ -405,7 +402,6 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
);
}
self.session_id = Some(r.session_id);
self.num_signers = num_signers;
let (Some(comm_privkey), Some(comm_participant_pubkey_getter)) = (
&self.args.comm_privkey,

View File

@ -66,7 +66,7 @@ pub(crate) async fn list(args: &Command) -> Result<(), Box<dyn Error>> {
let access_token = client
.post(format!("{}/login", host_port))
.json(&server::KeyLoginArgs {
uuid: challenge,
challenge,
pubkey: comm_pubkey.clone(),
signature: signature.to_vec(),
})
@ -102,7 +102,7 @@ pub(crate) async fn list(args: &Command) -> Result<(), Box<dyn Error>> {
let participants: Vec<_> = r
.pubkeys
.iter()
.map(|pubkey| config.contact_by_pubkey(pubkey))
.map(|pubkey| config.contact_by_pubkey(&pubkey.0))
.collect();
eprintln!("Session with ID {}", session_id);
eprintln!(

View File

@ -37,7 +37,7 @@ pub struct Args {
pub ip: String,
/// Port to connect to, if using online comms
#[arg(short, long, default_value_t = 2744)]
#[arg(short, long, default_value_t = 443)]
pub port: u16,
/// Optional Session ID

View File

@ -203,7 +203,7 @@ where
self.client
.post(format!("{}/login", self.host_port))
.json(&server::KeyLoginArgs {
uuid: challenge,
challenge,
pubkey: self
.args
.comm_pubkey

View File

@ -16,6 +16,7 @@ derivative = "2.2.0"
eyre = "0.6.11"
frost-core = { version = "2.0.0", features = ["serde"] }
frost-rerandomized = { version = "2.0.0-rc.0", features = ["serde"] }
hex = "0.4"
rand = "0.8"
rcgen = "0.13.1"
serde = { version = "1.0", features = ["derive"] }
@ -32,7 +33,6 @@ xeddsa = "1.0.2"
futures-util = "0.3.31"
futures = "0.3.31"
thiserror = "2.0.3"
hex = "0.4.3"
[dev-dependencies]
axum-test = "16.4.0"

View File

@ -1,14 +1,12 @@
# FROST Server
This is a HTTP server that allow clients (Coordinator and Participants) to
run FROST without needing to directly connect to one another.
This is a JSON-HTTPS server that allow FROST clients (Coordinator and
Participants) to run FROST without needing to directly connect to one another.
## Status ⚠
This is a prototype which is NOT SECURE since messages are not encrypted nor
authenticated. DO NOT USE this for anything other than testing.
This project has not being audited.
## Usage
@ -17,19 +15,14 @@ NOTE: This is for demo purposes only and should not be used in production.
You will need to have [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) installed.
To run:
To compile and run:
1. Clone the repo. Run `git clone https://github.com/ZcashFoundation/frost-zcash-demo.git`
2. Run `cargo install`
3. Run `cargo run --bin server`
2. Run `cargo build --release --bin server`
3. Run `./target/release/server -h` to learn about the command line arguments.
You can specify the IP and port to bind to using `--ip` and `--port`, e.g.
`cargo run --bin server -- --ip 127.0.0.1 --port 2744`.
You will need to specify a TLS certificate and key with the `--tls-cert`
and `--tls-key` arguments.
## TODO
- Add specific error codes
- Remove frost-specific types (when data is encrypted)
- Session timeouts
- Encryption/authentication
- DoS protections and other production-ready requirements
-
For more details on using and deploying, refer to the [ZF FROST
Book](https://frost.zfnd.org/).

View File

@ -10,7 +10,7 @@ use crate::{
};
/// Implement the challenge API.
#[tracing::instrument(ret, err(Debug), skip(state, _args))]
#[tracing::instrument(level = "debug", err(Debug), skip(state, _args))]
pub(crate) async fn challenge(
State(state): State<SharedState>,
Json(_args): Json<ChallengeArgs>,
@ -25,7 +25,7 @@ pub(crate) async fn challenge(
}
/// Implement the key_login API.
#[tracing::instrument(ret, err(Debug), skip(state, args))]
#[tracing::instrument(level = "debug", err(Debug), skip(state, args))]
pub(crate) async fn login(
State(state): State<SharedState>,
Json(args): Json<KeyLoginArgs>,
@ -41,11 +41,11 @@ pub(crate) async fn login(
let signature = TryInto::<[u8; 64]>::try_into(args.signature)
.map_err(|_| AppError::InvalidArgument("signature".into()))?;
pubkey
.verify(args.uuid.as_bytes(), &signature)
.verify(args.challenge.as_bytes(), &signature)
.map_err(|_| AppError::Unauthorized)?;
let mut challenges = state.challenges.write().unwrap();
if !challenges.remove(&args.uuid) {
if !challenges.remove(&args.challenge) {
return Err(AppError::Unauthorized);
}
drop(challenges);
@ -61,7 +61,7 @@ pub(crate) async fn login(
}
/// Implement the logout API.
#[tracing::instrument(ret, err(Debug), skip(state, user))]
#[tracing::instrument(level = "debug", ret, err(Debug), skip(state, user))]
pub(crate) async fn logout(
State(state): State<SharedState>,
user: User,
@ -75,7 +75,7 @@ pub(crate) async fn logout(
}
/// Implement the create_new_session API.
#[tracing::instrument(ret, err(Debug), skip(state, user))]
#[tracing::instrument(level = "debug", ret, err(Debug), skip(state, user))]
pub(crate) async fn create_new_session(
State(state): State<SharedState>,
user: User,
@ -94,7 +94,7 @@ pub(crate) async fn create_new_session(
// Save session ID in global state
for pubkey in &args.pubkeys {
sessions_by_pubkey
.entry(pubkey.0.clone())
.entry(pubkey.clone().0)
.or_default()
.insert(id);
}
@ -102,7 +102,6 @@ pub(crate) async fn create_new_session(
let session = Session {
pubkeys: args.pubkeys.into_iter().map(|p| p.0).collect(),
coordinator_pubkey: user.pubkey,
num_signers: args.num_signers,
message_count: args.message_count,
queue: Default::default(),
};
@ -114,7 +113,7 @@ pub(crate) async fn create_new_session(
}
/// Implement the create_new_session API.
#[tracing::instrument(ret, err(Debug), skip(state, user))]
#[tracing::instrument(level = "debug", ret, err(Debug), skip(state, user))]
pub(crate) async fn list_sessions(
State(state): State<SharedState>,
user: User,
@ -130,7 +129,7 @@ pub(crate) async fn list_sessions(
}
/// Implement the get_session_info API
#[tracing::instrument(ret, err(Debug), skip(state, user))]
#[tracing::instrument(level = "debug", ret, err(Debug), skip(state, user))]
pub(crate) async fn get_session_info(
State(state): State<SharedState>,
user: User,
@ -152,7 +151,6 @@ pub(crate) async fn get_session_info(
.ok_or(AppError::SessionNotFound)?;
Ok(Json(GetSessionInfoOutput {
num_signers: session.num_signers,
message_count: session.message_count,
pubkeys: session.pubkeys.iter().cloned().map(PublicKey).collect(),
coordinator_pubkey: session.coordinator_pubkey.clone(),
@ -161,7 +159,7 @@ pub(crate) async fn get_session_info(
/// Implement the send API
// TODO: get identifier from channel rather from arguments
#[tracing::instrument(ret, err(Debug), skip(state, user))]
#[tracing::instrument(level = "debug", ret, err(Debug), skip(state, user))]
pub(crate) async fn send(
State(state): State<SharedState>,
user: User,
@ -197,7 +195,7 @@ pub(crate) async fn send(
}
/// Implement the recv API
#[tracing::instrument(ret, err(Debug), skip(state, user))]
#[tracing::instrument(level = "debug", ret, err(Debug), skip(state, user))]
pub(crate) async fn receive(
State(state): State<SharedState>,
user: User,
@ -240,7 +238,7 @@ pub(crate) async fn receive(
}
/// Implement the close_session API.
#[tracing::instrument(ret, err(Debug), skip(state, user))]
#[tracing::instrument(level = "debug", ret, err(Debug), skip(state, user))]
pub(crate) async fn close_session(
State(state): State<SharedState>,
user: User,

View File

@ -49,9 +49,10 @@ pub async fn run(args: &Args) -> Result<(), Box<dyn std::error::Error>> {
if args.no_tls_very_insecure {
tracing::warn!(
"starting an INSECURE HTTP server. This should be done only for \
testing or if you are providing TLS/HTTPS with a separate \
mechanism (e.g. reverse proxy such as nginx)"
"starting an INSECURE HTTP server at {}. This should be done only \
for testing or if you are providing TLS/HTTPS with a separate \
mechanism (e.g. reverse proxy such as nginx)",
addr,
);
let listener = tokio::net::TcpListener::bind(addr).await?;
Ok(axum::serve(listener, app).await?)

View File

@ -1,12 +1,20 @@
use clap::Parser;
use server::args::Args;
use server::run;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
// initialize tracing
tracing_subscriber::fmt::init();
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
tracing::event!(tracing::Level::INFO, "server running");
run(&args).await
}

View File

@ -42,8 +42,6 @@ pub struct Session {
pub(crate) pubkeys: Vec<Vec<u8>>,
/// The public key of the coordinator
pub(crate) coordinator_pubkey: Vec<u8>,
/// The number of signers in the session.
pub(crate) num_signers: u16,
/// The number of messages being simultaneously signed.
pub(crate) message_count: u8,
/// The message queue.

View File

@ -32,7 +32,7 @@ pub struct ChallengeOutput {
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyLoginArgs {
pub uuid: Uuid,
pub challenge: Uuid,
#[serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
@ -64,7 +64,6 @@ pub struct LoginArgs {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CreateNewSessionArgs {
pub pubkeys: Vec<PublicKey>,
pub num_signers: u16,
pub message_count: u8,
}
@ -85,7 +84,6 @@ pub struct GetSessionInfoArgs {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GetSessionInfoOutput {
pub num_signers: u16,
pub message_count: u8,
pub pubkeys: Vec<PublicKey>,
pub coordinator_pubkey: Vec<u8>,

View File

@ -89,7 +89,7 @@ async fn test_main_router<
let res = server
.post("/login")
.json(&server::KeyLoginArgs {
uuid: alice_challenge,
challenge: alice_challenge,
pubkey: alice_keypair.public.clone(),
signature: alice_signature.to_vec(),
})
@ -104,7 +104,7 @@ async fn test_main_router<
let res = server
.post("/login")
.json(&server::KeyLoginArgs {
uuid: bob_challenge,
challenge: bob_challenge,
pubkey: bob_keypair.public.clone(),
signature: bob_signature.to_vec(),
})
@ -124,7 +124,6 @@ async fn test_main_router<
server::PublicKey(alice_keypair.public.clone()),
server::PublicKey(bob_keypair.public.clone()),
],
num_signers: 2,
message_count: 2,
})
.await;
@ -463,7 +462,7 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
let r = client
.post("https://127.0.0.1:2744/login")
.json(&server::KeyLoginArgs {
uuid: alice_challenge,
challenge: alice_challenge,
pubkey: alice_keypair.public.clone(),
signature: alice_signature.to_vec(),
})
@ -485,7 +484,6 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
server::PublicKey(bob_keypair.public.clone()),
],
message_count: 1,
num_signers: 2,
})
.send()
.await?;
@ -528,7 +526,7 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
let r = client
.post("https://127.0.0.1:2744/login")
.json(&server::KeyLoginArgs {
uuid: bob_challenge,
challenge: bob_challenge,
pubkey: bob_keypair.public.clone(),
signature: bob_signature.to_vec(),
})