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:
parent
f142c25b72
commit
1c2113617f
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -146,6 +146,8 @@ pub enum Error {
|
||||
DataMissing,
|
||||
/// Connect timed out
|
||||
ConnectionTimedOut,
|
||||
/// Rotation machine took too many steps
|
||||
RotationMachineTooManySteps,
|
||||
}
|
||||
|
||||
impl From<DeserializeError> for Error {
|
||||
|
@ -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>>>,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
@ -192,6 +192,7 @@ typedef enum {
|
||||
SignalErrorCodeChatServiceInactive = 139,
|
||||
SignalErrorCodeSvrDataMissing = 150,
|
||||
SignalErrorCodeSvrRestoreFailed = 151,
|
||||
SignalErrorCodeSvrRotationMachineTooManySteps = 152,
|
||||
SignalErrorCodeAppExpired = 160,
|
||||
SignalErrorCodeDeviceDeregistered = 161,
|
||||
SignalErrorCodeBackupValidation = 170,
|
||||
|
Loading…
Reference in New Issue
Block a user