| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- /*! 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<u32>,
- /// The set of open invitation buckets that have been distributed
- distributed_buckets: Vec<u32>,
- #[serde(skip)]
- today: DateTime<Utc>,
- 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<G>,
- // /// The public key for verifying update_cred credentials
- pub pub_key: CMZPubkey<G>,
- }
- #[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<OldKeyStore>,
- /// Most recently outdated open_invitation VerifyingKey for verifying update_openinv tokens
- bridgedb_key: Vec<VerifyingKey>,
- /// Most recently outdated invitation secret and private keys for verifying update_inv credentials
- invitation_keys: Vec<OldKeyStore>,
- }
- #[derive(Debug, Default, Clone, Serialize, Deserialize)]
- #[cfg(feature = "bridgeauth")]
- pub struct OldFilters {
- /// Most recently outdated lox id filter
- lox_filter: Vec<dup_filter::DupFilter<Scalar>>,
- /// Most recently outdated open invitation filter
- openinv_filter: Vec<dup_filter::DupFilter<Scalar>>,
- /// Most recently outdated invitation filter
- invitation_filter: Vec<dup_filter::DupFilter<Scalar>>,
- }
- /// 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<G>,
- /// The public key for the main Lox credential
- pub lox_pub: CMZPubkey<G>,
- /// The private key for migration credentials
- migration_priv: CMZPrivkey<G>,
- /// The public key for migration credentials
- pub migration_pub: CMZPubkey<G>,
- /// The private key for migration key credentials
- migrationkey_priv: CMZPrivkey<G>,
- /// The public key for migration key credentials
- pub migrationkey_pub: CMZPubkey<G>,
- /// The private key for bucket reachability credentials
- reachability_priv: CMZPrivkey<G>,
- /// The public key for bucket reachability credentials
- pub reachability_pub: CMZPubkey<G>,
- /// The private key for invitation credentials
- invitation_priv: CMZPrivkey<G>,
- /// The public key for invitation credentials
- pub invitation_pub: CMZPubkey<G>,
- /// 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<String, BridgeVerificationInfo>,
- /// The migration tables
- trustup_migration_table: MigrationTable,
- blockage_migration_table: MigrationTable,
- /// Duplicate filter for open invitations
- bridgedb_pub_filter: dup_filter::DupFilter<Scalar>,
- /// Duplicate filter for Lox credential ids
- id_filter: dup_filter::DupFilter<Scalar>,
- /// Duplicate filter for Invitation credential ids
- inv_id_filter: dup_filter::DupFilter<Scalar>,
- /// Duplicate filter for trust promotions (from untrusted level 0 to
- /// trusted level 1)
- trust_promotion_filter: dup_filter::DupFilter<Scalar>,
- // 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::<Sha512>(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::<String, BridgeVerificationInfo>::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<Utc> value
- pub fn today_date(&self) -> DateTime<Utc> {
- Utc::now()
- }
- }
- // 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: &[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()))
- }
|