mirror of
https://github.com/signalapp/libsignal.git
synced 2024-09-19 19:42:19 +02:00
SVR - plumb new protocol into net.
This commit is contained in:
parent
e46841ea2c
commit
303d7a6659
@ -13,10 +13,11 @@ pub const ENCLAVE_ID_CDSI_STAGING_AND_PROD: &[u8] =
|
||||
&hex!("0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57");
|
||||
pub const ENCLAVE_ID_SVR2_STAGING: &[u8] =
|
||||
&hex!("acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482");
|
||||
|
||||
pub const ENCLAVE_ID_SVR3_SGX_STAGING: &[u8] =
|
||||
&hex!("29cd5aa268da2412ae14e9de2168608c9b22daadfd7effa2029abac02289e691");
|
||||
pub const ENCLAVE_ID_SVR3_NITRO_STAGING: &[u8] = b"ffe631d7.52b91975.a4544fb5";
|
||||
pub const ENCLAVE_ID_SVR3_TPM2SNP_STAGING: &[u8] = b"0.20240703.193412";
|
||||
&hex!("64fd0ab571f2de73665befed1f34862e17cdd3d58201e335be2f68b3f985180f");
|
||||
pub const ENCLAVE_ID_SVR3_NITRO_STAGING: &[u8] = b"e651a442.52b91975.5c89712f";
|
||||
pub const ENCLAVE_ID_SVR3_TPM2SNP_STAGING: &[u8] = b"0.20240824.003942";
|
||||
|
||||
pub const ENCLAVE_ID_SVR3_SGX_PROD: &[u8] =
|
||||
&hex!("0899bf951b57f27b5cd3d2dd4dbe5a144a4a62154853a4e352ac2c93ecfe6a2c");
|
||||
@ -30,9 +31,9 @@ pub(crate) const NITRO_EXPECTED_PCRS: SmallMap<&'static [u8], nitro::PcrMap, 1>
|
||||
(
|
||||
ENCLAVE_ID_SVR3_NITRO_STAGING,
|
||||
SmallMap::new([
|
||||
(0, hex!("ffe631d7b726c672480ddde425f3ed9cbdaafa354dc6a85277dde6bfca56e93fafd66052f1dd93bf5f240c5a55fb2cb1")),
|
||||
(0, hex!("e651a442efc3be65a893c7ea3211d3a826b5c0b2102a224df9b42b6a7c0306b8e67553beed4069db0192224a644d80bc")),
|
||||
(1, hex!("52b919754e1643f4027eeee8ec39cc4a2cb931723de0c93ce5cc8d407467dc4302e86490c01c0d755acfe10dbf657546")),
|
||||
(2, hex!("a4544fb5e5f416c08c4aca4c3f14efaf8d16d7ddc39d15f17b3b02605ef6e3a834553a0901fbce8716cc0de8caea028d")),
|
||||
(2, hex!("5c89712fd49d4cbebfa1c7f7c59e19a2f94203938647348ebd0dfd20ce3e17b9c7447d3e4856348a89d3c96b5a747a35")),
|
||||
]),
|
||||
),
|
||||
]);
|
||||
@ -47,8 +48,8 @@ pub(crate) const TPM2SNP_EXPECTED_PCRS: SmallMap<&'static [u8], &'static tpm2snp
|
||||
(3, hex!("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969")),
|
||||
(4, hex!("6038382cdf539eb64d05c804c510e22b81e2c71fb171c9616ab14504f3654bb1")),
|
||||
(7, hex!("590471a4fbd0c881c4fdc6349bc697e4df18c660c3ae3de9cb29028f8ef77280")),
|
||||
(8, hex!("7ea60a57d8d6b5c522dabeb55a3fa15aed6df65ae470b43865042e39be26ac06")),
|
||||
(9, hex!("95abd7aab524ef6b25f18f58a8b7f7e5b6eb285686fa8b7b28ccd39a73ac8d30")),
|
||||
(8, hex!("de28c40baca9bdb2024cd5e7a0af223396f8459b2bb14b5edce90ff78bc83c93")),
|
||||
(9, hex!("27117054ad1ca7d2daf5f6dbbc5fb7c3268f460afe6a2933d32d34968a13b12a")),
|
||||
(11, hex!("0000000000000000000000000000000000000000000000000000000000000000")),
|
||||
(12, hex!("0000000000000000000000000000000000000000000000000000000000000000")),
|
||||
(13, hex!("0000000000000000000000000000000000000000000000000000000000000000")),
|
||||
@ -101,16 +102,16 @@ pub const RAFT_CONFIG_SVR3_SGX_STAGING: &RaftConfig = &RaftConfig {
|
||||
min_voting_replicas: 3,
|
||||
max_voting_replicas: 9,
|
||||
super_majority: 0,
|
||||
group_id: 15742431669367858463,
|
||||
group_id: 2598856512118716214,
|
||||
};
|
||||
pub const RAFT_CONFIG_SVR3_NITRO_STAGING: &RaftConfig = &RaftConfig {
|
||||
group_id: 10298929430185113734,
|
||||
group_id: 12784208226750162631,
|
||||
min_voting_replicas: 3,
|
||||
max_voting_replicas: 9,
|
||||
super_majority: 0,
|
||||
};
|
||||
pub const RAFT_CONFIG_SVR3_TPM2SNP_STAGING: &RaftConfig = &RaftConfig {
|
||||
group_id: 2579018130363863974,
|
||||
group_id: 15331762113118535803,
|
||||
min_voting_replicas: 3,
|
||||
max_voting_replicas: 9,
|
||||
super_majority: 0,
|
||||
|
@ -6,7 +6,7 @@
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use bincode::Options as _;
|
||||
use libsignal_svr3::{EvaluationResult, MaskedShareSet};
|
||||
use libsignal_svr3::{EvaluationResult, MaskedSecret};
|
||||
use rand_core::CryptoRngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@ -29,31 +29,28 @@ pub struct OpaqueMaskedShareSet {
|
||||
inner: SerializableMaskedShareSet,
|
||||
}
|
||||
|
||||
// Non pub version of ppss::MaskedShareSet used for serialization
|
||||
// Non pub version of svr3::MaskedSecret used for serialization
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Default))]
|
||||
struct SerializableMaskedShareSet {
|
||||
server_ids: Vec<u64>,
|
||||
masked_shares: Vec<[u8; 32]>,
|
||||
commitment: [u8; 32],
|
||||
masked_secret: [u8; 32],
|
||||
}
|
||||
|
||||
impl From<MaskedShareSet> for SerializableMaskedShareSet {
|
||||
fn from(value: MaskedShareSet) -> Self {
|
||||
impl From<MaskedSecret> for SerializableMaskedShareSet {
|
||||
fn from(value: MaskedSecret) -> Self {
|
||||
Self {
|
||||
server_ids: value.server_ids,
|
||||
masked_shares: value.masked_shares,
|
||||
commitment: value.commitment,
|
||||
masked_secret: value.masked_secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializableMaskedShareSet {
|
||||
fn into(self) -> MaskedShareSet {
|
||||
MaskedShareSet {
|
||||
fn into(self) -> MaskedSecret {
|
||||
MaskedSecret {
|
||||
server_ids: self.server_ids,
|
||||
masked_shares: self.masked_shares,
|
||||
commitment: self.commitment,
|
||||
masked_secret: self.masked_secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,12 +69,12 @@ pub enum DeserializeError {
|
||||
impl LogSafeDisplay for DeserializeError {}
|
||||
|
||||
impl OpaqueMaskedShareSet {
|
||||
fn new(inner: MaskedShareSet) -> Self {
|
||||
fn new(inner: MaskedSecret) -> Self {
|
||||
Self {
|
||||
inner: inner.into(),
|
||||
}
|
||||
}
|
||||
fn into_inner(self) -> MaskedShareSet {
|
||||
fn into_inner(self) -> MaskedSecret {
|
||||
self.inner.into()
|
||||
}
|
||||
|
||||
@ -308,8 +305,7 @@ mod test {
|
||||
OpaqueMaskedShareSet {
|
||||
inner: SerializableMaskedShareSet {
|
||||
server_ids: vec![],
|
||||
masked_shares: vec![],
|
||||
commitment: [0; 32],
|
||||
masked_secret: [0u8; 32],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use std::num::NonZeroU32;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures_util::future::join_all;
|
||||
use libsignal_svr3::{make_remove_request, Backup, EvaluationResult, Query, Restore};
|
||||
use libsignal_svr3::{Backup4, EvaluationResult, MaskedSecret, Query4, Remove4, Restore1};
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
use super::{Error, OpaqueMaskedShareSet};
|
||||
@ -31,14 +31,20 @@ pub async fn do_backup<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>>(
|
||||
let ConnectionContext {
|
||||
mut connections,
|
||||
addresses,
|
||||
mut errors,
|
||||
errors,
|
||||
} = ConnectionContext::new(connect_results);
|
||||
if let Some(err) = errors.pop_front() {
|
||||
if let Some(err) = errors.into_iter().next() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let server_ids = Env::server_ids();
|
||||
let backup = Backup::new(server_ids.as_ref(), password, secret, max_tries, rng)?;
|
||||
let backup = Backup4::new(
|
||||
server_ids.as_ref(),
|
||||
password.as_bytes(),
|
||||
&secret,
|
||||
max_tries,
|
||||
rng,
|
||||
)?;
|
||||
let futures = connections
|
||||
.iter_mut()
|
||||
.zip(&backup.requests)
|
||||
@ -47,9 +53,8 @@ pub async fn do_backup<S: AsyncDuplexStream + 'static, Env: PpssSetup<S>>(
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
let responses = collect_responses(results?, addresses.iter())?;
|
||||
let share_set = backup.finalize(rng, &responses)?;
|
||||
Ok(OpaqueMaskedShareSet::new(share_set))
|
||||
collect_responses(results?, addresses.iter())?;
|
||||
Ok(OpaqueMaskedShareSet::new(backup.masked_secret))
|
||||
}
|
||||
|
||||
pub async fn do_restore<S: AsyncDuplexStream + 'static>(
|
||||
@ -61,23 +66,46 @@ pub async fn do_restore<S: AsyncDuplexStream + 'static>(
|
||||
let ConnectionContext {
|
||||
mut connections,
|
||||
addresses,
|
||||
mut errors,
|
||||
errors,
|
||||
} = ConnectionContext::new(connect_results);
|
||||
if let Some(err) = errors.pop_front() {
|
||||
if let Some(err) = errors.into_iter().next() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let restore = Restore::new(password, share_set.into_inner(), rng)?;
|
||||
let masked_secret: MaskedSecret = share_set.into_inner();
|
||||
|
||||
let restore1 = Restore1::new(masked_secret.server_ids.as_ref(), password.as_bytes(), rng);
|
||||
let responses1 = {
|
||||
let futures = connections
|
||||
.iter_mut()
|
||||
.zip(&restore.requests)
|
||||
.zip(&restore1.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())?;
|
||||
Ok(restore.finalize(&responses)?)
|
||||
collect_responses(results?, addresses.iter())?
|
||||
};
|
||||
|
||||
let restore2 = restore1.restore2(&responses1, rng)?;
|
||||
let tries_remaining = restore2.tries_remaining;
|
||||
let responses2 = {
|
||||
let futures = connections
|
||||
.iter_mut()
|
||||
.zip(&restore2.requests)
|
||||
.map(|(connection, request)| run_attested_interaction(connection, request));
|
||||
let results = join_all(futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
collect_responses(results?, addresses.iter())?
|
||||
};
|
||||
let output = restore2.restore(&responses2)?;
|
||||
|
||||
Ok(EvaluationResult {
|
||||
value: output.unmask_secret(&masked_secret.masked_secret),
|
||||
tries_remaining,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn do_remove<S: AsyncDuplexStream + 'static>(
|
||||
@ -93,9 +121,11 @@ pub async fn do_remove<S: AsyncDuplexStream + 'static>(
|
||||
// and proceed to work with the successful connections.
|
||||
log::debug!("Connection failure '{:?}' will be ignored.", &err);
|
||||
}
|
||||
|
||||
let futures = connections
|
||||
.iter_mut()
|
||||
.map(|connection| run_attested_interaction(connection, make_remove_request()));
|
||||
.zip(Remove4::requests())
|
||||
.map(|(connection, request)| run_attested_interaction(connection, request));
|
||||
let results = join_all(futures)
|
||||
.await
|
||||
.into_iter()
|
||||
@ -110,22 +140,22 @@ pub async fn do_query<S: AsyncDuplexStream + 'static>(
|
||||
let ConnectionContext {
|
||||
mut connections,
|
||||
addresses,
|
||||
mut errors,
|
||||
errors,
|
||||
} = ConnectionContext::new(connect_results);
|
||||
if let Some(err) = errors.pop_front() {
|
||||
if let Some(err) = errors.into_iter().next() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let futures = connections
|
||||
.iter_mut()
|
||||
.zip(Query::requests())
|
||||
.zip(Query4::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())?;
|
||||
Ok(Query::finalize(&responses)?)
|
||||
Ok(Query4::finalize(&responses)?)
|
||||
}
|
||||
|
||||
struct ConnectionContext<S> {
|
||||
|
@ -18,12 +18,59 @@ use sha2::{Sha256, Sha512};
|
||||
use crate::errors::Error;
|
||||
use crate::proto::svr4;
|
||||
|
||||
/// Make a request to remove a record from SVR4.
|
||||
pub fn make_remove4_request() -> Vec<u8> {
|
||||
/// Perfrom array XOR: into ^= from
|
||||
fn arr_xor(from: &[u8], into: &mut [u8]) {
|
||||
assert_eq!(from.len(), into.len());
|
||||
for (dst, src) in std::iter::zip(into, from) {
|
||||
*dst ^= src;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Query4 {}
|
||||
|
||||
impl Query4 {
|
||||
// Using `impl Iterator<...>` makes libsignal-net fail to build in Rust 1.72
|
||||
pub fn requests() -> std::iter::Repeat<Vec<u8>> {
|
||||
std::iter::repeat(
|
||||
svr4::Request4 {
|
||||
inner: Some(svr4::request4::Inner::Query(svr4::request4::Query {})),
|
||||
}
|
||||
.encode_to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn finalize(responses: &[Vec<u8>]) -> Result<u32, Error> {
|
||||
assert!(!responses.is_empty());
|
||||
responses
|
||||
.iter()
|
||||
.map(|b| svr4::Response4::decode(b.as_ref()).map_err(|_| Error::BadData))
|
||||
.map(|rr| match rr?.inner {
|
||||
Some(svr4::response4::Inner::Query(r)) => match status_error(r.status) {
|
||||
Ok(()) => Ok(r.tries_remaining),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
_ => Err(Error::BadData),
|
||||
})
|
||||
// Get the min tries_remaining while short circuiting on errors.
|
||||
// Should never actually return u32::MAX, since there should be at least one
|
||||
// response, which will either be an error (returning an error overall)
|
||||
// or a value less than MAX.
|
||||
.try_fold(u32::MAX, |acc, tr| Ok(std::cmp::min(acc, tr?)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Remove4 {}
|
||||
|
||||
impl Remove4 {
|
||||
// Using `impl Iterator<...>` makes libsignal-net fail to build in Rust 1.72
|
||||
pub fn requests() -> std::iter::Repeat<Vec<u8>> {
|
||||
std::iter::repeat(
|
||||
svr4::Request4 {
|
||||
inner: Some(svr4::request4::Inner::Remove(svr4::request4::Remove {})),
|
||||
}
|
||||
.encode_to_vec()
|
||||
.encode_to_vec(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of `n` scalars which are chosen randomly but
|
||||
@ -49,9 +96,7 @@ fn bytes_xoring_to<R: CryptoRngCore>(n: NonZeroUsize, b: &[u8], rng: &mut R) ->
|
||||
.collect();
|
||||
let mut xor: Vec<u8> = b.into();
|
||||
for vec in v.iter() {
|
||||
for (i, c) in vec.iter().enumerate() {
|
||||
xor[i] ^= c;
|
||||
}
|
||||
arr_xor(vec, &mut xor);
|
||||
}
|
||||
v.push(xor);
|
||||
v
|
||||
@ -118,19 +163,35 @@ fn to_ristretto_pt(b: &[u8]) -> Option<RistrettoPoint> {
|
||||
CompressedRistretto::from_slice(b).ok()?.decompress()
|
||||
}
|
||||
|
||||
/// Given a set of password bytes from the client that may be in any
|
||||
/// format or any length, generate a deterministic 64-byte key
|
||||
/// for use in Backup/Restore operations.
|
||||
fn password_to_uniform_input(passwd: &[u8]) -> [u8; 64] {
|
||||
Kdf::make(b"Signal_SVR_InputFromPassword_20240823", passwd, b"")
|
||||
}
|
||||
|
||||
pub struct Backup4 {
|
||||
pub requests: Vec<Vec<u8>>,
|
||||
pub output: Output4,
|
||||
pub masked_secret: MaskedSecret,
|
||||
}
|
||||
|
||||
/// Contains everything necessary to restore a secret given a password.
|
||||
pub struct MaskedSecret {
|
||||
pub server_ids: Vec<u64>,
|
||||
pub masked_secret: [u8; 32],
|
||||
}
|
||||
|
||||
impl Backup4 {
|
||||
pub fn new<R: CryptoRngCore>(
|
||||
server_ids: &[u64],
|
||||
input: [u8; 64],
|
||||
password: &[u8],
|
||||
secret: &[u8; 32],
|
||||
max_tries: NonZeroU32,
|
||||
rng: &mut R,
|
||||
) -> Result<Self, Error> {
|
||||
assert!(!server_ids.is_empty());
|
||||
let input = password_to_uniform_input(password);
|
||||
let k_oprf = Scalar::random(rng);
|
||||
|
||||
let mut s_enc = [0u8; 32];
|
||||
@ -144,7 +205,11 @@ impl Backup4 {
|
||||
|
||||
let auth_pt = auth_pt(&input, &k_oprf);
|
||||
let auth_commitments = auth_commitments(server_ids, &input, &auth_pt);
|
||||
let version = rng.next_u64();
|
||||
let version = rng.next_u32();
|
||||
let output = Output4 {
|
||||
k_auth: auth_secret(&input, &auth_pt),
|
||||
s_enc,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
requests: (0usize..server_ids.len())
|
||||
@ -160,10 +225,11 @@ impl Backup4 {
|
||||
})
|
||||
.map(|cr| cr.encode_to_vec())
|
||||
.collect(),
|
||||
output: Output4 {
|
||||
k_auth: auth_secret(&input, &auth_pt),
|
||||
s_enc,
|
||||
masked_secret: MaskedSecret {
|
||||
server_ids: server_ids.to_vec(),
|
||||
masked_secret: output.mask_secret(secret),
|
||||
},
|
||||
output,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -182,20 +248,28 @@ impl Output4 {
|
||||
&self.k_auth,
|
||||
)
|
||||
}
|
||||
pub fn mask_secret(&self, secret: &[u8; 32]) -> [u8; 32] {
|
||||
let mut out = self.encryption_key();
|
||||
arr_xor(secret, &mut out);
|
||||
out
|
||||
}
|
||||
pub fn unmask_secret(&self, masked_secret: &[u8; 32]) -> [u8; 32] {
|
||||
self.mask_secret(masked_secret) // masking and unmasking are the same function
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Restore1<'a> {
|
||||
server_ids: &'a [u64],
|
||||
input: &'a [u8; 64],
|
||||
input: [u8; 64],
|
||||
blind: Scalar,
|
||||
pub requests: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub struct Restore2<'a> {
|
||||
server_ids: &'a [u64],
|
||||
input: &'a [u8; 64],
|
||||
input: [u8; 64],
|
||||
auth_pt: RistrettoPoint,
|
||||
tries_remaining: Option<u32>,
|
||||
pub tries_remaining: u32,
|
||||
pub requests: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
@ -212,14 +286,15 @@ fn status_errors<I: Iterator<Item = i32>>(statuses: &mut I) -> Result<(), Error>
|
||||
}
|
||||
|
||||
impl<'a> Restore1<'a> {
|
||||
pub fn new<R: CryptoRngCore>(server_ids: &'a [u64], input: &'a [u8; 64], rng: &mut R) -> Self {
|
||||
pub fn new<R: CryptoRngCore>(server_ids: &'a [u64], password: &[u8], rng: &mut R) -> Self {
|
||||
let blind = Scalar::random(rng);
|
||||
let input = password_to_uniform_input(password);
|
||||
Restore1 {
|
||||
requests: server_ids
|
||||
.iter()
|
||||
.map(|_| svr4::Request4 {
|
||||
inner: Some(svr4::request4::Inner::Restore1(svr4::request4::Restore1 {
|
||||
blinded: (input_hash_pt(input) * blind)
|
||||
blinded: (input_hash_pt(&input) * blind)
|
||||
.compress()
|
||||
.to_bytes()
|
||||
.to_vec(),
|
||||
@ -304,7 +379,7 @@ impl<'a> Restore1<'a> {
|
||||
|
||||
// Now, we use auth_pt to recompute auth commitments, which we send
|
||||
// back to the server, proving we have the correct value for `input`.
|
||||
let auth_commitments = auth_commitments(self.server_ids, self.input, &auth_pt);
|
||||
let auth_commitments = auth_commitments(self.server_ids, &self.input, &auth_pt);
|
||||
let rand = Scalar::random(rng);
|
||||
let proof_pt_bytes = (RISTRETTO_BASEPOINT_TABLE * &rand).compress().to_bytes();
|
||||
let proof_scalar_base = Scalar::hash_from_bytes::<Sha512>(&proof_pt_bytes);
|
||||
@ -325,14 +400,15 @@ impl<'a> Restore1<'a> {
|
||||
server_ids: self.server_ids,
|
||||
input: self.input,
|
||||
auth_pt,
|
||||
tries_remaining,
|
||||
tries_remaining: tries_remaining
|
||||
.expect("all responses had to be OK, so we should have this"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Given a set of responses, each of which have some number of Auths,
|
||||
/// return a version that is available in all responses, if such a
|
||||
/// one exists.
|
||||
fn version_to_use(&self, responses1: &[svr4::response4::Restore1]) -> Option<u64> {
|
||||
fn version_to_use(&self, responses1: &[svr4::response4::Restore1]) -> Option<u32> {
|
||||
let mut versions = BTreeMap::new();
|
||||
for r1 in responses1 {
|
||||
for auth in &r1.auth {
|
||||
@ -352,7 +428,7 @@ impl<'a> Restore1<'a> {
|
||||
/// given version, or an Error::BadResponse if they're not found.
|
||||
fn auths_with_version<'b>(
|
||||
&self,
|
||||
version: u64,
|
||||
version: u32,
|
||||
responses1: &'b [svr4::response4::Restore1],
|
||||
) -> Result<Vec<&'b svr4::response4::restore1::Auth>, Error> {
|
||||
let mut out = Vec::with_capacity(responses1.len());
|
||||
@ -394,33 +470,27 @@ impl<'a> Restore2<'a> {
|
||||
_ => Err(Error::BadResponse),
|
||||
})
|
||||
.collect::<Result<Vec<svr4::response4::Restore2>, _>>()?;
|
||||
status_errors(&mut responses2.iter().map(|r| r.status)).map_err(|e| {
|
||||
match self.tries_remaining {
|
||||
Some(tr) => Error::RestoreFailed(tr),
|
||||
None => e,
|
||||
}
|
||||
})?;
|
||||
status_errors(&mut responses2.iter().map(|r| r.status))
|
||||
.map_err(|_| Error::RestoreFailed(self.tries_remaining))?;
|
||||
|
||||
let mut s_enc = [0u8; 32];
|
||||
for resp in responses2.iter() {
|
||||
if resp.encryption_secretshare.len() != s_enc.len() {
|
||||
return Err(Error::BadResponse);
|
||||
}
|
||||
for (i, c) in resp.encryption_secretshare.iter().enumerate() {
|
||||
s_enc[i] ^= c;
|
||||
}
|
||||
arr_xor(&resp.encryption_secretshare, &mut s_enc);
|
||||
}
|
||||
Ok(Output4 {
|
||||
s_enc,
|
||||
k_auth: auth_secret(self.input, &self.auth_pt),
|
||||
k_auth: auth_secret(&self.input, &self.auth_pt),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum RotationAction {
|
||||
DoNothing,
|
||||
Commit(u64),
|
||||
Rollback(u64),
|
||||
Commit(u32),
|
||||
Rollback(u32),
|
||||
}
|
||||
|
||||
/// RotationMachineState defines a simple state machine for performing
|
||||
@ -477,11 +547,11 @@ enum RotationMachineState {
|
||||
/// Start our actual rotation, using the stored new (random) version
|
||||
/// number. This creates new key deltas for OPRF and Encryption keys
|
||||
/// and provides them to the backing servers.
|
||||
RotateStart(u64),
|
||||
RotateStart(u32),
|
||||
/// Commit the version we created with RotateStart, rolling all backends
|
||||
/// forward to the new version. If this succeeds, all servers will be
|
||||
/// at the new version.
|
||||
RotateCommit(u64),
|
||||
RotateCommit(u32),
|
||||
/// There's nothing more to do, either because we hit an unrecoverable
|
||||
/// error or because we've completed successfully.
|
||||
Done,
|
||||
@ -569,7 +639,7 @@ impl<'a> RotationMachine<'a> {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn rotate_start_requests(&mut self, version: u64) -> Vec<Vec<u8>> {
|
||||
fn rotate_start_requests(&mut self, version: u32) -> Vec<Vec<u8>> {
|
||||
let n = NonZeroUsize::new(self.server_ids.len()).unwrap();
|
||||
let oprf_secretshares = scalars_summing_to(n, &Scalar::ZERO, &mut self.rng);
|
||||
let mut encryption_secretshares = bytes_xoring_to(n, &[0u8; 32], &mut self.rng);
|
||||
@ -589,7 +659,7 @@ impl<'a> RotationMachine<'a> {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn rotate_commit_requests(&self, version: u64) -> Vec<Vec<u8>> {
|
||||
fn rotate_commit_requests(&self, version: u32) -> Vec<Vec<u8>> {
|
||||
self.server_ids
|
||||
.iter()
|
||||
.map(|_| svr4::Request4 {
|
||||
@ -640,7 +710,7 @@ impl<'a> RotationMachine<'a> {
|
||||
|
||||
let first = resps[0];
|
||||
// Utility function to check that a version is in all responses.
|
||||
let version_in_all_responses = |v: u64| -> bool {
|
||||
let version_in_all_responses = |v: u32| -> bool {
|
||||
resps
|
||||
.iter()
|
||||
.filter(|r| r.version == v || r.new_version == v)
|
||||
@ -679,7 +749,7 @@ impl<'a> RotationMachine<'a> {
|
||||
if action_required {
|
||||
self.state = RotationMachineState::FixPrevious(actions);
|
||||
} else {
|
||||
self.state = RotationMachineState::RotateStart(self.rng.next_u64());
|
||||
self.state = RotationMachineState::RotateStart(self.rng.next_u32());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -694,11 +764,11 @@ impl<'a> RotationMachine<'a> {
|
||||
Some(svr4::response4::Inner::RotateRollback(r)) => status_error(r.status),
|
||||
_ => Err(Error::BadResponse),
|
||||
})?;
|
||||
self.state = RotationMachineState::RotateStart(self.rng.next_u64());
|
||||
self.state = RotationMachineState::RotateStart(self.rng.next_u32());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rotate_start_responses(&mut self, responses: &[Vec<u8>], version: u64) -> Result<(), Error> {
|
||||
fn rotate_start_responses(&mut self, responses: &[Vec<u8>], version: u32) -> Result<(), Error> {
|
||||
let _resps = responses
|
||||
.iter()
|
||||
.map(|b| svr4::Response4::decode(b.as_ref()).map_err(|_| Error::BadData))
|
||||
@ -788,7 +858,7 @@ mod test {
|
||||
/// TestServer implements the server-side for a single-user interaction.
|
||||
struct TestServer {
|
||||
tries: u32,
|
||||
versions: BTreeMap<u64, TestServerVersion>,
|
||||
versions: BTreeMap<u32, TestServerVersion>,
|
||||
}
|
||||
|
||||
/// TestServerVersion holds a single version's data for a single user.
|
||||
@ -930,17 +1000,19 @@ mod test {
|
||||
.iter()
|
||||
.map(|_| TestServer::new())
|
||||
.collect::<Vec<_>>();
|
||||
let input = [2u8; 64];
|
||||
let password = [2u8; 67]; // can be arbitrary length
|
||||
|
||||
// Create a new backup
|
||||
let backup =
|
||||
Backup4::new(&server_ids, input, nonzero!(10u32), &mut rng).expect("create Backup4");
|
||||
let secret = [3u8; 32];
|
||||
let backup = Backup4::new(&server_ids, &password, &secret, nonzero!(10u32), &mut rng)
|
||||
.expect("create Backup4");
|
||||
for (server, req) in servers.iter_mut().zip(backup.requests) {
|
||||
server.create(&req);
|
||||
}
|
||||
let masked_secret = backup.masked_secret;
|
||||
|
||||
// Restoring existing backup.
|
||||
let restore1 = Restore1::new(&server_ids, &input, &mut rng);
|
||||
let restore1 = Restore1::new(&server_ids, &password, &mut rng);
|
||||
let restore1_responses = servers
|
||||
.iter_mut()
|
||||
.zip(&restore1.requests)
|
||||
@ -958,6 +1030,10 @@ mod test {
|
||||
.restore(&restore2_responses)
|
||||
.expect("call restored");
|
||||
assert_eq!(backup.output, got);
|
||||
assert_eq!(
|
||||
secret,
|
||||
backup.output.unmask_secret(&masked_secret.masked_secret)
|
||||
);
|
||||
}
|
||||
|
||||
/// Deterministic RNG for testing
|
||||
|
@ -16,6 +16,7 @@ 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};
|
||||
use proto::svr3::{self, create_response, evaluate_response, query_response};
|
||||
pub use proto::svr4::response4::Status as V4Status;
|
||||
|
||||
|
@ -26,7 +26,7 @@ message Request4 {
|
||||
bytes auth_commitment = 3; // clienteck^pk
|
||||
bytes encryption_secretshare = 4; // s_i
|
||||
bytes zero_secretshare = 5; // z_i
|
||||
fixed64 version = 6; // cross-backend version # for joins
|
||||
fixed32 version = 6; // cross-backend version # for joins
|
||||
}
|
||||
message Restore1 {
|
||||
bytes blinded = 1;
|
||||
@ -34,22 +34,22 @@ message Request4 {
|
||||
message Restore2 {
|
||||
bytes auth_point = 1; // R
|
||||
bytes auth_scalar = 2; // z
|
||||
fixed64 version = 3;
|
||||
fixed32 version = 3;
|
||||
}
|
||||
message Remove {
|
||||
}
|
||||
message Query {
|
||||
}
|
||||
message RotateStart {
|
||||
fixed64 version = 1; // new cross-backend version #
|
||||
fixed32 version = 1; // new cross-backend version #
|
||||
bytes oprf_secretshare_delta = 2; // k_i^oprf scalar delta
|
||||
bytes encryption_secretshare_delta = 3; // s_i XOR delta
|
||||
}
|
||||
message RotateCommit {
|
||||
fixed64 version = 1; // new version #
|
||||
fixed32 version = 1; // new version #
|
||||
}
|
||||
message RotateRollback {
|
||||
fixed64 version = 1; // new version #
|
||||
fixed32 version = 1; // new version #
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +75,8 @@ message Response4 {
|
||||
VERSION_MISMATCH = 6;
|
||||
NOT_ROTATING = 7;
|
||||
ALREADY_ROTATING = 8;
|
||||
MERKLE_FAILURE = 9;
|
||||
DUPLICATE_VERSION = 10;
|
||||
}
|
||||
|
||||
message Create {
|
||||
@ -86,7 +88,7 @@ message Response4 {
|
||||
Status status = 1;
|
||||
message Auth {
|
||||
bytes element = 1;
|
||||
fixed64 version = 2;
|
||||
fixed32 version = 2;
|
||||
}
|
||||
// There will be either 1 or 2 of these, depending on
|
||||
// whether we're currently rotating.
|
||||
@ -105,8 +107,8 @@ message Response4 {
|
||||
message Query {
|
||||
Status status = 1;
|
||||
uint32 tries_remaining = 2;
|
||||
fixed64 version = 3;
|
||||
fixed64 new_version = 4;
|
||||
fixed32 version = 3;
|
||||
fixed32 new_version = 4;
|
||||
}
|
||||
|
||||
message RotateStart {
|
||||
|
Loading…
Reference in New Issue
Block a user