/*! 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 uCMZ credentials (Orr`u, 2024 https://eprint.iacr.org/2024/1552.pdf) which improves issuer efficiency over our original CMZ14 credential (GGM version, which is more efficient, but makes a stronger security assumption) implementation: "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 want Scalars to be lowercase letters, and Points and credentials // to be capital letters #![allow(non_snake_case)] use curve25519_dalek::scalar::Scalar; #[cfg(feature = "bridgeauth")] use ed25519_dalek::{Signature, SignatureError, SigningKey, Verifier, VerifyingKey}; use subtle::ConstantTimeEq; #[cfg(feature = "bridgeauth")] use chrono::{DateTime, Utc}; #[cfg(feature = "bridgeauth")] use cmz::*; #[cfg(feature = "bridgeauth")] use curve25519_dalek::ristretto::RistrettoPoint as G; use rand_core::OsRng; #[cfg(feature = "bridgeauth")] use sha2::Sha512; pub mod bridge_table; pub mod dup_filter; pub mod lox_creds; pub mod migration_table; pub mod proto { pub mod blockage_migration; pub mod errors; pub mod level_up; pub mod open_invite; pub mod redeem_invite; } #[cfg(feature = "bridgeauth")] use bridge_table::BridgeTable; // BridgeLine, EncryptedBucket, MAX_BRIDGES_PER_BUCKET, MIN_BUCKET_REACHABILITY, //}; use lox_creds::*; #[cfg(feature = "bridgeauth")] use migration_table::{MigrationTable, MigrationType}; #[cfg(feature = "bridgeauth")] use serde::{Deserialize, Serialize}; #[cfg(feature = "bridgeauth")] use std::collections::HashSet; /// Number of times a given invitation is ditributed pub const OPENINV_K: u32 = 10; /// TODO: Decide on maximum daily number of invitations to be distributed pub const MAX_DAILY_BRIDGES: u32 = 100; /// 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, Serialize, Deserialize)] #[cfg(feature = "bridgeauth")] pub struct BridgeDb { /// The keypair for signing open invitations keypair: SigningKey, /// The public key for verifying open invitations pub pubkey: VerifyingKey, /// The set of open-invitation buckets openinv_buckets: HashSet, /// The set of open invitation buckets that have been distributed distributed_buckets: Vec, #[serde(skip)] today: DateTime, pub current_k: u32, pub daily_bridges_distributed: u32, } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg(feature = "bridgeauth")] pub struct OldKeyStore { // /// Most recently outdated lox secret and private keys for verifying update_cred credentials priv_key: CMZPrivkey, // /// The public key for verifying update_cred credentials pub pub_key: CMZPubkey, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[cfg(feature = "bridgeauth")] pub struct OldKeys { /// Most recently outdated lox secret and private keys for verifying update_cred credentials lox_keys: Vec, /// Most recently outdated open_invitation VerifyingKey for verifying update_openinv tokens bridgedb_key: Vec, /// Most recently outdated invitation secret and private keys for verifying update_inv credentials invitation_keys: Vec, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[cfg(feature = "bridgeauth")] pub struct OldFilters { /// Most recently outdated lox id filter lox_filter: Vec>, /// Most recently outdated open invitation filter openinv_filter: Vec>, /// Most recently outdated invitation filter invitation_filter: Vec>, } /// 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 #[cfg(feature = "bridgeauth")] impl BridgeDb { /// Create the BridgeDb. pub fn new() -> Self { let mut csprng = OsRng {}; let keypair = SigningKey::generate(&mut csprng); let pubkey = keypair.verifying_key(); Self { keypair, pubkey, openinv_buckets: Default::default(), distributed_buckets: Default::default(), today: Utc::now(), current_k: 0, daily_bridges_distributed: 0, } } /// 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: VerifyingKey, ) -> 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()); let s = Scalar::from_canonical_bytes(invitation[0..32].try_into().unwrap()); if s.is_some().into() { Ok((s.unwrap(), bucket)) } else { // It should never happen that there's a valid signature on // an invalid serialization of a Scalar, but check anyway. Err(SignatureError::new()) } } } /// The bridge authority. This will typically be a singleton object. #[cfg(feature = "bridgeauth")] #[derive(Debug, Serialize, Deserialize)] pub struct BridgeAuth { /// The private key for the main Lox credential lox_priv: CMZPrivkey, /// The public key for the main Lox credential pub lox_pub: CMZPubkey, /// The private key for migration credentials migration_priv: CMZPrivkey, /// The public key for migration credentials pub migration_pub: CMZPubkey, /// The private key for migration key credentials migrationkey_priv: CMZPrivkey, /// The public key for migration key credentials pub migrationkey_pub: CMZPubkey, /// The private key for bucket reachability credentials reachability_priv: CMZPrivkey, /// The public key for bucket reachability credentials pub reachability_pub: CMZPubkey, /// The private key for invitation credentials invitation_priv: CMZPrivkey, /// The public key for invitation credentials pub invitation_pub: CMZPubkey, /// The public key of the BridgeDb issuing open invitations pub bridgedb_pub: VerifyingKey, /// The bridge table bridge_table: BridgeTable, // Map of bridge fingerprint to values needed to verify TP reports //pub tp_bridge_infos: HashMap, /// The migration tables trustup_migration_table: MigrationTable, blockage_migration_table: MigrationTable, /// Duplicate filter for open invitations bridgedb_pub_filter: dup_filter::DupFilter, /// Duplicate filter for Lox credential ids id_filter: dup_filter::DupFilter, /// Duplicate filter for Invitation credential ids inv_id_filter: dup_filter::DupFilter, /// Duplicate filter for trust promotions (from untrusted level 0 to /// trusted level 1) trust_promotion_filter: dup_filter::DupFilter, // Outdated Lox Keys to be populated with the old Lox private and public keys // after a key rotation old_keys: OldKeys, old_filters: OldFilters, /// For testing only: offset of the true time to the simulated time #[serde(skip)] time_offset: time::Duration, } #[cfg(feature = "bridgeauth")] impl BridgeAuth { pub fn new(bridgedb_pub: VerifyingKey) -> Self { // Initialization let mut rng = rand::thread_rng(); cmz_group_init(G::hash_from_bytes::(b"CMZ Generator A")); // Create the private and public keys for each of the types of // credential with 'true' to indicate uCMZ let (lox_priv, lox_pub) = Lox::gen_keys(&mut rng, true); let (migration_priv, migration_pub) = Migration::gen_keys(&mut rng, true); let (migrationkey_priv, migrationkey_pub) = MigrationKey::gen_keys(&mut rng, true); let (reachability_priv, reachability_pub) = BucketReachability::gen_keys(&mut rng, true); let (invitation_priv, invitation_pub) = Invitation::gen_keys(&mut rng, true); Self { lox_priv, lox_pub, migration_priv, migration_pub, migrationkey_priv, migrationkey_pub, reachability_priv, reachability_pub, invitation_priv, invitation_pub, bridgedb_pub, bridge_table: Default::default(), // tp_bridge_infos: HashMap::::new(), trustup_migration_table: MigrationTable::new(MigrationType::TrustUpgrade), blockage_migration_table: MigrationTable::new(MigrationType::Blockage), bridgedb_pub_filter: Default::default(), id_filter: Default::default(), inv_id_filter: Default::default(), trust_promotion_filter: Default::default(), time_offset: time::Duration::ZERO, old_keys: Default::default(), old_filters: Default::default(), } } /// Get today's (real or simulated) date as u32 pub fn today(&self) -> u32 { // We will not encounter negative Julian dates (~6700 years ago) // or ones larger than 32 bits (time::OffsetDateTime::now_utc().date() + self.time_offset) .to_julian_day() .try_into() .unwrap() } /// Get today's (real or simulated) date as a DateTime value pub fn today_date(&self) -> DateTime { Utc::now() } } // Try to extract a u32 from a Scalar pub fn scalar_u32(s: &Scalar) -> Option { // Check that the top 28 bytes of the Scalar are 0 let sbytes: &[u8; 32] = 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())) }