From ba13545b3cda5aef4cec322cd80cf1f7be3d1ae3 Mon Sep 17 00:00:00 2001 From: Ian Goldberg Date: Sat, 1 May 2021 17:12:03 -0400 Subject: [PATCH] Create Bucket Reachability credentials and put them in buckets as appropriate --- crates/lox-library/src/bridge_table.rs | 154 +++++++++++++++++++++---- crates/lox-library/src/cred.rs | 79 ++++++++----- crates/lox-library/src/lib.rs | 36 ++++++ crates/lox-library/src/tests.rs | 16 ++- 4 files changed, 228 insertions(+), 57 deletions(-) diff --git a/crates/lox-library/src/bridge_table.rs b/crates/lox-library/src/bridge_table.rs index 39b8683..43823be 100644 --- a/crates/lox-library/src/bridge_table.rs +++ b/crates/lox-library/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, +); + +/// 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, + 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, + /// 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 { // 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 { 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)?; diff --git a/crates/lox-library/src/cred.rs b/crates/lox-library/src/cred.rs index 871f6c6..2d32b4d 100644 --- a/crates/lox-library/src/cred.rs +++ b/crates/lox-library/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, +} diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index 8ada29e..043084c 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/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 diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index aec5c37..9a43f68 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/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); }