2021-04-26 22:44:59 -04:00
|
|
|
/*! Implementation of a new style of bridge authority for Tor that
|
|
|
|
allows users to invite other users, while protecting the social graph
|
|
|
|
from the bridge authority itself.
|
|
|
|
|
|
|
|
We use CMZ14 credentials (GGM version, which is more efficient, but
|
|
|
|
makes a stronger security assumption): "Algebraic MACs and
|
|
|
|
Keyed-Verification Anonymous Credentials" (Chase, Meiklejohn, and
|
|
|
|
Zaverucha, CCS 2014)
|
|
|
|
|
|
|
|
The notation follows that of the paper "Hyphae: Social Secret Sharing"
|
|
|
|
(Lovecruft and de Valence, 2017), Section 4. */
|
|
|
|
|
|
|
|
// We really want points to be capital letters and scalars to be
|
|
|
|
// lowercase letters
|
|
|
|
#![allow(non_snake_case)]
|
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
extern crate zkp;
|
|
|
|
|
2021-04-27 13:00:18 -04:00
|
|
|
pub mod bridge_table;
|
2021-04-28 13:36:04 -04:00
|
|
|
pub mod cred;
|
2021-04-27 08:53:22 -04:00
|
|
|
pub mod dup_filter;
|
2021-04-29 15:18:54 -04:00
|
|
|
pub mod migration_table;
|
2021-04-27 08:53:22 -04:00
|
|
|
|
2021-04-26 22:44:59 -04:00
|
|
|
use sha2::Sha512;
|
|
|
|
|
|
|
|
use rand::rngs::OsRng;
|
|
|
|
use rand::RngCore;
|
|
|
|
use std::convert::{TryFrom, TryInto};
|
|
|
|
|
|
|
|
use curve25519_dalek::constants as dalek_constants;
|
|
|
|
use curve25519_dalek::ristretto::RistrettoBasepointTable;
|
|
|
|
use curve25519_dalek::ristretto::RistrettoPoint;
|
|
|
|
use curve25519_dalek::scalar::Scalar;
|
2021-04-30 16:24:42 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
use curve25519_dalek::traits::IsIdentity;
|
2021-04-26 22:44:59 -04:00
|
|
|
|
|
|
|
use ed25519_dalek::{Keypair, PublicKey, Signature, SignatureError, Signer, Verifier};
|
2021-04-29 18:22:06 -04:00
|
|
|
use subtle::ConstantTimeEq;
|
2021-04-26 22:44:59 -04:00
|
|
|
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
pub static ref CMZ_A: RistrettoPoint =
|
|
|
|
RistrettoPoint::hash_from_bytes::<Sha512>(b"CMZ Generator A");
|
|
|
|
pub static ref CMZ_B: RistrettoPoint = dalek_constants::RISTRETTO_BASEPOINT_POINT;
|
|
|
|
pub static ref CMZ_A_TABLE: RistrettoBasepointTable = RistrettoBasepointTable::create(&CMZ_A);
|
|
|
|
pub static ref CMZ_B_TABLE: RistrettoBasepointTable =
|
|
|
|
dalek_constants::RISTRETTO_BASEPOINT_TABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct IssuerPrivKey {
|
|
|
|
x0tilde: Scalar,
|
|
|
|
x: Vec<Scalar>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IssuerPrivKey {
|
|
|
|
/// Create an IssuerPrivKey for credentials with the given number of
|
|
|
|
/// attributes.
|
|
|
|
pub fn new(n: u16) -> IssuerPrivKey {
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
let x0tilde = Scalar::random(&mut rng);
|
|
|
|
let mut x: Vec<Scalar> = Vec::with_capacity((n + 1) as usize);
|
|
|
|
|
|
|
|
// Set x to a vector of n+1 random Scalars
|
|
|
|
x.resize_with((n + 1) as usize, || Scalar::random(&mut rng));
|
|
|
|
|
|
|
|
IssuerPrivKey { x0tilde, x }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct IssuerPubKey {
|
|
|
|
X: Vec<RistrettoPoint>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IssuerPubKey {
|
|
|
|
/// Create an IssuerPubKey from the corresponding IssuerPrivKey
|
|
|
|
pub fn new(privkey: &IssuerPrivKey) -> IssuerPubKey {
|
|
|
|
let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
|
|
|
|
let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
|
|
|
|
let n_plus_one = privkey.x.len();
|
|
|
|
let mut X: Vec<RistrettoPoint> = Vec::with_capacity(n_plus_one);
|
|
|
|
|
|
|
|
// The first element is a special case; it is
|
|
|
|
// X[0] = x0tilde*A + x[0]*B
|
|
|
|
X.push(&privkey.x0tilde * Atable + &privkey.x[0] * Btable);
|
|
|
|
|
|
|
|
// The other elements (1 through n) are X[i] = x[i]*A
|
|
|
|
for i in 1..n_plus_one {
|
|
|
|
X.push(&privkey.x[i] * Atable);
|
|
|
|
}
|
|
|
|
IssuerPubKey { X }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The BridgeDb. This will typically be a singleton object. The
|
|
|
|
/// BridgeDb's role is simply to issue signed "open invitations" to
|
|
|
|
/// people who are not yet part of the system.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct BridgeDb {
|
|
|
|
/// The keypair for signing open invitations
|
|
|
|
keypair: Keypair,
|
|
|
|
/// The public key for verifying open invitations
|
|
|
|
pub pubkey: PublicKey,
|
|
|
|
/// The number of open-invitation buckets
|
|
|
|
num_openinv_buckets: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
|
|
|
|
/// bytes are the serialization of a random Scalar (the invitation id),
|
|
|
|
/// the next 4 bytes are a little-endian bucket number, and the last
|
|
|
|
/// SIGNATURE_LENGTH bytes are the signature on the first 36 bytes.
|
|
|
|
pub const OPENINV_LENGTH: usize = 32 // the length of the random
|
|
|
|
// invitation id (a Scalar)
|
|
|
|
+ 4 // the length of the u32 for the bucket number
|
|
|
|
+ ed25519_dalek::SIGNATURE_LENGTH; // the length of the signature
|
|
|
|
|
|
|
|
impl BridgeDb {
|
|
|
|
/// Create the BridgeDb.
|
2021-04-28 13:36:04 -04:00
|
|
|
pub fn new(num_openinv_buckets: u32) -> Self {
|
2021-04-26 22:44:59 -04:00
|
|
|
let mut csprng = OsRng {};
|
|
|
|
let keypair = Keypair::generate(&mut csprng);
|
|
|
|
let pubkey = keypair.public;
|
2021-04-28 13:36:04 -04:00
|
|
|
Self {
|
2021-04-26 22:44:59 -04:00
|
|
|
keypair,
|
|
|
|
pubkey,
|
|
|
|
num_openinv_buckets,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Produce an open invitation. In this example code, we just
|
|
|
|
/// choose a random open-invitation bucket.
|
|
|
|
pub fn invite(&self) -> [u8; OPENINV_LENGTH] {
|
|
|
|
let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH];
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
// Choose a random invitation id (a Scalar) and serialize it
|
|
|
|
let id = Scalar::random(&mut rng);
|
|
|
|
res[0..32].copy_from_slice(&id.to_bytes());
|
|
|
|
// Choose a random bucket number (mod num_openinv_buckets) and
|
|
|
|
// serialize it
|
|
|
|
let bucket_num = rng.next_u32() % self.num_openinv_buckets;
|
|
|
|
res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes());
|
|
|
|
// Sign the first 36 bytes and serialize it
|
|
|
|
let sig = self.keypair.sign(&res[0..(32 + 4)]);
|
|
|
|
res[(32 + 4)..].copy_from_slice(&sig.to_bytes());
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify an open invitation. Returns the invitation id and the
|
|
|
|
/// bucket number if the signature checked out. It is up to the
|
|
|
|
/// caller to then check that the invitation id has not been used
|
|
|
|
/// before.
|
|
|
|
pub fn verify(
|
|
|
|
invitation: [u8; OPENINV_LENGTH],
|
|
|
|
pubkey: PublicKey,
|
|
|
|
) -> Result<(Scalar, u32), SignatureError> {
|
|
|
|
// Pull out the signature and verify it
|
|
|
|
let sig = Signature::try_from(&invitation[(32 + 4)..])?;
|
|
|
|
pubkey.verify(&invitation[0..(32 + 4)], &sig)?;
|
|
|
|
// The signature passed. Pull out the bucket number and then
|
|
|
|
// the invitation id
|
|
|
|
let bucket = u32::from_le_bytes(invitation[32..(32 + 4)].try_into().unwrap());
|
|
|
|
match Scalar::from_canonical_bytes(invitation[0..32].try_into().unwrap()) {
|
|
|
|
// It should never happen that there's a valid signature on
|
|
|
|
// an invalid serialization of a Scalar, but check anyway.
|
|
|
|
None => Err(SignatureError::new()),
|
|
|
|
Some(s) => Ok((s, bucket)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-04-28 13:36:04 -04:00
|
|
|
|
|
|
|
/// The bridge authority. This will typically be a singleton object.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct BridgeAuth {
|
|
|
|
/// The private key for the main Lox credential
|
|
|
|
lox_priv: IssuerPrivKey,
|
|
|
|
/// The public key for the main Lox credential
|
|
|
|
pub lox_pub: IssuerPubKey,
|
|
|
|
/// The private key for migration credentials
|
|
|
|
migration_priv: IssuerPrivKey,
|
|
|
|
/// The public key for migration credentials
|
|
|
|
pub migration_pub: IssuerPubKey,
|
2021-04-29 15:18:54 -04:00
|
|
|
/// The private key for migration key credentials
|
|
|
|
migrationkey_priv: IssuerPrivKey,
|
|
|
|
/// The public key for migration key credentials
|
|
|
|
pub migrationkey_pub: IssuerPubKey,
|
2021-05-01 17:12:03 -04:00
|
|
|
/// The private key for bucket reachability credentials
|
|
|
|
reachability_priv: IssuerPrivKey,
|
|
|
|
/// The public key for bucket reachability credentials
|
|
|
|
pub reachability_pub: IssuerPubKey,
|
2021-04-28 13:36:04 -04:00
|
|
|
|
|
|
|
/// The public key of the BridgeDb issuing open invitations
|
|
|
|
pub bridgedb_pub: PublicKey,
|
|
|
|
|
2021-04-28 18:31:47 -04:00
|
|
|
/// The bridge table
|
|
|
|
bridge_table: bridge_table::BridgeTable,
|
|
|
|
|
2021-04-29 15:18:54 -04:00
|
|
|
/// The migration table
|
|
|
|
migration_table: migration_table::MigrationTable,
|
|
|
|
|
2021-04-28 13:36:04 -04:00
|
|
|
/// Duplicate filter for open invitations
|
|
|
|
openinv_filter: dup_filter::DupFilter<Scalar>,
|
|
|
|
/// Duplicate filter for credential ids
|
|
|
|
id_filter: dup_filter::DupFilter<Scalar>,
|
2021-04-29 15:18:54 -04:00
|
|
|
/// Duplicate filter for trust promotions (from untrusted level 0 to
|
|
|
|
/// trusted level 1)
|
|
|
|
trust_promotion_filter: dup_filter::DupFilter<Scalar>,
|
2021-04-28 13:36:04 -04:00
|
|
|
|
|
|
|
/// For testing only: offset of the true time to the simulated time
|
|
|
|
time_offset: time::Duration,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BridgeAuth {
|
|
|
|
pub fn new(bridgedb_pub: PublicKey) -> Self {
|
2021-04-29 15:18:54 -04:00
|
|
|
// Create the private and public keys for each of the types of
|
|
|
|
// credential, each with the appropriate number of attributes
|
2021-04-28 13:36:04 -04:00
|
|
|
let lox_priv = IssuerPrivKey::new(6);
|
|
|
|
let lox_pub = IssuerPubKey::new(&lox_priv);
|
|
|
|
let migration_priv = IssuerPrivKey::new(3);
|
|
|
|
let migration_pub = IssuerPubKey::new(&migration_priv);
|
2021-04-29 15:18:54 -04:00
|
|
|
let migrationkey_priv = IssuerPrivKey::new(2);
|
|
|
|
let migrationkey_pub = IssuerPubKey::new(&migrationkey_priv);
|
2021-05-01 17:12:03 -04:00
|
|
|
let reachability_priv = IssuerPrivKey::new(2);
|
|
|
|
let reachability_pub = IssuerPubKey::new(&reachability_priv);
|
2021-04-28 13:36:04 -04:00
|
|
|
Self {
|
|
|
|
lox_priv,
|
|
|
|
lox_pub,
|
|
|
|
migration_priv,
|
|
|
|
migration_pub,
|
2021-04-29 15:18:54 -04:00
|
|
|
migrationkey_priv,
|
|
|
|
migrationkey_pub,
|
2021-05-01 17:12:03 -04:00
|
|
|
reachability_priv,
|
|
|
|
reachability_pub,
|
2021-04-28 13:36:04 -04:00
|
|
|
bridgedb_pub,
|
2021-04-28 18:31:47 -04:00
|
|
|
bridge_table: Default::default(),
|
2021-04-29 15:18:54 -04:00
|
|
|
migration_table: Default::default(),
|
2021-04-28 13:36:04 -04:00
|
|
|
openinv_filter: Default::default(),
|
|
|
|
id_filter: Default::default(),
|
2021-04-29 15:18:54 -04:00
|
|
|
trust_promotion_filter: Default::default(),
|
2021-04-28 13:36:04 -04:00
|
|
|
time_offset: time::Duration::zero(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:24:32 -04:00
|
|
|
#[cfg(test)]
|
2021-04-28 13:36:04 -04:00
|
|
|
/// For testing only: manually advance the day by 1 day
|
|
|
|
pub fn advance_day(&mut self) {
|
|
|
|
self.time_offset += time::Duration::days(1);
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:24:32 -04:00
|
|
|
#[cfg(test)]
|
2021-04-28 13:36:04 -04:00
|
|
|
/// For testing only: manually advance the day by the given number
|
|
|
|
/// of days
|
|
|
|
pub fn advance_days(&mut self, days: u16) {
|
|
|
|
self.time_offset += time::Duration::days(days.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get today's (real or simulated) date
|
2021-05-01 15:33:45 -04:00
|
|
|
fn today(&self) -> u32 {
|
2021-04-28 18:31:47 -04:00
|
|
|
// We will not encounter negative Julian dates (~6700 years ago)
|
2021-05-01 15:33:45 -04:00
|
|
|
// or ones larger than 32 bits
|
2021-04-28 18:31:47 -04:00
|
|
|
(time::OffsetDateTime::now_utc().date() + self.time_offset)
|
|
|
|
.julian_day()
|
|
|
|
.try_into()
|
|
|
|
.unwrap()
|
2021-04-28 13:36:04 -04:00
|
|
|
}
|
2021-04-29 21:24:32 -04:00
|
|
|
|
2021-05-01 17:12:03 -04:00
|
|
|
/// 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
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:24:32 -04:00
|
|
|
#[cfg(test)]
|
2021-04-30 16:24:42 -04:00
|
|
|
/// Verify the two MACs on a Lox credential
|
2021-04-29 21:24:32 -04:00
|
|
|
pub fn verify_lox(&self, cred: &cred::Lox) -> bool {
|
2021-05-03 14:13:13 -04:00
|
|
|
if cred.P.is_identity() {
|
2021-04-30 16:24:42 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:24:32 -04:00
|
|
|
let Q = (self.lox_priv.x[0]
|
|
|
|
+ cred.id * self.lox_priv.x[1]
|
|
|
|
+ cred.bucket * self.lox_priv.x[2]
|
|
|
|
+ cred.trust_level * self.lox_priv.x[3]
|
|
|
|
+ cred.level_since * self.lox_priv.x[4]
|
|
|
|
+ cred.invites_remaining * self.lox_priv.x[5]
|
|
|
|
+ cred.invites_issued * self.lox_priv.x[6])
|
|
|
|
* cred.P;
|
2021-04-30 16:24:42 -04:00
|
|
|
|
2021-05-03 14:13:13 -04:00
|
|
|
return Q == cred.Q;
|
2021-04-29 21:24:32 -04:00
|
|
|
}
|
2021-04-30 13:30:20 -04:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
/// Verify the MAC on a Migration credential
|
|
|
|
pub fn verify_migration(&self, cred: &cred::Migration) -> bool {
|
2021-04-30 16:24:42 -04:00
|
|
|
if cred.P.is_identity() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-04-30 13:30:20 -04:00
|
|
|
let Q = (self.migration_priv.x[0]
|
|
|
|
+ cred.lox_id * self.migration_priv.x[1]
|
|
|
|
+ cred.from_bucket * self.migration_priv.x[2]
|
|
|
|
+ cred.to_bucket * self.migration_priv.x[3])
|
|
|
|
* cred.P;
|
|
|
|
return Q == cred.Q;
|
|
|
|
}
|
2021-05-01 17:12:03 -04:00
|
|
|
|
|
|
|
#[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;
|
|
|
|
}
|
2021-04-28 13:36:04 -04:00
|
|
|
}
|
2021-04-28 15:42:16 -04:00
|
|
|
|
2021-04-29 18:22:06 -04:00
|
|
|
/// Try to extract a u64 from a Scalar
|
|
|
|
pub fn scalar_u64(s: &Scalar) -> Option<u64> {
|
|
|
|
// Check that the top 24 bytes of the Scalar are 0
|
|
|
|
let sbytes = s.as_bytes();
|
|
|
|
if sbytes[8..].ct_eq(&[0u8; 24]).unwrap_u8() == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(u64::from_le_bytes(sbytes[..8].try_into().unwrap()))
|
|
|
|
}
|
|
|
|
|
2021-05-01 15:33:45 -04:00
|
|
|
/// Try to extract a u32 from a Scalar
|
|
|
|
pub fn scalar_u32(s: &Scalar) -> Option<u32> {
|
|
|
|
// Check that the top 28 bytes of the Scalar are 0
|
|
|
|
let sbytes = s.as_bytes();
|
|
|
|
if sbytes[4..].ct_eq(&[0u8; 28]).unwrap_u8() == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(u32::from_le_bytes(sbytes[..4].try_into().unwrap()))
|
|
|
|
}
|
|
|
|
|
2021-04-29 18:22:06 -04:00
|
|
|
/// Double a Scalar
|
|
|
|
pub fn scalar_dbl(s: &Scalar) -> Scalar {
|
|
|
|
s + s
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:24:32 -04:00
|
|
|
/// Double a RistrettoPoint
|
|
|
|
pub fn pt_dbl(P: &RistrettoPoint) -> RistrettoPoint {
|
|
|
|
P + P
|
|
|
|
}
|
|
|
|
|
2021-05-01 15:21:50 -04:00
|
|
|
/// The protocol modules.
|
|
|
|
///
|
|
|
|
/// Each protocol lives in a submodule. Each submodule defines structs
|
|
|
|
/// for Request (the message from the client to the bridge authority),
|
|
|
|
/// State (the state held by the client while waiting for the reply),
|
|
|
|
/// and Response (the message from the bridge authority to the client).
|
|
|
|
/// Each submodule defines functions request, which produces a (Request,
|
|
|
|
/// State) pair, and handle_response, which consumes a State and a
|
|
|
|
/// Response. It also adds a handle_* function to the BridgeAuth struct
|
|
|
|
/// that consumes a Request and produces a Result<Response, ProofError>.
|
|
|
|
pub mod proto {
|
2021-05-01 22:25:32 -04:00
|
|
|
pub mod level_up;
|
2021-05-01 15:21:50 -04:00
|
|
|
pub mod migration;
|
|
|
|
pub mod open_invite;
|
|
|
|
pub mod trust_promotion;
|
|
|
|
}
|
2021-04-28 18:48:52 -04:00
|
|
|
|
2021-04-29 16:12:53 -04:00
|
|
|
// Unit tests
|
2021-04-28 18:48:52 -04:00
|
|
|
#[cfg(test)]
|
2021-04-29 16:12:53 -04:00
|
|
|
mod tests;
|