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 bind to, if using socket comms.
/// Port to connect to, if using HTTP mode. /// 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, pub port: u16,
} }

View File

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

View File

@ -37,7 +37,7 @@ pub struct Args {
pub ip: String, pub ip: String,
/// Port to connect to, if using online comms /// 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, pub port: u16,
/// Optional Session ID /// Optional Session ID

View File

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

View File

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

View File

@ -1,14 +1,12 @@
# FROST Server # FROST Server
This is a JSON-HTTPS server that allow FROST clients (Coordinator and
This is a HTTP server that allow clients (Coordinator and Participants) to Participants) to run FROST without needing to directly connect to one another.
run FROST without needing to directly connect to one another.
## Status ⚠ ## Status ⚠
This is a prototype which is NOT SECURE since messages are not encrypted nor This project has not being audited.
authenticated. DO NOT USE this for anything other than testing.
## Usage ## 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. 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` 1. Clone the repo. Run `git clone https://github.com/ZcashFoundation/frost-zcash-demo.git`
2. Run `cargo install` 2. Run `cargo build --release --bin server`
3. Run `cargo run --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. You will need to specify a TLS certificate and key with the `--tls-cert`
`cargo run --bin server -- --ip 127.0.0.1 --port 2744`. and `--tls-key` arguments.
## TODO For more details on using and deploying, refer to the [ZF FROST
Book](https://frost.zfnd.org/).
- Add specific error codes
- Remove frost-specific types (when data is encrypted)
- Session timeouts
- Encryption/authentication
- DoS protections and other production-ready requirements
-

View File

@ -10,7 +10,7 @@ use crate::{
}; };
/// Implement the challenge API. /// 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( pub(crate) async fn challenge(
State(state): State<SharedState>, State(state): State<SharedState>,
Json(_args): Json<ChallengeArgs>, Json(_args): Json<ChallengeArgs>,
@ -25,7 +25,7 @@ pub(crate) async fn challenge(
} }
/// Implement the key_login API. /// 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( pub(crate) async fn login(
State(state): State<SharedState>, State(state): State<SharedState>,
Json(args): Json<KeyLoginArgs>, Json(args): Json<KeyLoginArgs>,
@ -41,11 +41,11 @@ pub(crate) async fn login(
let signature = TryInto::<[u8; 64]>::try_into(args.signature) let signature = TryInto::<[u8; 64]>::try_into(args.signature)
.map_err(|_| AppError::InvalidArgument("signature".into()))?; .map_err(|_| AppError::InvalidArgument("signature".into()))?;
pubkey pubkey
.verify(args.uuid.as_bytes(), &signature) .verify(args.challenge.as_bytes(), &signature)
.map_err(|_| AppError::Unauthorized)?; .map_err(|_| AppError::Unauthorized)?;
let mut challenges = state.challenges.write().unwrap(); let mut challenges = state.challenges.write().unwrap();
if !challenges.remove(&args.uuid) { if !challenges.remove(&args.challenge) {
return Err(AppError::Unauthorized); return Err(AppError::Unauthorized);
} }
drop(challenges); drop(challenges);
@ -61,7 +61,7 @@ pub(crate) async fn login(
} }
/// Implement the logout API. /// 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( pub(crate) async fn logout(
State(state): State<SharedState>, State(state): State<SharedState>,
user: User, user: User,
@ -75,7 +75,7 @@ pub(crate) async fn logout(
} }
/// Implement the create_new_session API. /// 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( pub(crate) async fn create_new_session(
State(state): State<SharedState>, State(state): State<SharedState>,
user: User, user: User,
@ -94,7 +94,7 @@ pub(crate) async fn create_new_session(
// Save session ID in global state // Save session ID in global state
for pubkey in &args.pubkeys { for pubkey in &args.pubkeys {
sessions_by_pubkey sessions_by_pubkey
.entry(pubkey.0.clone()) .entry(pubkey.clone().0)
.or_default() .or_default()
.insert(id); .insert(id);
} }
@ -102,7 +102,6 @@ pub(crate) async fn create_new_session(
let session = Session { let session = Session {
pubkeys: args.pubkeys.into_iter().map(|p| p.0).collect(), pubkeys: args.pubkeys.into_iter().map(|p| p.0).collect(),
coordinator_pubkey: user.pubkey, coordinator_pubkey: user.pubkey,
num_signers: args.num_signers,
message_count: args.message_count, message_count: args.message_count,
queue: Default::default(), queue: Default::default(),
}; };
@ -114,7 +113,7 @@ pub(crate) async fn create_new_session(
} }
/// Implement the create_new_session API. /// 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( pub(crate) async fn list_sessions(
State(state): State<SharedState>, State(state): State<SharedState>,
user: User, user: User,
@ -130,7 +129,7 @@ pub(crate) async fn list_sessions(
} }
/// Implement the get_session_info API /// 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( pub(crate) async fn get_session_info(
State(state): State<SharedState>, State(state): State<SharedState>,
user: User, user: User,
@ -152,7 +151,6 @@ pub(crate) async fn get_session_info(
.ok_or(AppError::SessionNotFound)?; .ok_or(AppError::SessionNotFound)?;
Ok(Json(GetSessionInfoOutput { Ok(Json(GetSessionInfoOutput {
num_signers: session.num_signers,
message_count: session.message_count, message_count: session.message_count,
pubkeys: session.pubkeys.iter().cloned().map(PublicKey).collect(), pubkeys: session.pubkeys.iter().cloned().map(PublicKey).collect(),
coordinator_pubkey: session.coordinator_pubkey.clone(), coordinator_pubkey: session.coordinator_pubkey.clone(),
@ -161,7 +159,7 @@ pub(crate) async fn get_session_info(
/// Implement the send API /// Implement the send API
// TODO: get identifier from channel rather from arguments // 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( pub(crate) async fn send(
State(state): State<SharedState>, State(state): State<SharedState>,
user: User, user: User,
@ -197,7 +195,7 @@ pub(crate) async fn send(
} }
/// Implement the recv API /// 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( pub(crate) async fn receive(
State(state): State<SharedState>, State(state): State<SharedState>,
user: User, user: User,
@ -240,7 +238,7 @@ pub(crate) async fn receive(
} }
/// Implement the close_session API. /// 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( pub(crate) async fn close_session(
State(state): State<SharedState>, State(state): State<SharedState>,
user: User, 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 { if args.no_tls_very_insecure {
tracing::warn!( tracing::warn!(
"starting an INSECURE HTTP server. This should be done only for \ "starting an INSECURE HTTP server at {}. This should be done only \
testing or if you are providing TLS/HTTPS with a separate \ for testing or if you are providing TLS/HTTPS with a separate \
mechanism (e.g. reverse proxy such as nginx)" mechanism (e.g. reverse proxy such as nginx)",
addr,
); );
let listener = tokio::net::TcpListener::bind(addr).await?; let listener = tokio::net::TcpListener::bind(addr).await?;
Ok(axum::serve(listener, app).await?) Ok(axum::serve(listener, app).await?)

View File

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

View File

@ -42,8 +42,6 @@ pub struct Session {
pub(crate) pubkeys: Vec<Vec<u8>>, pub(crate) pubkeys: Vec<Vec<u8>>,
/// The public key of the coordinator /// The public key of the coordinator
pub(crate) coordinator_pubkey: Vec<u8>, 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. /// The number of messages being simultaneously signed.
pub(crate) message_count: u8, pub(crate) message_count: u8,
/// The message queue. /// The message queue.

View File

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

View File

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