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

svr3: Implement restore with fallback function

This commit is contained in:
moiseev-signal 2024-07-08 14:03:51 -07:00 committed by GitHub
parent 4694d0a1a8
commit c870e23a5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 495 additions and 210 deletions

View File

@ -7,13 +7,13 @@ use std::convert::TryInto as _;
use std::num::{NonZeroU16, NonZeroU32};
use base64::prelude::{Engine, BASE64_STANDARD};
use libsignal_bridge_macros::{bridge_fn, bridge_io};
use libsignal_bridge_types::net::svr3_connect;
use libsignal_net::auth::Auth;
use libsignal_net::env::Svr3Env;
use libsignal_net::svr3::{self, OpaqueMaskedShareSet, PpssOps as _};
use rand::rngs::OsRng;
use libsignal_bridge_macros::{bridge_fn, bridge_io};
use libsignal_bridge_types::net::Svr3Client;
use libsignal_net::auth::Auth;
use libsignal_net::svr3::{self, OpaqueMaskedShareSet, Svr3Client as _};
pub use libsignal_bridge_types::net::{ConnectionManager, Environment, TokioAsyncContext};
use crate::support::*;
@ -80,15 +80,10 @@ async fn Svr3Backup(
.try_into()
.expect("can only backup 32 bytes");
let mut rng = OsRng;
let connections = svr3_connect(connection_manager, username, enclave_password).await?;
let share_set = Svr3Env::backup(
connections,
&password,
secret,
max_tries.into_inner(),
&mut rng,
)
.await?;
let client = Svr3Client::new(connection_manager, username, enclave_password);
let share_set = client
.backup(&password, secret, max_tries.into_inner(), &mut rng)
.await?;
Ok(share_set.serialize().expect("can serialize the share set"))
}
@ -102,8 +97,8 @@ async fn Svr3Restore(
) -> Result<Vec<u8>, svr3::Error> {
let mut rng = OsRng;
let share_set = OpaqueMaskedShareSet::deserialize(&share_set)?;
let connections = svr3_connect(connection_manager, username, enclave_password).await?;
let restored_secret = Svr3Env::restore(connections, &password, share_set, &mut rng).await?;
let client = Svr3Client::new(connection_manager, username, enclave_password);
let restored_secret = client.restore(&password, share_set, &mut rng).await?;
Ok(restored_secret.serialize())
}
@ -113,8 +108,8 @@ async fn Svr3Remove(
username: String, // hex-encoded uid
enclave_password: String, // timestamp:otp(...)
) -> Result<(), svr3::Error> {
let connections = svr3_connect(connection_manager, username, enclave_password).await?;
Svr3Env::remove(connections).await?;
let client = Svr3Client::new(connection_manager, username, enclave_password);
client.remove().await?;
Ok(())
}

View File

@ -6,12 +6,13 @@
use std::num::NonZeroU16;
use std::panic::RefUnwindSafe;
use async_trait::async_trait;
use http::uri::PathAndQuery;
use libsignal_net::auth::Auth;
use libsignal_net::enclave::{
Cdsi, EnclaveEndpoint, EnclaveEndpointConnection, EnclaveKind, Nitro, PpssSetup, Sgx, Tpm2Snp,
};
use libsignal_net::env;
use libsignal_net::env::{add_user_agent_header, Env, Svr3Env};
use libsignal_net::infra::connection_manager::MultiRouteConnectionManager;
use libsignal_net::infra::dns::DnsResolver;
@ -20,7 +21,8 @@ use libsignal_net::infra::tcp_ssl::{
TcpSslConnector, TcpSslConnectorStream,
};
use libsignal_net::infra::{make_ws_config, EndpointConnection};
use libsignal_net::svr::{self, SvrConnection};
use libsignal_net::svr::SvrConnection;
use libsignal_net::svr3::Svr3Connect;
use libsignal_net::timeouts::ONE_ROUTE_CONNECTION_TIMEOUT;
use crate::*;
@ -68,7 +70,8 @@ impl ConnectionManager {
DnsResolver::new_with_static_fallback(environment.env().static_fallback());
let transport_connector =
std::sync::Mutex::new(TcpSslDirectConnector::new(dns_resolver).into());
let chat_endpoint = PathAndQuery::from_static(env::constants::WEB_SOCKET_PATH);
let chat_endpoint =
PathAndQuery::from_static(libsignal_net::env::constants::WEB_SOCKET_PATH);
let chat_connection_params = environment
.env()
.chat_domain_config
@ -150,23 +153,48 @@ impl ConnectionManager {
bridge_as_handle!(ConnectionManager);
pub async fn svr3_connect<'a>(
connection_manager: &ConnectionManager,
username: String,
password: String,
) -> Result<<Svr3Env<'a> as PpssSetup<TcpSslConnectorStream>>::Connections, svr::Error> {
let auth = Auth { username, password };
let ConnectionManager {
chat: _chat,
cdsi: _cdsi,
svr3: (sgx, nitro, tpm2snp),
transport_connector,
} = connection_manager;
let transport_connector = transport_connector.lock().expect("not poisoned").clone();
let sgx = SvrConnection::connect(auth.clone(), sgx, transport_connector.clone()).await?;
let nitro = SvrConnection::connect(auth.clone(), nitro, transport_connector.clone()).await?;
let tpm2snp = SvrConnection::connect(auth, tpm2snp, transport_connector).await?;
Ok((sgx, nitro, tpm2snp))
pub struct Svr3Client<'a> {
connection_manager: &'a ConnectionManager,
auth: Auth,
}
impl<'a> Svr3Client<'a> {
pub fn new(
connection_manager: &'a ConnectionManager,
username: String,
password: String,
) -> Self {
Svr3Client {
connection_manager,
auth: Auth { username, password },
}
}
}
#[async_trait]
impl<'a> Svr3Connect for Svr3Client<'a> {
type Stream = TcpSslConnectorStream;
type Env = Svr3Env<'static>;
async fn connect(
&self,
) -> Result<<Self::Env as PpssSetup<Self::Stream>>::Connections, libsignal_net::enclave::Error>
{
let ConnectionManager {
chat: _chat,
cdsi: _cdsi,
svr3: (sgx, nitro, tpm2snp),
transport_connector,
} = &self.connection_manager;
let transport_connector = transport_connector.lock().expect("not poisoned").clone();
let sgx =
SvrConnection::connect(self.auth.clone(), sgx, transport_connector.clone()).await?;
let nitro =
SvrConnection::connect(self.auth.clone(), nitro, transport_connector.clone()).await?;
let tpm2snp =
SvrConnection::connect(self.auth.clone(), tpm2snp, transport_connector).await?;
Ok((sgx, nitro, tpm2snp))
}
}
#[cfg(test)]

View File

@ -8,22 +8,23 @@
//! as well as the password that will be used to protect the data being stored. Since the
//! actual stored secret data needs to be exactly 32 bytes long, it is generated randomly
//! at each invocation instead of being passed via the command line.
use std::time::Duration;
use assert_matches::assert_matches;
use async_trait::async_trait;
use base64::prelude::{Engine, BASE64_STANDARD};
use clap::Parser;
use colored::Colorize as _;
use libsignal_net::infra::dns::DnsResolver;
use nonzero_ext::nonzero;
use rand_core::{CryptoRngCore, OsRng, RngCore};
use libsignal_net::auth::Auth;
use libsignal_net::enclave::{EnclaveEndpointConnection, Nitro, Sgx, Tpm2Snp};
use libsignal_net::enclave::PpssSetup;
use libsignal_net::env::Svr3Env;
use libsignal_net::infra::tcp_ssl::DirectConnector as TcpSslTransportConnector;
use libsignal_net::svr::SvrConnection;
use libsignal_net::svr3::{Error, OpaqueMaskedShareSet, PpssOps};
use libsignal_net::infra::tcp_ssl::DirectConnector;
use libsignal_net::infra::TransportConnector;
use libsignal_net::svr3::{
simple_svr3_connect, Error, OpaqueMaskedShareSet, Svr3Client as _, Svr3Connect,
};
#[derive(Parser, Debug)]
struct Args {
@ -34,6 +35,26 @@ struct Args {
#[arg(long)]
password: String,
}
struct Svr3Client {
env: Svr3Env<'static>,
auth: Auth,
}
type Stream = <DirectConnector as TransportConnector>::Stream;
#[async_trait]
impl Svr3Connect for Svr3Client {
type Stream = Stream;
type Env = Svr3Env<'static>;
async fn connect(
&self,
) -> Result<<Svr3Env as PpssSetup<Stream>>::Connections, libsignal_net::enclave::Error> {
simple_svr3_connect(&self.env, &self.auth).await
}
}
#[tokio::main]
async fn main() {
init_logger();
@ -49,36 +70,16 @@ async fn main() {
let mut rng = OsRng;
let env = libsignal_net::env::STAGING.svr3;
let uid = {
let mut bytes = [0u8; 16];
rng.fill_bytes(&mut bytes[..]);
bytes
};
let auth = Auth::from_uid_and_secret(uid, enclave_secret);
let connect = || async {
let connector = TcpSslTransportConnector::new(DnsResolver::default());
let connection_a = EnclaveEndpointConnection::new_multi(
env.sgx(),
env.sgx().domain_config.connection_params_with_fallback(),
Duration::from_secs(10),
);
let a = SvrConnection::<Sgx, _>::connect(auth.clone(), &connection_a, connector.clone())
.await
.expect("can attestedly connect to SGX");
let connection_b = EnclaveEndpointConnection::new(env.nitro(), Duration::from_secs(10));
let b = SvrConnection::<Nitro, _>::connect(auth.clone(), &connection_b, connector.clone())
.await
.expect("can attestedly connect to Nitro");
let connection_c = EnclaveEndpointConnection::new(env.tpm2snp(), Duration::from_secs(10));
let c = SvrConnection::<Tpm2Snp, _>::connect(auth.clone(), &connection_c, connector)
.await
.expect("can attestedly connect to Tpm2Snp");
(a, b, c)
let client = {
let env = libsignal_net::env::STAGING.svr3;
let auth = Auth::from_uid_and_secret(uid, enclave_secret);
Svr3Client { env, auth }
};
let secret = make_secret(&mut rng);
@ -86,10 +87,10 @@ async fn main() {
let tries = nonzero!(10u32);
let share_set_bytes = {
let opaque_share_set =
Svr3Env::backup(connect().await, &args.password, secret, tries, &mut rng)
.await
.expect("can multi backup");
let opaque_share_set = client
.backup(&args.password, secret, tries, &mut rng)
.await
.expect("can multi backup");
opaque_share_set.serialize().expect("can serialize")
};
println!("{}: {}", "Share set".cyan(), hex::encode(&share_set_bytes));
@ -97,7 +98,8 @@ async fn main() {
let restored = {
let opaque_share_set =
OpaqueMaskedShareSet::deserialize(&share_set_bytes).expect("can deserialize");
Svr3Env::restore(connect().await, &args.password, opaque_share_set, &mut rng)
client
.restore(&args.password, opaque_share_set, &mut rng)
.await
.expect("can mutli restore")
};
@ -112,17 +114,18 @@ async fn main() {
println!("{}: {}", "Tries remaining".cyan(), restored.tries_remaining);
println!("{}...", "Querying...".cyan());
let query_result = Svr3Env::query(connect().await).await.expect("can query");
let query_result = client.query().await.expect("can query");
println!("{}: {}", "Tries remaining".cyan(), query_result);
println!("{}...", "Removing the secret".cyan());
Svr3Env::remove(connect().await).await.expect("can remove");
client.remove().await.expect("can remove");
// The next attempt to restore should fail
{
let opaque_share_set =
OpaqueMaskedShareSet::deserialize(&share_set_bytes).expect("can deserialize");
let failed_restore_result =
Svr3Env::restore(connect().await, &args.password, opaque_share_set, &mut rng).await;
let failed_restore_result = client
.restore(&args.password, opaque_share_set, &mut rng)
.await;
assert_matches!(failed_restore_result, Err(Error::DataMissing));
}
println!("{}.", "Done".green());

View File

@ -12,24 +12,26 @@
use std::borrow::Cow;
use std::time::Duration;
use async_trait::async_trait;
use attest::svr2::RaftConfig;
use base64::prelude::{Engine, BASE64_STANDARD};
use clap::Parser;
use hex_literal::hex;
use libsignal_net::infra::dns::DnsResolver;
use nonzero_ext::nonzero;
use rand_core::{CryptoRngCore, OsRng, RngCore};
use attest::svr2::RaftConfig;
use libsignal_net::auth::Auth;
use libsignal_net::enclave::{
EnclaveEndpoint, EnclaveEndpointConnection, EndpointParams, MrEnclave, PpssSetup, Sgx,
EnclaveEndpoint, EnclaveEndpointConnection, EndpointParams, Error, MrEnclave, PpssSetup, Sgx,
Svr3Flavor,
};
use libsignal_net::env::{DomainConfig, PROXY_CONFIG_F_STAGING, PROXY_CONFIG_G};
use libsignal_net::infra::certs::RootCertificates;
use libsignal_net::infra::dns::DnsResolver;
use libsignal_net::infra::tcp_ssl::DirectConnector as TcpSslTransportConnector;
use libsignal_net::infra::TransportConnector;
use libsignal_net::svr::SvrConnection;
use libsignal_net::svr3::{OpaqueMaskedShareSet, PpssOps};
use libsignal_net::svr3::{OpaqueMaskedShareSet, Svr3Client as _, Svr3Connect};
const TEST_SERVER_CERT: RootCertificates = RootCertificates::FromDer(Cow::Borrowed(
include_bytes!("../res/sgx_test_server_cert.cer"),
@ -69,6 +71,7 @@ where
B: Svr3Flavor + Send,
S: Send,
{
type Stream = <TcpSslTransportConnector as TransportConnector>::Stream;
type Connections = (SvrConnection<A, S>, SvrConnection<B, S>);
type ServerIds = [u64; 2];
@ -87,6 +90,31 @@ struct Args {
password: String,
}
struct Client {
env: TwoForTwoEnv<'static, Sgx, Sgx>,
auth_a: Auth,
auth_b: Auth,
}
#[async_trait]
impl Svr3Connect for Client {
type Stream = <TcpSslTransportConnector as TransportConnector>::Stream;
type Env = TwoForTwoEnv<'static, Sgx, Sgx>;
async fn connect(&self) -> Result<<Self::Env as PpssSetup<Self::Stream>>::Connections, Error> {
let connector = TcpSslTransportConnector::new(DnsResolver::default());
let connection_a = EnclaveEndpointConnection::new(&self.env.0, Duration::from_secs(10));
let a =
SvrConnection::connect(self.auth_a.clone(), &connection_a, connector.clone()).await?;
let connection_b = EnclaveEndpointConnection::new(&self.env.1, Duration::from_secs(10));
let b = SvrConnection::connect(self.auth_b.clone(), &connection_b, connector).await?;
Ok((a, b))
}
}
#[tokio::main]
async fn main() {
let args = Args::parse();
@ -101,14 +129,6 @@ async fn main() {
let mut rng = OsRng;
let mut make_uid = || {
let mut bytes = [0u8; 16];
rng.fill_bytes(&mut bytes[..]);
bytes
};
let make_auth = |uid: [u8; 16]| Auth::from_uid_and_secret(uid, auth_secret);
let two_sgx_env = {
let endpoint = EnclaveEndpoint::<Sgx> {
domain_config: TEST_SERVER_DOMAIN_CONFIG,
@ -117,37 +137,30 @@ async fn main() {
TwoForTwoEnv(endpoint.clone(), endpoint)
};
let (uid_a, uid_b) = (make_uid(), make_uid());
let client = {
let mut make_uid = || {
let mut bytes = [0u8; 16];
rng.fill_bytes(&mut bytes[..]);
bytes
};
let connect = || async {
let connector = TcpSslTransportConnector::new(DnsResolver::default());
let connection_a = EnclaveEndpointConnection::new(&two_sgx_env.0, Duration::from_secs(10));
let make_auth = |uid: [u8; 16]| Auth::from_uid_and_secret(uid, auth_secret);
let a = SvrConnection::connect(make_auth(uid_a), &connection_a, connector.clone())
.await
.expect("can attestedly connect");
let connection_b = EnclaveEndpointConnection::new(&two_sgx_env.1, Duration::from_secs(10));
let b = SvrConnection::connect(make_auth(uid_b), &connection_b, connector)
.await
.expect("can attestedly connect");
(a, b)
Client {
env: two_sgx_env,
auth_a: make_auth(make_uid()),
auth_b: make_auth(make_uid()),
}
};
let secret = make_secret(&mut rng);
println!("Secret to be stored: {}", hex::encode(secret));
let share_set_bytes = {
let opaque_share_set = TwoForTwoEnv::backup(
connect().await,
&args.password,
secret,
nonzero!(10u32),
&mut rng,
)
.await
.expect("can multi backup");
let opaque_share_set = client
.backup(&args.password, secret, nonzero!(10u32), &mut rng)
.await
.expect("can multi backup");
opaque_share_set.serialize().expect("can serialize")
};
println!("Share set: {}", hex::encode(&share_set_bytes));
@ -155,7 +168,8 @@ async fn main() {
let restored = {
let opaque_share_set =
OpaqueMaskedShareSet::deserialize(&share_set_bytes).expect("can deserialize");
TwoForTwoEnv::restore(connect().await, &args.password, opaque_share_set, &mut rng)
client
.restore(&args.password, opaque_share_set, &mut rng)
.await
.expect("can multi restore")
};

View File

@ -7,20 +7,21 @@ use std::collections::HashMap;
use std::time::Duration;
use assert_matches::assert_matches;
use libsignal_net::infra::dns::DnsResolver;
use libsignal_net::infra::ws::DefaultStream;
use async_trait::async_trait;
use proptest::prelude::*;
use proptest::test_runner::Config;
use proptest_state_machine::{prop_state_machine, ReferenceStateMachine, StateMachineTest};
use rand_core::OsRng;
use libsignal_net::auth::Auth;
use libsignal_net::enclave::{EnclaveEndpointConnection, Nitro, PpssSetup, Sgx, Tpm2Snp};
use libsignal_net::enclave::{self, PpssSetup};
use libsignal_net::env::Svr3Env;
use libsignal_net::infra::tcp_ssl::DirectConnector as TcpSslTransportConnector;
use libsignal_net::svr::SvrConnection;
use libsignal_net::svr3::{Error, OpaqueMaskedShareSet, PpssOps as _};
use libsignal_net::infra::ws::DefaultStream;
use libsignal_net::svr3::{
simple_svr3_connect, Error, OpaqueMaskedShareSet, Svr3Client, Svr3Connect,
};
use libsignal_svr3::EvaluationResult;
use support::*;
const MAX_TRIES_LIMIT: u32 = 10;
@ -296,6 +297,39 @@ impl StateMachineTest for Svr3Storage {
}
}
struct Client<'a> {
auth: Auth,
env: &'a Svr3Env<'static>,
config: &'a SUTConfig,
}
impl<'a> Client<'a> {
fn new(uid: Uid, storage: &'a Svr3Storage) -> Self {
let auth = Auth::from_uid_and_secret(uid, storage.enclave_secret);
Self {
auth,
env: &storage.env,
config: &storage.config,
}
}
}
#[async_trait]
impl Svr3Connect for Client<'_> {
type Stream = DefaultStream;
type Env = Svr3Env<'static>;
async fn connect(
&self,
) -> Result<<Self::Env as PpssSetup<Self::Stream>>::Connections, enclave::Error> {
if let Some(duration) = self.config.sleep {
log::info!("💤 to avoid throttling...");
tokio::time::sleep(duration).await;
}
simple_svr3_connect(self.env, &self.auth).await
}
}
impl Svr3Storage {
fn new() -> Self {
let enclave_secret = {
@ -319,47 +353,14 @@ impl Svr3Storage {
}
}
async fn connect(&self, uid: Uid) -> <Svr3Env as PpssSetup<DefaultStream>>::Connections {
let connector = TcpSslTransportConnector::new(DnsResolver::default());
if let Some(duration) = self.config.sleep {
tokio::time::sleep(duration).await;
}
let auth = Auth::from_uid_and_secret(uid, self.enclave_secret);
let sgx_connection =
EnclaveEndpointConnection::new(self.env.sgx(), Duration::from_secs(10));
let a = SvrConnection::<Sgx, _>::connect(auth.clone(), &sgx_connection, connector.clone())
.await
.expect("can attestedly connect to SGX");
let nitro_connection =
EnclaveEndpointConnection::new(self.env.nitro(), Duration::from_secs(10));
let b =
SvrConnection::<Nitro, _>::connect(auth.clone(), &nitro_connection, connector.clone())
.await
.expect("can attestedly connect to Nitro");
let tpm2snp_connection =
EnclaveEndpointConnection::new(self.env.tpm2snp(), Duration::from_secs(10));
let c = SvrConnection::<Tpm2Snp, _>::connect(auth.clone(), &tpm2snp_connection, connector)
.await
.expect("can attestedly connect to Nitro");
(a, b, c)
}
fn backup(&mut self, uid: Uid, what: Secret, max_tries: u32) -> OpaqueMaskedShareSet {
self.runtime.block_on(async {
let mut rng = OsRng;
let connections = self.connect(uid).await;
Svr3Env::backup(
connections,
"password",
what,
max_tries.try_into().unwrap(),
&mut rng,
)
.await
.expect("can backup")
let client = Client::new(uid, self);
client
.backup("password", what, max_tries.try_into().unwrap(), &mut rng)
.await
.expect("can backup")
})
}
@ -371,8 +372,8 @@ impl Svr3Storage {
) -> Result<EvaluationResult, Error> {
self.runtime.block_on(async {
let mut rng = OsRng;
let connections = self.connect(uid).await;
Svr3Env::restore(connections, password, share_set, &mut rng).await
let client = Client::new(uid, self);
client.restore(password, share_set, &mut rng).await
})
}
}

View File

@ -155,6 +155,7 @@ impl<T, const N: usize> ArrayIsh<T> for [T; N] {
}
pub trait PpssSetup<S> {
type Stream;
type Connections: IntoConnections<Stream = S> + Send;
type ServerIds: ArrayIsh<u64> + Send;
const N: usize = Self::ServerIds::N;
@ -162,6 +163,7 @@ pub trait PpssSetup<S> {
}
impl<S: Send> PpssSetup<S> for Svr3Env<'_> {
type Stream = S;
type Connections = (
SvrConnection<Sgx, S>,
SvrConnection<Nitro, S>,

View File

@ -3,22 +3,28 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
use thiserror::Error;
use std::num::NonZeroU32;
use std::time::Duration;
use crate::enclave::{IntoConnections, PpssSetup};
use crate::infra::errors::LogSafeDisplay;
use crate::infra::ws::{
run_attested_interaction, AttestedConnectionError, NextOrClose, WebSocketConnectError,
WebSocketServiceError,
};
use crate::infra::AsyncDuplexStream;
use async_trait::async_trait;
use bincode::Options as _;
use futures_util::future::try_join_all;
use libsignal_svr3::{Backup, EvaluationResult, MaskedShareSet, Query, Restore};
use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize};
use std::num::NonZeroU32;
use thiserror::Error;
use libsignal_svr3::{EvaluationResult, MaskedShareSet};
use crate::auth::Auth;
use crate::enclave::{self, EnclaveEndpointConnection, Nitro, PpssSetup, Sgx, Tpm2Snp};
use crate::env::Svr3Env;
use crate::infra::dns::DnsResolver;
use crate::infra::errors::LogSafeDisplay;
use crate::infra::tcp_ssl::DirectConnector;
use crate::infra::ws::{
AttestedConnectionError, DefaultStream, WebSocketConnectError, WebSocketServiceError,
};
use crate::infra::AsyncDuplexStream;
use crate::svr::SvrConnection;
const MASKED_SHARE_SET_FORMAT: u8 = 0;
@ -199,37 +205,30 @@ impl From<AttestedConnectionError> for Error {
}
}
#[async_trait]
pub trait PpssOps<S>: PpssSetup<S> {
async fn backup(
connections: Self::Connections,
password: &str,
secret: [u8; 32],
max_tries: NonZeroU32,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<OpaqueMaskedShareSet, Error>;
/// High level data operations on instances of `PpssSetup`
///
/// These functions are useful if we ever want to perform multiple operations
/// on the same set of open connections, as opposed to having to connect for
/// each individual operation, as implied by `Svr3Client` trait.
mod ppss_ops {
use super::{Error, OpaqueMaskedShareSet};
async fn restore(
connections: Self::Connections,
password: &str,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<EvaluationResult, Error>;
use crate::enclave::{IntoConnections, PpssSetup};
use crate::infra::ws::{run_attested_interaction, NextOrClose};
use crate::infra::AsyncDuplexStream;
use futures_util::future::try_join_all;
use libsignal_svr3::{Backup, EvaluationResult, Query, Restore};
use rand_core::CryptoRngCore;
use std::num::NonZeroU32;
async fn remove(connections: Self::Connections) -> Result<(), Error>;
async fn query(connections: Self::Connections) -> Result<u32, Error>;
}
#[async_trait]
impl<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>> PpssOps<S> for Env {
async fn backup(
connections: Self::Connections,
pub async fn do_backup<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>>(
connections: Env::Connections,
password: &str,
secret: [u8; 32],
max_tries: NonZeroU32,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<OpaqueMaskedShareSet, Error> {
let server_ids = Self::server_ids();
let server_ids = Env::server_ids();
let backup = Backup::new(server_ids.as_ref(), password, secret, max_tries, rng)?;
let mut connections = connections.into_connections();
let futures = connections
@ -244,8 +243,8 @@ impl<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>> PpssOps<S> for Env {
Ok(OpaqueMaskedShareSet::new(share_set))
}
async fn restore(
connections: Self::Connections,
pub async fn do_restore<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>>(
connections: Env::Connections,
password: &str,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
@ -263,7 +262,9 @@ impl<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>> PpssOps<S> for Env {
Ok(restore.finalize(&responses)?)
}
async fn remove(connections: Self::Connections) -> Result<(), Error> {
pub async fn do_remove<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>>(
connections: Env::Connections,
) -> Result<(), Error> {
let requests = std::iter::repeat(libsignal_svr3::make_remove_request());
let mut connections = connections.into_connections();
let futures = connections
@ -278,7 +279,9 @@ impl<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>> PpssOps<S> for Env {
Ok(())
}
async fn query(connections: Self::Connections) -> Result<u32, Error> {
pub async fn do_query<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>>(
connections: Env::Connections,
) -> Result<u32, Error> {
let mut connections = connections.into_connections();
let futures = connections
.as_mut()
@ -290,25 +293,156 @@ impl<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>> PpssOps<S> for Env {
let responses = collect_responses(results, addresses)?;
Ok(Query::finalize(&responses)?)
}
fn collect_responses<'a>(
results: impl IntoIterator<Item = NextOrClose<Vec<u8>>>,
addresses: impl IntoIterator<Item = &'a url::Host>,
) -> Result<Vec<Vec<u8>>, Error> {
results
.into_iter()
.zip(addresses)
.map(|(next_or_close, address)| {
next_or_close.next_or(Error::Protocol(format!("no response from {}", address)))
})
.collect()
}
}
fn collect_responses<'a>(
results: impl IntoIterator<Item = NextOrClose<Vec<u8>>>,
addresses: impl IntoIterator<Item = &'a url::Host>,
) -> Result<Vec<Vec<u8>>, Error> {
results
.into_iter()
.zip(addresses)
.map(|(next_or_close, address)| {
next_or_close.next_or(Error::Protocol(format!("no response from {}", address)))
})
.collect()
#[async_trait]
pub trait Svr3Client {
async fn backup(
&self,
password: &str,
secret: [u8; 32],
max_tries: NonZeroU32,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<OpaqueMaskedShareSet, Error>;
async fn restore(
&self,
password: &str,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<EvaluationResult, Error>;
async fn remove(&self) -> Result<(), Error>;
async fn query(&self) -> Result<u32, Error>;
}
#[async_trait]
pub trait Svr3Connect {
// Stream is needed for the blanket implementation,
// otherwise S would be an unconstrained generic parameter.
type Stream;
type Env: PpssSetup<Self::Stream>;
async fn connect(
&self,
) -> Result<<Self::Env as PpssSetup<Self::Stream>>::Connections, enclave::Error>;
}
#[async_trait]
impl<T> Svr3Client for T
where
T: Svr3Connect + Sync,
T::Stream: AsyncDuplexStream + 'static,
{
async fn backup(
&self,
password: &str,
secret: [u8; 32],
max_tries: NonZeroU32,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<OpaqueMaskedShareSet, Error> {
ppss_ops::do_backup::<T::Stream, T::Env>(
self.connect().await?,
password,
secret,
max_tries,
rng,
)
.await
}
async fn restore(
&self,
password: &str,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<EvaluationResult, Error> {
ppss_ops::do_restore::<T::Stream, T::Env>(self.connect().await?, password, share_set, rng)
.await
}
async fn remove(&self) -> Result<(), Error> {
ppss_ops::do_remove::<T::Stream, T::Env>(self.connect().await?).await
}
async fn query(&self) -> Result<u32, Error> {
ppss_ops::do_query::<T::Stream, T::Env>(self.connect().await?).await
}
}
/// Attempt a restore from a pair of SVR3 instances.
///
/// The function is meant to be used in the registration flow, when the client
/// app does not yet know whether it is supposed to be trusting one set of enclaves
/// or another. Therefore, it first reads from the primary falling back to the
/// secondary enclaves only if the primary returned `DataMissing`, that is, the
/// data has not been migrated yet. Any other error terminates the whole operation
/// and will need to be retried.
///
/// The choice of terms "primary" and "fallback" is, perhaps, a little confusing
/// when thinking about the enclave migration, where they would be called,
/// respectively, "next" and "current", but ordering of parameters and actions in
/// the body of the function make "primary" and "fallback" a better fit.
pub async fn restore_with_fallback<Primary, Fallback>(
clients: (Primary, Fallback),
password: &str,
share_set: OpaqueMaskedShareSet,
rng: &mut (impl CryptoRngCore + Send),
) -> Result<EvaluationResult, Error>
where
Primary: Svr3Client + Sync,
Fallback: Svr3Client + Sync,
{
let (primary_conn, fallback_conn) = clients;
match primary_conn.restore(password, share_set.clone(), rng).await {
Err(Error::DataMissing) => (/* fall through to fallback */),
result @ (Err(_) | Ok(_)) => return result,
}
fallback_conn.restore(password, share_set, rng).await
}
/// Simplest way to connect to an SVR3 Environment in integration tests and examples.
pub async fn simple_svr3_connect(
env: &Svr3Env<'static>,
auth: &Auth,
) -> Result<<Svr3Env<'static> as PpssSetup<DefaultStream>>::Connections, enclave::Error> {
let connector = DirectConnector::new(DnsResolver::default());
let sgx_connection = EnclaveEndpointConnection::new(env.sgx(), Duration::from_secs(10));
let a =
SvrConnection::<Sgx, _>::connect(auth.clone(), &sgx_connection, connector.clone()).await?;
let nitro_connection = EnclaveEndpointConnection::new(env.nitro(), Duration::from_secs(10));
let b = SvrConnection::<Nitro, _>::connect(auth.clone(), &nitro_connection, connector.clone())
.await?;
let tpm2snp_connection = EnclaveEndpointConnection::new(env.tpm2snp(), Duration::from_secs(10));
let c =
SvrConnection::<Tpm2Snp, _>::connect(auth.clone(), &tpm2snp_connection, connector).await?;
Ok((a, b, c))
}
#[cfg(test)]
mod test {
use super::*;
use assert_matches::assert_matches;
use rand_core::OsRng;
fn new_empty_share_set() -> OpaqueMaskedShareSet {
OpaqueMaskedShareSet {
inner: SerializableMaskedShareSet {
@ -345,4 +479,112 @@ mod test {
DeserializeError::BadVersion(_),
));
}
struct TestSvr3Client {
restore_fn: fn() -> Result<EvaluationResult, Error>,
}
#[async_trait]
impl Svr3Client for TestSvr3Client {
async fn backup(
&self,
_password: &str,
_secret: [u8; 32],
_max_tries: NonZeroU32,
_rng: &mut (impl CryptoRngCore + Send),
) -> Result<OpaqueMaskedShareSet, Error> {
unreachable!()
}
async fn restore(
&self,
_password: &str,
_share_set: OpaqueMaskedShareSet,
_rng: &mut (impl CryptoRngCore + Send),
) -> Result<EvaluationResult, Error> {
(self.restore_fn)()
}
async fn remove(&self) -> Result<(), Error> {
unreachable!()
}
async fn query(&self) -> Result<u32, Error> {
unreachable!()
}
}
fn test_share_set() -> OpaqueMaskedShareSet {
OpaqueMaskedShareSet {
inner: SerializableMaskedShareSet {
server_ids: vec![],
masked_shares: vec![],
commitment: [0; 32],
},
}
}
fn test_evaluation_result() -> EvaluationResult {
EvaluationResult {
value: [0; 32],
tries_remaining: 42,
}
}
#[tokio::test]
async fn restore_with_fallback_primary_success() {
let primary = TestSvr3Client {
restore_fn: || Ok(test_evaluation_result()),
};
let fallback = TestSvr3Client {
restore_fn: || panic!("Must not be called"),
};
let mut rng = OsRng;
let result =
restore_with_fallback((primary, fallback), "", test_share_set(), &mut rng).await;
assert_matches!(result, Ok(evaluation_result) => assert_eq!(evaluation_result, test_evaluation_result()));
}
#[tokio::test]
async fn restore_with_fallback_primary_fatal_error() {
let primary = TestSvr3Client {
restore_fn: || Err(Error::ConnectionTimedOut),
};
let fallback = TestSvr3Client {
restore_fn: || panic!("Must not be called"),
};
let mut rng = OsRng;
let result =
restore_with_fallback((primary, fallback), "", test_share_set(), &mut rng).await;
assert_matches!(result, Err(Error::ConnectionTimedOut));
}
#[tokio::test]
async fn restore_with_fallback_fallback_error() {
let primary = TestSvr3Client {
restore_fn: || Err(Error::DataMissing),
};
let fallback = TestSvr3Client {
restore_fn: || Err(Error::RestoreFailed(31415)),
};
let mut rng = OsRng;
let result =
restore_with_fallback((primary, fallback), "", test_share_set(), &mut rng).await;
assert_matches!(result, Err(Error::RestoreFailed(31415)));
}
#[tokio::test]
async fn restore_with_fallback_fallback_success() {
let primary = TestSvr3Client {
restore_fn: || Err(Error::DataMissing),
};
let fallback = TestSvr3Client {
restore_fn: || Ok(test_evaluation_result()),
};
let mut rng = OsRng;
let result =
restore_with_fallback((primary, fallback), "", test_share_set(), &mut rng).await;
assert_matches!(result, Ok(evaluation_result) => assert_eq!(evaluation_result, test_evaluation_result()));
}
}

View File

@ -87,7 +87,7 @@ pub struct Restore<'a> {
pub requests: Vec<Vec<u8>>,
}
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub struct EvaluationResult {
pub value: [u8; SECRET_BYTES],
pub tries_remaining: u32,