frost-client: force encryption when using HTTP (#398)

* frost-client: force encryption when using HTTP

* fix test

* fix new test
This commit is contained in:
Conrado Gouvea 2024-12-26 18:40:12 -03:00 committed by GitHub
parent 30cdd00fa6
commit 11c4583091
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 147 additions and 161 deletions

2
Cargo.lock generated
View File

@ -2688,8 +2688,8 @@ dependencies = [
"serde_json",
"serdect",
"snow",
"thiserror 2.0.4",
"tempfile",
"thiserror 2.0.4",
"tokio",
"tower-http",
"tracing",

View File

@ -26,11 +26,6 @@ pub struct Args {
#[arg(long, default_value_t = false)]
pub cli: bool,
/// HTTP mode. If enabled, it will use HTTP communication with a
/// FROST server.
#[arg(long, default_value_t = false)]
pub http: bool,
/// The comma-separated usernames of the signers to use in HTTP mode.
/// If HTTP mode is enabled and this is empty, then the session ID
/// will be printed and will have to be shared manually.
@ -178,7 +173,7 @@ impl<C: Ciphersuite + 'static> ProcessedArgs<C> {
Ok(ProcessedArgs {
cli: args.cli,
http: args.http,
http: false,
signers,
num_signers,
public_key_package,

View File

@ -296,44 +296,40 @@ impl<C: Ciphersuite> HTTPComms<C> {
})
}
// Encrypts a message for a given recipient if encryption is enabled.
fn encrypt_if_needed(
&mut self,
recipient: &Vec<u8>,
msg: Vec<u8>,
) -> Result<Vec<u8>, Box<dyn Error>> {
if let Some(noise_map) = &mut self.send_noise {
let noise = noise_map
.get_mut(recipient)
.ok_or_eyre("unknown recipient")?;
let mut encrypted = vec![0; 65535];
let len = noise.write_message(&msg, &mut encrypted)?;
encrypted.truncate(len);
Ok(encrypted)
} else {
Ok(msg)
}
// Encrypts a message for a given recipient.
fn encrypt(&mut self, recipient: &Vec<u8>, msg: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
let noise_map = self
.send_noise
.as_mut()
.expect("send_noise must have been set previously");
let noise = noise_map
.get_mut(recipient)
.ok_or_eyre("unknown recipient")?;
let mut encrypted = vec![0; 65535];
let len = noise.write_message(&msg, &mut encrypted)?;
encrypted.truncate(len);
Ok(encrypted)
}
// Decrypts a message if encryption is enabled.
// Decrypts a message.
// Note that this authenticates the `sender` in the `Msg` struct; if the
// sender is tampered with, the message would fail to decrypt.
fn decrypt_if_needed(&mut self, msg: Msg) -> Result<Msg, Box<dyn Error>> {
if let Some(noise_map) = &mut self.recv_noise {
let noise = noise_map
.get_mut(&msg.sender)
.ok_or_eyre("unknown sender")?;
let mut decrypted = vec![0; 65535];
decrypted.resize(65535, 0);
let len = noise.read_message(&msg.msg, &mut decrypted)?;
decrypted.truncate(len);
Ok(Msg {
sender: msg.sender,
msg: decrypted,
})
} else {
Ok(msg)
}
fn decrypt(&mut self, msg: Msg) -> Result<Msg, Box<dyn Error>> {
let noise_map = self
.recv_noise
.as_mut()
.expect("recv_noise must have been set previously");
let noise = noise_map
.get_mut(&msg.sender)
.ok_or_eyre("unknown sender")?;
let mut decrypted = vec![0; 65535];
decrypted.resize(65535, 0);
let len = noise.read_message(&msg.msg, &mut decrypted)?;
decrypted.truncate(len);
Ok(Msg {
sender: msg.sender,
msg: decrypted,
})
}
}
@ -411,48 +407,49 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
self.session_id = Some(r.session_id);
self.num_signers = num_signers;
// If encryption is enabled, create the Noise objects
(self.send_noise, self.recv_noise) = if 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_participant_pubkey_getter,
) {
let mut send_noise_map = HashMap::new();
let mut recv_noise_map = HashMap::new();
for pubkey in &self.args.signers {
let comm_participant_pubkey = comm_participant_pubkey_getter(pubkey).ok_or_eyre("A participant in specified FROST session is not registered in the coordinator's address book")?;
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
);
let send_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_participant_pubkey)
.build_initiator()?,
);
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
);
let recv_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_participant_pubkey)
.build_responder()?,
);
send_noise_map.insert(pubkey.clone(), send_noise);
recv_noise_map.insert(pubkey.clone(), recv_noise);
}
(Some(send_noise_map), Some(recv_noise_map))
} else {
(None, None)
) else {
return Err(
eyre!("comm_privkey and comm_participant_pubkey_getter must be specified").into(),
);
};
// If encryption is enabled, create the Noise objects
let mut send_noise_map = HashMap::new();
let mut recv_noise_map = HashMap::new();
for pubkey in &self.args.signers {
let comm_participant_pubkey = comm_participant_pubkey_getter(pubkey).ok_or_eyre("A participant in specified FROST session is not registered in the coordinator's address book")?;
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
);
let send_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_participant_pubkey)
.build_initiator()?,
);
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
);
let recv_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_participant_pubkey)
.build_responder()?,
);
send_noise_map.insert(pubkey.clone(), send_noise);
recv_noise_map.insert(pubkey.clone(), recv_noise);
}
self.send_noise = Some(send_noise_map);
self.recv_noise = Some(recv_noise_map);
eprint!("Waiting for participants to send their commitments...");
loop {
@ -469,7 +466,7 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
.json::<server::ReceiveOutput>()
.await?;
for msg in r.msgs {
let msg = self.decrypt_if_needed(msg)?;
let msg = self.decrypt(msg)?;
self.state.recv(msg)?;
}
tokio::time::sleep(Duration::from_secs(2)).await;
@ -505,8 +502,7 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
// individually for each recipient.
let pubkeys: Vec<_> = self.pubkeys.keys().cloned().collect();
for recipient in pubkeys {
let msg = self
.encrypt_if_needed(&recipient, serde_json::to_vec(&send_signing_package_args)?)?;
let msg = self.encrypt(&recipient, serde_json::to_vec(&send_signing_package_args)?)?;
let _r = self
.client
.post(format!("{}/send", self.host_port))
@ -546,7 +542,7 @@ impl<C: Ciphersuite + 'static> Comms<C> for HTTPComms<C> {
.json::<server::ReceiveOutput>()
.await?;
for msg in r.msgs {
let msg = self.decrypt_if_needed(msg)?;
let msg = self.decrypt(msg)?;
self.state.recv(msg)?;
}
tokio::time::sleep(Duration::from_secs(2)).await;

View File

@ -26,11 +26,6 @@ pub struct Args {
#[arg(long, default_value_t = false)]
pub cli: bool,
/// HTTP mode. If enabled, it will use HTTP communication with a
/// FROST server.
#[arg(long, default_value_t = false)]
pub http: bool,
/// Public key package to use. Can be a file with a JSON-encoded
/// package, or "". If the file does not exist or if "" is specified,
/// then it will be read from standard input.
@ -110,7 +105,7 @@ impl<C: Ciphersuite + 'static> ProcessedArgs<C> {
Ok(ProcessedArgs {
cli: args.cli,
http: args.http,
http: false,
key_package,
ip: args.ip.clone(),
port: args.port,

View File

@ -132,29 +132,29 @@ where
})
}
// Encrypts a message for the coordinator if encryption is enabled.
fn encrypt_if_needed(&mut self, msg: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
if let Some(noise) = &mut self.send_noise {
let mut encrypted = vec![0; 65535];
let len = noise.write_message(&msg, &mut encrypted)?;
encrypted.truncate(len);
Ok(encrypted)
} else {
Ok(msg)
}
// Encrypts a message for the coordinator.
fn encrypt(&mut self, msg: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
let noise = self
.send_noise
.as_mut()
.expect("send_noise must have been set previously");
let mut encrypted = vec![0; 65535];
let len = noise.write_message(&msg, &mut encrypted)?;
encrypted.truncate(len);
Ok(encrypted)
}
// Decrypts a message from the coordinator if encryption is enabled.
fn decrypt_if_needed(&mut self, msg: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
if let Some(noise) = &mut self.recv_noise {
let mut decrypted = vec![0; 65535];
decrypted.resize(65535, 0);
let len = noise.read_message(&msg, &mut decrypted)?;
decrypted.truncate(len);
Ok(decrypted)
} else {
Ok(msg)
}
// Decrypts a message from the coordinator.
fn decrypt(&mut self, msg: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
let noise = self
.recv_noise
.as_mut()
.expect("recv_noise must have been set previously");
let mut decrypted = vec![0; 65535];
decrypted.resize(65535, 0);
let len = noise.read_message(&msg, &mut decrypted)?;
decrypted.truncate(len);
Ok(decrypted)
}
}
@ -241,60 +241,61 @@ where
};
self.session_id = Some(session_id);
// If encryption is enabled, create the Noise objects
(self.send_noise, self.recv_noise) = if let (
Some(comm_privkey),
Some(comm_coordinator_pubkey_getter),
) = (
let (Some(comm_privkey), Some(comm_coordinator_pubkey_getter)) = (
&self.args.comm_privkey,
&self.args.comm_coordinator_pubkey_getter,
) {
// We need to know what is the username of the coordinator in order
// to encrypt message to them.
let session_info = self
.client
.post(format!("{}/get_session_info", self.host_port))
.json(&server::GetSessionInfoArgs { session_id })
.bearer_auth(self.access_token.as_ref().expect("was just set"))
.send()
.await?
.json::<server::GetSessionInfoOutput>()
.await?;
let comm_coordinator_pubkey = comm_coordinator_pubkey_getter(&session_info.coordinator_pubkey).ok_or_eyre("The coordinator for the specified FROST session is not registered in the user's address book")?;
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
) else {
return Err(
eyre!("comm_privkey and comm_coordinator_pubkey_getter must be specified").into(),
);
let send_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_coordinator_pubkey)
.build_initiator()?,
);
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
);
let recv_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_coordinator_pubkey)
.build_responder()?,
);
(Some(send_noise), Some(recv_noise))
} else {
(None, None)
};
// If encryption is enabled, create the Noise objects
// We need to know what is the username of the coordinator in order
// to encrypt message to them.
let session_info = self
.client
.post(format!("{}/get_session_info", self.host_port))
.json(&server::GetSessionInfoArgs { session_id })
.bearer_auth(self.access_token.as_ref().expect("was just set"))
.send()
.await?
.json::<server::GetSessionInfoOutput>()
.await?;
let comm_coordinator_pubkey = comm_coordinator_pubkey_getter(&session_info.coordinator_pubkey).ok_or_eyre("The coordinator for the specified FROST session is not registered in the user's address book")?;
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
);
let send_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_coordinator_pubkey)
.build_initiator()?,
);
let builder = snow::Builder::new(
"Noise_K_25519_ChaChaPoly_BLAKE2s"
.parse()
.expect("should be a valid cipher"),
);
let recv_noise = Noise::new(
builder
.local_private_key(comm_privkey)
.remote_public_key(&comm_coordinator_pubkey)
.build_responder()?,
);
self.send_noise = Some(send_noise);
self.recv_noise = Some(recv_noise);
// Send Commitments to Server
let send_commitments_args = SendCommitmentsArgs {
identifier,
commitments: vec![commitments],
};
let msg = self.encrypt_if_needed(serde_json::to_vec(&send_commitments_args)?)?;
let msg = self.encrypt(serde_json::to_vec(&send_commitments_args)?)?;
self.client
.post(format!("{}/send", self.host_port))
.bearer_auth(self.access_token.as_ref().expect("was just set"))
@ -329,7 +330,7 @@ where
eprint!(".");
} else {
eprintln!("\nSigning package received");
let msg = self.decrypt_if_needed(r.msgs[0].msg.clone())?;
let msg = self.decrypt(r.msgs[0].msg.clone())?;
eprintln!("\n{}", String::from_utf8_lossy(&msg.clone()));
break serde_json::from_slice(&msg)?;
}
@ -365,7 +366,7 @@ where
signature_share: vec![signature_share],
};
let msg = self.encrypt_if_needed(serde_json::to_vec(&send_signature_shares_args)?)?;
let msg = self.encrypt(serde_json::to_vec(&send_signature_shares_args)?)?;
let _r = self
.client

View File

@ -45,7 +45,6 @@ async fn check_valid_round_1_inputs() {
ip: "0.0.0.0".to_string(),
port: 80,
session_id: "session-id".to_string(),
http: false,
};
let input = SECRET_SHARE_JSON;
let mut valid_input = input.as_bytes();

View File

@ -501,7 +501,7 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
// Test if passing the wrong session ID returns an error
let wrong_session_id = Uuid::new_v4();
let r = client
.post("http://127.0.0.1:2744/get_session_info")
.post("https://127.0.0.1:2744/get_session_info")
.bearer_auth(access_token)
.json(&server::GetSessionInfoArgs {
session_id: wrong_session_id,
@ -516,7 +516,7 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
// Attempt to close the session as a participant (Bob)
// Log in as Bob
let r = client
.post("http://127.0.0.1:2744/challenge")
.post("https://127.0.0.1:2744/challenge")
.json(&server::ChallengeArgs {})
.send()
.await?;
@ -526,7 +526,7 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
xed25519::PrivateKey::from(&TryInto::<[u8; 32]>::try_into(bob_keypair.private).unwrap());
let bob_signature: [u8; 64] = bob_private.sign(bob_challenge.as_bytes(), &mut rng);
let r = client
.post("http://127.0.0.1:2744/login")
.post("https://127.0.0.1:2744/login")
.json(&server::KeyLoginArgs {
uuid: bob_challenge,
pubkey: bob_keypair.public.clone(),
@ -538,7 +538,7 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
let bob_access_token = r.access_token;
// Try to close the session
let r = client
.post("http://127.0.0.1:2744/close_session")
.post("https://127.0.0.1:2744/close_session")
.bearer_auth(bob_access_token)
.json(&server::CloseSessionArgs { session_id })
.send()