0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-19 19:42:19 +02:00

SVR - Add new protocol Rotate trait and test.

Co-authored-by: Max Moiseev <moiseev@signal.org>
This commit is contained in:
gram-signal 2024-08-30 16:28:29 -07:00 committed by GitHub
parent f142c25b72
commit 1c2113617f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 102 additions and 7 deletions

View File

@ -97,6 +97,7 @@ pub enum SignalErrorCode {
SvrDataMissing = 150,
SvrRestoreFailed = 151,
SvrRotationMachineTooManySteps = 152,
AppExpired = 160,
DeviceDeregistered = 161,
@ -490,7 +491,10 @@ impl FfiError for Svr3Error {
Self::Service(e) => format!("WebSocket error: {e}"),
Self::Protocol(e) => format!("Protocol error: {e}"),
Self::AttestationError(inner) => inner.describe(),
Self::RequestFailed(_) | Self::RestoreFailed(_) | Self::DataMissing => {
Self::RequestFailed(_)
| Self::RestoreFailed(_)
| Self::DataMissing
| Self::RotationMachineTooManySteps => {
format!("SVR error: {self}")
}
}
@ -511,6 +515,7 @@ impl FfiError for Svr3Error {
Self::RequestFailed(_) => SignalErrorCode::UnknownError,
Self::RestoreFailed(_) => SignalErrorCode::SvrRestoreFailed,
Self::DataMissing => SignalErrorCode::SvrDataMissing,
Self::RotationMachineTooManySteps => SignalErrorCode::SvrRotationMachineTooManySteps,
}
}

View File

@ -295,7 +295,8 @@ impl From<Svr3Error> for SignalJniError {
Svr3Error::Protocol(_)
| Svr3Error::RequestFailed(_)
| Svr3Error::RestoreFailed(_)
| Svr3Error::DataMissing => SignalJniError::Svr3(err),
| Svr3Error::DataMissing
| Svr3Error::RotationMachineTooManySteps => SignalJniError::Svr3(err),
}
}
}

View File

@ -136,6 +136,7 @@ const INVALID_MEDIA_INPUT: &str = "InvalidMediaInput";
const IO_ERROR: &str = "IoError";
const RATE_LIMITED_ERROR: &str = "RateLimitedError";
const SVR3_DATA_MISSING: &str = "SvrDataMissing";
const SVR3_ROTATION_MACHINE_STEPS: &str = "SvrRotationMachineTooManySteps";
const SVR3_REQUEST_FAILED: &str = "SvrRequestFailed";
const SVR3_RESTORE_FAILED: &str = "SvrRestoreFailed";
const UNSUPPORTED_MEDIA_INPUT: &str = "UnsupportedMediaInput";
@ -504,6 +505,7 @@ impl SignalNodeError for libsignal_net::svr3::Error {
),
Svr3Error::DataMissing => (Some(SVR3_DATA_MISSING), None),
Svr3Error::Protocol(_) => (None, None),
Svr3Error::RotationMachineTooManySteps => (Some(SVR3_ROTATION_MACHINE_STEPS), None),
};
let message = self.to_string();

View File

@ -91,6 +91,16 @@ async fn main() {
};
println!("{}: {}", "Share set".cyan(), hex::encode(&share_set_bytes));
{
println!("{}", "Rotating secret".cyan());
let opaque_share_set =
OpaqueMaskedShareSet::deserialize(&share_set_bytes).expect("can deserialize");
client
.rotate(opaque_share_set, &mut rng)
.await
.expect("can rotate");
};
let restored = {
let opaque_share_set =
OpaqueMaskedShareSet::deserialize(&share_set_bytes).expect("can deserialize");

View File

@ -146,6 +146,8 @@ pub enum Error {
DataMissing,
/// Connect timed out
ConnectionTimedOut,
/// Rotation machine took too many steps
RotationMachineTooManySteps,
}
impl From<DeserializeError> for Error {

View File

@ -12,7 +12,10 @@ use std::num::NonZeroU32;
use std::sync::Arc;
use futures_util::future::join_all;
use libsignal_svr3::{Backup4, EvaluationResult, MaskedSecret, Query4, Remove4, Restore1};
use libsignal_svr3::{
Backup4, EvaluationResult, MaskedSecret, Query4, Remove4, Restore1, RotationMachine,
MAX_ROTATION_STEPS,
};
use rand_core::CryptoRngCore;
use super::{Error, OpaqueMaskedShareSet};
@ -158,6 +161,46 @@ pub async fn do_query<S: AsyncDuplexStream + 'static>(
Ok(Query4::finalize(&responses)?)
}
pub async fn do_rotate<S: AsyncDuplexStream + 'static>(
connect_results: impl IntoConnectionResults<Stream = S>,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<(), Error> {
let ConnectionContext {
mut connections,
addresses,
errors,
} = ConnectionContext::new(connect_results);
if let Some(err) = errors.into_iter().next() {
return Err(err);
}
let masked_secret: MaskedSecret = share_set.into_inner();
let mut rotation_machine = RotationMachine::new(masked_secret.server_ids.as_ref(), rng);
for _ in 0..MAX_ROTATION_STEPS {
if rotation_machine.is_done() {
break;
}
let requests = rotation_machine.requests();
let futures = connections
.iter_mut()
.zip(&requests)
.map(|(connection, request)| run_attested_interaction(connection, request));
let results = join_all(futures)
.await
.into_iter()
.collect::<Result<Vec<_>, _>>();
let responses = collect_responses(results?, addresses.iter())?;
rotation_machine.handle_responses(responses.as_ref())?;
}
if rotation_machine.is_done() {
Ok(())
} else {
Err(Error::RotationMachineTooManySteps)
}
}
struct ConnectionContext<S> {
connections: Vec<AttestedConnection<S>>,
addresses: Vec<Host<Arc<str>>>,

View File

@ -48,6 +48,15 @@ pub trait Remove {
async fn remove(&self) -> Result<(), Error>;
}
#[async_trait]
pub trait Rotate {
async fn rotate(
&self,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<(), Error>;
}
#[async_trait]
pub trait Svr3Connect {
// Stream is needed for the blanket implementation,
@ -118,3 +127,18 @@ where
ppss_ops::do_query(self.connect().await).await
}
}
#[async_trait]
impl<T> Rotate for T
where
T: Svr3Connect + Sync,
T::Stream: AsyncDuplexStream + 'static,
{
async fn rotate(
&self,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<(), Error> {
ppss_ops::do_rotate(self.connect().await, share_set, rng).await
}
}

View File

@ -493,6 +493,8 @@ enum RotationAction {
Rollback(u32),
}
pub const MAX_ROTATION_STEPS: usize = 4;
/// RotationMachineState defines a simple state machine for performing
/// a client-initiated rotation. Clients start in the InitialQuery state
/// and attempt to achieve the Done state. The following are the
@ -573,14 +575,14 @@ enum RotationMachineState {
/// Important: The order of request and response vectors matter: if
/// server.ids()[1] is X, then requests[1] is meant for server X
/// and responses[1] should be the response received from X.
struct RotationMachine<'a> {
pub struct RotationMachine<'a> {
pub server_ids: &'a [u64],
rng: &'a mut dyn CryptoRngCore,
rng: &'a mut (dyn CryptoRngCore + Send),
state: RotationMachineState,
}
impl<'a> RotationMachine<'a> {
pub fn new<R: CryptoRngCore>(server_ids: &'a [u64], rng: &'a mut R) -> Self {
pub fn new<R: CryptoRngCore + Send>(server_ids: &'a [u64], rng: &'a mut R) -> Self {
Self {
server_ids,
rng,

View File

@ -16,7 +16,9 @@ pub use ppss::{MaskedShareSet, OPRFSession};
mod errors;
pub use errors::{Error, ErrorStatus, OPRFError, PPSSError};
mod proto;
pub use client::{Backup4, MaskedSecret, Query4, Remove4, Restore1, Restore2};
pub use client::{
Backup4, MaskedSecret, Query4, Remove4, Restore1, Restore2, RotationMachine, MAX_ROTATION_STEPS,
};
use proto::svr3::{self, create_response, evaluate_response, query_response};
pub use proto::svr4::response4::Status as V4Status;

View File

@ -58,6 +58,7 @@ public enum SignalError: Error {
case rateLimitedError(retryAfter: TimeInterval, message: String)
case svrDataMissing(String)
case svrRestoreFailed(triesRemaining: UInt32, message: String)
case svrRotationMachineTooManySteps(String)
case chatServiceInactive(String)
case appExpired(String)
case deviceDeregistered(String)
@ -196,6 +197,8 @@ internal func checkError(_ error: SignalFfiErrorRef?) throws {
signal_error_get_tries_remaining(error, $0)
}
throw SignalError.svrRestoreFailed(triesRemaining: triesRemaining, message: errStr)
case SignalErrorCodeSvrRotationMachineTooManySteps:
throw SignalError.svrRotationMachineTooManySteps(errStr)
case SignalErrorCodeChatServiceInactive:
throw SignalError.chatServiceInactive(errStr)
case SignalErrorCodeAppExpired:

View File

@ -192,6 +192,7 @@ typedef enum {
SignalErrorCodeChatServiceInactive = 139,
SignalErrorCodeSvrDataMissing = 150,
SignalErrorCodeSvrRestoreFailed = 151,
SignalErrorCodeSvrRotationMachineTooManySteps = 152,
SignalErrorCodeAppExpired = 160,
SignalErrorCodeDeviceDeregistered = 161,
SignalErrorCodeBackupValidation = 170,