소스 검색

Create Bucket Reachability credentials and put them in buckets as appropriate

Ian Goldberg 3 년 전
부모
커밋
d7559ab558
4개의 변경된 파일228개의 추가작업 그리고 57개의 파일을 삭제
  1. 129 25
      src/bridge_table.rs
  2. 52 27
      src/cred.rs
  3. 36 0
      src/lib.rs
  4. 11 5
      src/tests.rs

+ 129 - 25
src/bridge_table.rs

@@ -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)?;

+ 52 - 27
src/cred.rs

@@ -7,9 +7,10 @@ zero-knowledge proof of its correctness (as it does at issuing time). */
 use curve25519_dalek::ristretto::RistrettoPoint;
 use curve25519_dalek::scalar::Scalar;
 
-/// A migration credential.  This credential authorizes the holder of
-/// the Lox credential with the given id to switch from bucket
-/// from_bucket to bucket to_bucket.
+/// A migration credential.
+///
+/// This credential authorizes the holder of the Lox credential with the
+/// given id to switch from bucket from_bucket to bucket to_bucket.
 #[derive(Debug)]
 pub struct Migration {
     pub P: RistrettoPoint,
@@ -19,15 +20,17 @@ pub struct Migration {
     pub to_bucket: Scalar,
 }
 
-/// The main user credential in the Lox system.  Its id is jointly
-/// generated by the user and the BA (bridge authority), but known only
-/// to the user.  The level_since date is the Julian date of when this
-/// user was changed to the current trust level. (P_noopmigration,
-/// Q_noopmigration) are the MAC on the implicit no-op migration
-/// credential formed by the attributes (id, bucket, bucket), which
-/// authorizes the user to switch from its current bucket to the same
-/// bucket (i.e., a no-op).  This can be useful for hiding from the BA
-/// whether or not the user is performing a bucket migration.
+/// The main user credential in the Lox system.
+///
+/// Its id is jointly generated by the user and the BA (bridge
+/// authority), but known only to the user.  The level_since date is the
+/// Julian date of when this user was changed to the current trust
+/// level. (P_noopmigration, Q_noopmigration) are the MAC on the
+/// implicit no-op migration credential formed by the attributes (id,
+/// bucket, bucket), which authorizes the user to switch from its
+/// current bucket to the same bucket (i.e., a no-op).  This can be
+/// useful for hiding from the BA whether or not the user is performing
+/// a bucket migration.
 #[derive(Debug)]
 pub struct Lox {
     pub P: RistrettoPoint,
@@ -42,18 +45,40 @@ pub struct Lox {
     pub Q_noopmigration: RistrettoPoint,
 }
 
-// The migration key credential is never actually instantiated.  It is
-// an implicit credential with the following attributes:
-// - lox_id: Scalar,
-// - from_bucket: Scalar
-// Plus the usual (P,Q) MAC.  This credential type does have an
-// associated private and public key, however.  The idea is that if a
-// user proves (in zero knowledge) that their Lox credential entitles
-// them to migrate from one bucket to another, the BA will issue a
-// (blinded, so the BA will not know the values of the attributes or of
-// Q) MAC on this implicit credential.  The Q value will then be used
-// (actually, a hash of lox_id, from_bucket, and Q) to encrypt the
-// to_bucket, P, and Q fields of a Migration credential.  That way,
-// people entitled to migrate buckets can receive a Migration credential
-// with their new bucket, without the BA learning either their old or
-// new buckets.
+/// The migration key credential.
+///
+/// This credential is never actually instantiated.  It is an implicit
+/// credential on attributes lox_id and from_bucket.  This credential
+/// type does have an associated private and public key, however.  The
+/// idea is that if a user proves (in zero knowledge) that their Lox
+/// credential entitles them to migrate from one bucket to another, the
+/// BA will issue a (blinded, so the BA will not know the values of the
+/// attributes or of Q) MAC on this implicit credential.  The Q value
+/// will then be used (actually, a hash of lox_id, from_bucket, and Q)
+/// to encrypt the to_bucket, P, and Q fields of a Migration credential.
+/// That way, people entitled to migrate buckets can receive a Migration
+/// credential with their new bucket, without the BA learning either
+/// their old or new buckets.
+#[derive(Debug)]
+pub struct MigrationKey {
+    pub P: RistrettoPoint,
+    pub Q: RistrettoPoint,
+    pub lox_id: Scalar,
+    pub from_bucket: Scalar,
+}
+
+/// The Bucket Reachability credential.
+///
+/// Each day, a credential of this type is put in each bucket that has
+/// at least a (configurable) threshold number of bridges that have not
+/// been blocked as of the given date.  Users can present this
+/// credential (in zero knowledge) with today's date to prove that the
+/// bridges in their bucket have not been blocked, in order to gain a
+/// trust level.
+#[derive(Debug)]
+pub struct BucketReachability {
+    pub P: RistrettoPoint,
+    pub Q: RistrettoPoint,
+    pub date: Scalar,
+    pub bucket: Scalar,
+}

+ 36 - 0
src/lib.rs

@@ -186,6 +186,10 @@ pub struct BridgeAuth {
     migrationkey_priv: IssuerPrivKey,
     /// The public key for migration key credentials
     pub migrationkey_pub: IssuerPubKey,
+    /// The private key for bucket reachability credentials
+    reachability_priv: IssuerPrivKey,
+    /// The public key for bucket reachability credentials
+    pub reachability_pub: IssuerPubKey,
 
     /// The public key of the BridgeDb issuing open invitations
     pub bridgedb_pub: PublicKey,
@@ -218,6 +222,8 @@ impl BridgeAuth {
         let migration_pub = IssuerPubKey::new(&migration_priv);
         let migrationkey_priv = IssuerPrivKey::new(2);
         let migrationkey_pub = IssuerPubKey::new(&migrationkey_priv);
+        let reachability_priv = IssuerPrivKey::new(2);
+        let reachability_pub = IssuerPubKey::new(&reachability_priv);
         Self {
             lox_priv,
             lox_pub,
@@ -225,6 +231,8 @@ impl BridgeAuth {
             migration_pub,
             migrationkey_priv,
             migrationkey_pub,
+            reachability_priv,
+            reachability_pub,
             bridgedb_pub,
             bridge_table: Default::default(),
             migration_table: Default::default(),
@@ -258,6 +266,20 @@ impl BridgeAuth {
             .unwrap()
     }
 
+    /// Get a reference to the encrypted bridge table.
+    ///
+    /// Be sure to call this function when you want the latest version
+    /// of the table, since it will put fresh Bucket Reachability
+    /// credentials in the buckets each day.
+    pub fn enc_bridge_table(&mut self) -> &Vec<[u8; bridge_table::ENC_BUCKET_BYTES]> {
+        let today = self.today();
+        if self.bridge_table.date_last_enc != today {
+            self.bridge_table
+                .encrypt_table(today, &self.reachability_priv);
+        }
+        &self.bridge_table.encbuckets
+    }
+
     #[cfg(test)]
     /// Verify the two MACs on a Lox credential
     pub fn verify_lox(&self, cred: &cred::Lox) -> bool {
@@ -297,6 +319,20 @@ impl BridgeAuth {
             * cred.P;
         return Q == cred.Q;
     }
+
+    #[cfg(test)]
+    /// Verify the MAC on a Bucket Reachability credential
+    pub fn verify_reachability(&self, cred: &cred::BucketReachability) -> bool {
+        if cred.P.is_identity() {
+            return false;
+        }
+
+        let Q = (self.reachability_priv.x[0]
+            + cred.date * self.reachability_priv.x[1]
+            + cred.bucket * self.reachability_priv.x[2])
+            * cred.P;
+        return Q == cred.Q;
+    }
 }
 
 /// Try to extract a u64 from a Scalar

+ 11 - 5
src/tests.rs

@@ -28,7 +28,7 @@ fn test_open_invite() {
         ba.bridge_table.new_bucket(bucket);
     }
     // Create the encrypted bridge table
-    ba.bridge_table.encrypt_table();
+    ba.enc_bridge_table();
 
     // Issue an open invitation
     let inv = bdb.invite();
@@ -40,7 +40,9 @@ fn test_open_invite() {
 
     // Check that we can use the credential to read a bucket
     let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
-    let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap();
+    let encbuckets = ba.enc_bridge_table();
+    let bucket =
+        bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap();
     println!("cred = {:?}", cred);
     println!("bucket = {:?}", bucket);
     assert!(ba.verify_lox(&cred));
@@ -74,7 +76,7 @@ fn setup() -> (BridgeDb, BridgeAuth) {
         ba.migration_table.table.insert(3 * i + 2, 15 + i);
     }
     // Create the encrypted bridge table
-    ba.bridge_table.encrypt_table();
+    ba.enc_bridge_table();
 
     (bdb, ba)
 }
@@ -109,7 +111,9 @@ fn test_trust_promotion() {
     // Check that we can use the to_bucket in the Migration credenital
     // to read a bucket
     let (id, key) = bridge_table::from_scalar(migcred.to_bucket).unwrap();
-    let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap();
+    let encbuckets = ba.enc_bridge_table();
+    let bucket =
+        bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap();
     println!("bucket = {:?}", bucket);
 }
 
@@ -128,6 +132,8 @@ fn test_level0_migration() {
     println!("newloxcred = {:?}", newloxcred);
     // Check that we can use the credenital to read a bucket
     let (id, key) = bridge_table::from_scalar(newloxcred.bucket).unwrap();
-    let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap();
+    let encbuckets = ba.enc_bridge_table();
+    let bucket =
+        bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap();
     println!("bucket = {:?}", bucket);
 }