|
@@ -8,11 +8,17 @@ buckets. Users will either download the whole encrypted bucket list or
|
|
|
use PIR to download a piece of it, so that the bridge authority does not
|
|
|
learn which bucket the user has access to. */
|
|
|
|
|
|
+use super::cred;
|
|
|
+use super::IssuerPrivKey;
|
|
|
+use super::CMZ_B_TABLE;
|
|
|
use aes_gcm::aead;
|
|
|
use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead};
|
|
|
use aes_gcm::Aes128Gcm;
|
|
|
+use curve25519_dalek::ristretto::CompressedRistretto;
|
|
|
+use curve25519_dalek::ristretto::RistrettoBasepointTable;
|
|
|
use curve25519_dalek::scalar::Scalar;
|
|
|
use rand::RngCore;
|
|
|
+use std::collections::HashSet;
|
|
|
use std::convert::TryInto;
|
|
|
use subtle::ConstantTimeEq;
|
|
|
|
|
@@ -22,14 +28,14 @@ pub const BRIDGE_BYTES: usize = 220;
|
|
|
/// The max number of bridges per bucket
|
|
|
pub const MAX_BRIDGES_PER_BUCKET: usize = 3;
|
|
|
|
|
|
-/// The size of a plaintext bucket
|
|
|
-pub const BUCKET_BYTES: usize = BRIDGE_BYTES * MAX_BRIDGES_PER_BUCKET;
|
|
|
-
|
|
|
-/// The size of an encrypted bucket
|
|
|
-pub const ENC_BUCKET_BYTES: usize = BUCKET_BYTES + 12 + 16;
|
|
|
+/// The minimum number of bridges in a bucket that must be reachable for
|
|
|
+/// the bucket to get a Bucket Reachability credential that will allow
|
|
|
+/// users of that bucket to gain trust levels (once they are already at
|
|
|
+/// level 1)
|
|
|
+pub const MIN_BUCKET_REACHABILITY: usize = 2;
|
|
|
|
|
|
/// A bridge information line
|
|
|
-#[derive(Copy, Clone, Debug)]
|
|
|
+#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
|
|
pub struct BridgeLine {
|
|
|
/// IPv4 or IPv6 address
|
|
|
pub addr: [u8; 16],
|
|
@@ -40,6 +46,20 @@ pub struct BridgeLine {
|
|
|
pub info: [u8; BRIDGE_BYTES - 18],
|
|
|
}
|
|
|
|
|
|
+/// A bucket contains MAX_BRIDGES_PER_BUCKET bridges plus the
|
|
|
+/// information needed to construct a Bucket Reachability credential,
|
|
|
+/// which is a 4-byte date, and a (P,Q) MAC
|
|
|
+type Bucket = (
|
|
|
+ [BridgeLine; MAX_BRIDGES_PER_BUCKET],
|
|
|
+ Option<cred::BucketReachability>,
|
|
|
+);
|
|
|
+
|
|
|
+/// The size of a plaintext bucket
|
|
|
+pub const BUCKET_BYTES: usize = BRIDGE_BYTES * MAX_BRIDGES_PER_BUCKET + 4 + 32 + 32;
|
|
|
+
|
|
|
+/// The size of an encrypted bucket
|
|
|
+pub const ENC_BUCKET_BYTES: usize = BUCKET_BYTES + 12 + 16;
|
|
|
+
|
|
|
impl Default for BridgeLine {
|
|
|
/// An "empty" BridgeLine is represented by all zeros
|
|
|
fn default() -> Self {
|
|
@@ -68,25 +88,79 @@ impl BridgeLine {
|
|
|
res.info.copy_from_slice(&data[18..]);
|
|
|
res
|
|
|
}
|
|
|
- /// Encode a bucket to a byte array
|
|
|
- pub fn bucket_encode(bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) -> [u8; BUCKET_BYTES] {
|
|
|
+ /// Encode a bucket to a byte array, including a Bucket Reachability
|
|
|
+ /// credential if appropriate
|
|
|
+ pub fn bucket_encode(
|
|
|
+ bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET],
|
|
|
+ reachable: &HashSet<BridgeLine>,
|
|
|
+ today: u32,
|
|
|
+ bucket_attr: &Scalar,
|
|
|
+ reachability_priv: &IssuerPrivKey,
|
|
|
+ ) -> [u8; BUCKET_BYTES] {
|
|
|
let mut res: [u8; BUCKET_BYTES] = [0; BUCKET_BYTES];
|
|
|
let mut pos: usize = 0;
|
|
|
+ let mut num_reachable: usize = 0;
|
|
|
for bridge in bucket {
|
|
|
res[pos..pos + BRIDGE_BYTES].copy_from_slice(&bridge.encode());
|
|
|
+ if reachable.contains(bridge) {
|
|
|
+ num_reachable += 1;
|
|
|
+ }
|
|
|
pos += BRIDGE_BYTES;
|
|
|
}
|
|
|
+ if num_reachable >= MIN_BUCKET_REACHABILITY {
|
|
|
+ // Construct a Bucket Reachability credential for this
|
|
|
+ // bucket and today's date
|
|
|
+ let today_attr: Scalar = today.into();
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
+ let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
|
|
|
+ let b = Scalar::random(&mut rng);
|
|
|
+ let P = &b * Btable;
|
|
|
+ let Q = &(b
|
|
|
+ * (reachability_priv.x[0]
|
|
|
+ + reachability_priv.x[1] * today_attr
|
|
|
+ + reachability_priv.x[2] * bucket_attr))
|
|
|
+ * Btable;
|
|
|
+ res[pos..pos + 4].copy_from_slice(&today.to_le_bytes());
|
|
|
+ res[pos + 4..pos + 36].copy_from_slice(&P.compress().as_bytes()[..]);
|
|
|
+ res[pos + 36..].copy_from_slice(&Q.compress().as_bytes()[..]);
|
|
|
+ }
|
|
|
res
|
|
|
}
|
|
|
- /// Decode a bucket from a byte array
|
|
|
- pub fn bucket_decode(data: &[u8; BUCKET_BYTES]) -> [BridgeLine; MAX_BRIDGES_PER_BUCKET] {
|
|
|
+ /// Decode a bucket from a byte array, yielding the array of
|
|
|
+ /// BridgeLine entries and an optional Bucket Reachability
|
|
|
+ /// credential
|
|
|
+ fn bucket_decode(data: &[u8; BUCKET_BYTES], bucket_attr: &Scalar) -> Bucket {
|
|
|
let mut pos: usize = 0;
|
|
|
- let mut res: [BridgeLine; MAX_BRIDGES_PER_BUCKET] = Default::default();
|
|
|
- for bridge in res.iter_mut().take(MAX_BRIDGES_PER_BUCKET) {
|
|
|
+ let mut bridges: [BridgeLine; MAX_BRIDGES_PER_BUCKET] = Default::default();
|
|
|
+ for bridge in bridges.iter_mut().take(MAX_BRIDGES_PER_BUCKET) {
|
|
|
*bridge = BridgeLine::decode(data[pos..pos + BRIDGE_BYTES].try_into().unwrap());
|
|
|
pos += BRIDGE_BYTES;
|
|
|
}
|
|
|
- res
|
|
|
+ // See if there's a nonzero date in the Bucket Reachability
|
|
|
+ // Credential
|
|
|
+ let date = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
|
|
|
+ let (optP, optQ) = if date > 0 {
|
|
|
+ (
|
|
|
+ CompressedRistretto::from_slice(&data[pos + 4..pos + 36]).decompress(),
|
|
|
+ CompressedRistretto::from_slice(&data[pos + 36..]).decompress(),
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ (None, None)
|
|
|
+ };
|
|
|
+ if let (Some(P), Some(Q)) = (optP, optQ) {
|
|
|
+ let date_attr: Scalar = date.into();
|
|
|
+ (
|
|
|
+ bridges,
|
|
|
+ Some(cred::BucketReachability {
|
|
|
+ P,
|
|
|
+ Q,
|
|
|
+ date: date_attr,
|
|
|
+ bucket: *bucket_attr,
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ (bridges, None)
|
|
|
+ }
|
|
|
}
|
|
|
/// Create a random BridgeLine for testing
|
|
|
#[cfg(test)]
|
|
@@ -134,6 +208,12 @@ pub struct BridgeTable {
|
|
|
pub keys: Vec<[u8; 16]>,
|
|
|
pub buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
|
|
|
pub encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>,
|
|
|
+ pub reachable: HashSet<BridgeLine>,
|
|
|
+ /// The date the buckets were last encrypted to make the encbucket.
|
|
|
+ ///
|
|
|
+ /// The encbucket must be rebuilt each day so that the Bucket
|
|
|
+ /// Reachability credentials in the buckets can be refreshed.
|
|
|
+ pub date_last_enc: u32,
|
|
|
}
|
|
|
|
|
|
// Invariant: the lengths of the keys and buckets vectors are the same.
|
|
@@ -153,18 +233,35 @@ impl BridgeTable {
|
|
|
rng.fill_bytes(&mut key);
|
|
|
self.keys.push(key);
|
|
|
self.buckets.push(bucket);
|
|
|
+ // Mark the new bridges as available
|
|
|
+ for b in bucket.iter() {
|
|
|
+ if b.port > 0 {
|
|
|
+ self.reachable.insert(*b);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// Create the vector of encrypted buckets from the keys and buckets
|
|
|
/// in the BridgeTable. All of the entries will be (randomly)
|
|
|
/// re-encrypted, so it will be hidden whether any individual bucket
|
|
|
/// has changed (except for entirely new buckets, of course).
|
|
|
- pub fn encrypt_table(&mut self) {
|
|
|
+ /// Bucket Reachability credentials are added to the buckets when
|
|
|
+ /// enough (at least MIN_BUCKET_REACHABILITY) bridges in the bucket
|
|
|
+ /// are reachable.
|
|
|
+ pub fn encrypt_table(&mut self, today: u32, reachability_priv: &IssuerPrivKey) {
|
|
|
let mut rng = rand::thread_rng();
|
|
|
self.encbuckets.clear();
|
|
|
- for (key, bucket) in self.keys.iter().zip(self.buckets.iter()) {
|
|
|
+ // We want id to be a u32, so we use .zip(0u32..) instead of
|
|
|
+ // enumerate()
|
|
|
+ for ((key, bucket), id) in self.keys.iter().zip(self.buckets.iter()).zip(0u32..) {
|
|
|
let mut encbucket: [u8; ENC_BUCKET_BYTES] = [0; ENC_BUCKET_BYTES];
|
|
|
- let plainbucket: [u8; BUCKET_BYTES] = BridgeLine::bucket_encode(bucket);
|
|
|
+ let plainbucket: [u8; BUCKET_BYTES] = BridgeLine::bucket_encode(
|
|
|
+ bucket,
|
|
|
+ &self.reachable,
|
|
|
+ today,
|
|
|
+ &to_scalar(id, key),
|
|
|
+ reachability_priv,
|
|
|
+ );
|
|
|
// Set the AES key
|
|
|
let aeskey = GenericArray::from_slice(key);
|
|
|
// Pick a random nonce
|
|
@@ -178,13 +275,16 @@ impl BridgeTable {
|
|
|
encbucket[12..].copy_from_slice(ciphertext.as_slice());
|
|
|
self.encbuckets.push(encbucket);
|
|
|
}
|
|
|
+ self.date_last_enc = today;
|
|
|
}
|
|
|
|
|
|
- /// Decrypt an individual encrypted bucket, given its key
|
|
|
+ /// Decrypt an individual encrypted bucket, given its id, key, and
|
|
|
+ /// the encrypted bucket itself
|
|
|
pub fn decrypt_bucket(
|
|
|
+ id: u32,
|
|
|
key: &[u8; 16],
|
|
|
encbucket: &[u8; ENC_BUCKET_BYTES],
|
|
|
- ) -> Result<[BridgeLine; MAX_BRIDGES_PER_BUCKET], aead::Error> {
|
|
|
+ ) -> Result<Bucket, aead::Error> {
|
|
|
// Set the nonce and the key
|
|
|
let nonce = GenericArray::from_slice(&encbucket[0..12]);
|
|
|
let aeskey = GenericArray::from_slice(key);
|
|
@@ -194,17 +294,14 @@ impl BridgeTable {
|
|
|
// Convert the plaintext bytes to an array of BridgeLines
|
|
|
Ok(BridgeLine::bucket_decode(
|
|
|
plaintext.as_slice().try_into().unwrap(),
|
|
|
+ &to_scalar(id, key),
|
|
|
))
|
|
|
}
|
|
|
|
|
|
/// Decrypt an individual encrypted bucket, given its id and key
|
|
|
- pub fn decrypt_bucket_id(
|
|
|
- &self,
|
|
|
- id: u32,
|
|
|
- key: &[u8; 16],
|
|
|
- ) -> Result<[BridgeLine; MAX_BRIDGES_PER_BUCKET], aead::Error> {
|
|
|
+ pub fn decrypt_bucket_id(&self, id: u32, key: &[u8; 16]) -> Result<Bucket, aead::Error> {
|
|
|
let encbucket = self.encbuckets[id as usize];
|
|
|
- BridgeTable::decrypt_bucket(key, &encbucket)
|
|
|
+ BridgeTable::decrypt_bucket(id, key, &encbucket)
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -216,6 +313,8 @@ mod tests {
|
|
|
|
|
|
#[test]
|
|
|
fn test_bridge_table() -> Result<(), aead::Error> {
|
|
|
+ // Create private keys for the Bucket Reachability credentials
|
|
|
+ let reachability_priv = IssuerPrivKey::new(2);
|
|
|
// Create an empty bridge table
|
|
|
let mut btable: BridgeTable = Default::default();
|
|
|
// Make 20 buckets with one random bridge each
|
|
@@ -233,8 +332,13 @@ mod tests {
|
|
|
];
|
|
|
btable.new_bucket(bucket);
|
|
|
}
|
|
|
+ let today: u32 = time::OffsetDateTime::now_utc()
|
|
|
+ .date()
|
|
|
+ .julian_day()
|
|
|
+ .try_into()
|
|
|
+ .unwrap();
|
|
|
// Create the encrypted bridge table
|
|
|
- btable.encrypt_table();
|
|
|
+ btable.encrypt_table(today, &reachability_priv);
|
|
|
// Try to decrypt a 1-bridge bucket
|
|
|
let key7 = btable.keys[7];
|
|
|
let bucket7 = btable.decrypt_bucket_id(7, &key7)?;
|