Create Bucket Reachability credentials and put them in buckets as appropriate

This commit is contained in:
Ian Goldberg 2021-05-01 17:12:03 -04:00
parent 33f3de3b68
commit ba13545b3c
4 changed files with 228 additions and 57 deletions

View File

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

View File

@ -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,
}

View File

@ -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

View File

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