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:
parent
4694d0a1a8
commit
c870e23a5b
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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());
|
||||
|
@ -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")
|
||||
};
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user