use super::lox_creds::BucketReachability;
use super::{Scalar, G};
use aes_gcm::aead;
use aes_gcm::aead::{generic_array::GenericArray, Aead};
use aes_gcm::{Aes128Gcm, KeyInit};
#[cfg(feature = "bridgeauth")]
#[allow(unused_imports)]
use base64::{engine::general_purpose, Engine as _};
use cmz::{CMZCredential, CMZPrivkey, CMZPubkey};
use curve25519_dalek::ristretto::CompressedRistretto;
#[allow(unused_imports)]
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use sha1::{Digest, Sha1};
use std::collections::{HashMap, HashSet};
use subtle::ConstantTimeEq;
pub const BRIDGE_BYTES: usize = 250;
pub const BRIDGE_INFO_BYTES: usize = BRIDGE_BYTES - 46;
pub const MAX_BRIDGES_PER_BUCKET: usize = 3;
pub const MIN_BUCKET_REACHABILITY: usize = 2;
#[serde_as]
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct BridgeLine {
pub addr: [u8; 16],
pub port: u16,
#[serde_as(as = "DisplayFromStr")]
pub uid_fingerprint: u64,
pub unhashed_fingerprint: [u8; 20], #[serde_as(as = "[_; BRIDGE_INFO_BYTES]")]
pub info: [u8; BRIDGE_INFO_BYTES],
}
impl BridgeLine {
pub fn get_hashed_fingerprint(&self) -> [u8; 20] {
let mut hasher = Sha1::new();
hasher.update(self.unhashed_fingerprint);
hasher.finalize().into()
}
}
type Bucket = (
[BridgeLine; MAX_BRIDGES_PER_BUCKET],
Option<BucketReachability>,
);
pub const BUCKET_BYTES: usize = BRIDGE_BYTES * MAX_BRIDGES_PER_BUCKET + 4 + 32 + 32;
pub const ENC_BUCKET_BYTES: usize = BUCKET_BYTES + 12 + 16;
impl Default for BridgeLine {
fn default() -> Self {
Self {
addr: [0; 16],
port: 0,
uid_fingerprint: 0,
unhashed_fingerprint: [0; 20],
info: [0; BRIDGE_INFO_BYTES],
}
}
}
impl BridgeLine {
pub fn encode(&self) -> [u8; BRIDGE_BYTES] {
let mut res: [u8; BRIDGE_BYTES] = [0; BRIDGE_BYTES];
res[0..16].copy_from_slice(&self.addr);
res[16..18].copy_from_slice(&self.port.to_be_bytes());
res[18..26].copy_from_slice(&self.uid_fingerprint.to_be_bytes());
res[26..46].copy_from_slice(&self.unhashed_fingerprint);
res[46..].copy_from_slice(&self.info);
res
}
pub fn decode(data: &[u8; BRIDGE_BYTES]) -> Self {
let mut res: Self = Default::default();
res.addr.copy_from_slice(&data[0..16]);
res.port = u16::from_be_bytes(data[16..18].try_into().unwrap());
res.uid_fingerprint = u64::from_be_bytes(data[18..26].try_into().unwrap());
res.unhashed_fingerprint.copy_from_slice(&data[26..46]);
res.info.copy_from_slice(&data[46..]);
res
}
pub fn bucket_encode(
rng: &mut (impl CryptoRng + RngCore),
bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET],
reachable: &HashMap<BridgeLine, Vec<(u32, usize)>>,
today: u32,
bucket_attr: &Scalar,
reachability_priv: &CMZPrivkey<G>,
) -> [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_key(bridge) {
num_reachable += 1;
}
pos += BRIDGE_BYTES;
}
if num_reachable >= MIN_BUCKET_REACHABILITY {
let today_attr: Scalar = today.into();
let mut B = BucketReachability::using_privkey(reachability_priv);
B.date = Some(today_attr);
B.bucket = Some(*bucket_attr);
let _ = B.create_MAC(rng, reachability_priv);
res[pos..pos + 4].copy_from_slice(&today.to_le_bytes());
res[pos + 4..pos + 36].copy_from_slice(B.MAC.P.compress().as_bytes());
res[pos + 36..].copy_from_slice(B.MAC.Q.compress().as_bytes());
}
res
}
fn bucket_decode(
data: &[u8; BUCKET_BYTES],
bucket_attr: &Scalar,
reachability_pub: &CMZPubkey<G>,
) -> Bucket {
let mut pos: usize = 0;
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;
}
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])
.expect("Unable to extract P from bucket")
.decompress(),
CompressedRistretto::from_slice(&data[pos + 36..])
.expect("Unable to extract Q from bucket")
.decompress(),
)
} else {
(None, None)
};
if let (Some(P), Some(Q)) = (optP, optQ) {
let date_attr: Scalar = date.into();
let mut B = BucketReachability::using_pubkey(reachability_pub);
B.date = Some(date_attr);
B.bucket = Some(*bucket_attr);
B.MAC.P = P;
B.MAC.Q = Q;
(bridges, Some(B))
} else {
(bridges, None)
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(try_from = "Vec<u8>", into = "Vec<u8>")]
pub struct EncryptedBucket([u8; ENC_BUCKET_BYTES]);
impl From<EncryptedBucket> for Vec<u8> {
fn from(e: EncryptedBucket) -> Vec<u8> {
e.0.into()
}
}
#[derive(thiserror::Error, Debug)]
#[error("wrong slice length")]
pub struct WrongSliceLengthError;
impl TryFrom<Vec<u8>> for EncryptedBucket {
type Error = WrongSliceLengthError;
fn try_from(v: Vec<u8>) -> Result<EncryptedBucket, Self::Error> {
Ok(EncryptedBucket(
*Box::<[u8; ENC_BUCKET_BYTES]>::try_from(v).map_err(|_| WrongSliceLengthError)?,
))
}
}
#[derive(Debug, Serialize, Deserialize)]
struct K {
encbucket: EncryptedBucket,
}
#[serde_as]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct BridgeTable {
pub counter: u32,
pub keys: HashMap<u32, [u8; 16]>,
pub buckets: HashMap<u32, [BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
pub encbuckets: HashMap<u32, EncryptedBucket>,
#[serde_as(as = "HashMap<serde_with::json::JsonString, _>")]
pub reachable: HashMap<BridgeLine, Vec<(u32, usize)>>,
pub spares: HashSet<u32>,
pub unallocated_bridges: Vec<BridgeLine>,
pub recycleable_keys: Vec<u32>,
pub blocked_keys: Vec<(u32, u32)>,
pub open_inv_keys: Vec<(u32, u32)>,
pub date_last_enc: u32,
}
impl BridgeTable {
#[cfg(any(feature = "bridgeauth", test))]
pub fn num_buckets(&self) -> usize {
self.buckets.len()
}
#[cfg(any(feature = "bridgeauth", test))]
pub fn new_bucket(&mut self, index: u32, bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
let mut rng = rand::rngs::OsRng;
let mut key: [u8; 16] = [0; 16];
rng.fill_bytes(&mut key);
self.keys.insert(index, key);
self.buckets.insert(index, *bucket);
for (i, b) in bucket.iter().enumerate() {
if b.port > 0 {
if let Some(v) = self.reachable.get_mut(b) {
v.push((index, i));
} else {
let v = vec![(index, i)];
self.reachable.insert(*b, v);
}
}
}
}
#[cfg(any(feature = "bridgeauth", test))]
pub fn encrypt_table(&mut self, today: u32, reachability_priv: &CMZPrivkey<G>) {
let rng = &mut rand::thread_rng();
self.encbuckets.clear();
for (uid, key) in self.keys.iter() {
let bucket = self.buckets.get(uid).unwrap();
let mut encbucket: [u8; ENC_BUCKET_BYTES] = [0; ENC_BUCKET_BYTES];
let plainbucket: [u8; BUCKET_BYTES] = BridgeLine::bucket_encode(
rng,
bucket,
&self.reachable,
today,
&to_scalar(*uid, key),
reachability_priv,
);
let aeskey = GenericArray::from_slice(key);
let mut noncebytes: [u8; 12] = [0; 12];
rng.fill_bytes(&mut noncebytes);
let nonce = GenericArray::from_slice(&noncebytes);
let cipher = Aes128Gcm::new(aeskey);
let ciphertext: Vec<u8> = cipher.encrypt(nonce, plainbucket.as_ref()).unwrap();
encbucket[0..12].copy_from_slice(&noncebytes);
encbucket[12..].copy_from_slice(ciphertext.as_slice());
let k = EncryptedBucket(encbucket);
self.encbuckets.insert(*uid, k);
}
self.date_last_enc = today;
}
pub fn decrypt_bucket(
id: u32,
key: &[u8; 16],
encbucket: &EncryptedBucket,
reachability_pub: &CMZPubkey<G>,
) -> Result<Bucket, aead::Error> {
let k = K {
encbucket: *encbucket,
};
let nonce = GenericArray::from_slice(&k.encbucket.0[0..12]);
let aeskey = GenericArray::from_slice(key);
let cipher = Aes128Gcm::new(aeskey);
let plaintext: Vec<u8> = cipher.decrypt(nonce, k.encbucket.0[12..].as_ref())?;
Ok(BridgeLine::bucket_decode(
plaintext.as_slice().try_into().unwrap(),
&to_scalar(id, key),
reachability_pub,
))
}
#[cfg(any(feature = "bridgeauth", test))]
pub fn decrypt_bucket_id(
&self,
id: u32,
key: &[u8; 16],
reachability_pub: &CMZPubkey<G>,
) -> Result<Bucket, aead::Error> {
let encbucket: &EncryptedBucket = match self.encbuckets.get(&id) {
Some(encbucket) => encbucket,
None => panic!("Provided ID not found"),
};
BridgeTable::decrypt_bucket(id, key, encbucket, reachability_pub)
}
}
#[cfg(all(test, feature = "bridgeauth"))]
mod tests {
use super::*;
use crate::mock_auth::random;
use cmz::cmz_group_init;
use sha2::Sha512;
#[test]
fn test_bridge_table() -> Result<(), aead::Error> {
let mut rng = rand::thread_rng();
cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
let (reachability_priv, reachability_pub) = BucketReachability::gen_keys(&mut rng, true);
let mut btable: BridgeTable = Default::default();
for _ in 0..20 {
let bucket: [BridgeLine; 3] = [random(), Default::default(), Default::default()];
btable.counter += 1;
btable.new_bucket(btable.counter, &bucket);
}
for _ in 0..20 {
let bucket: [BridgeLine; 3] = [random(), random(), random()];
btable.counter += 1;
btable.new_bucket(btable.counter, &bucket);
}
let today: u32 = time::OffsetDateTime::now_utc()
.date()
.to_julian_day()
.try_into()
.unwrap();
btable.encrypt_table(today, &reachability_priv);
let key7 = btable.keys.get(&7u32).unwrap();
let bucket7 = btable.decrypt_bucket_id(7, key7, &reachability_pub)?;
println!("bucket 7 = {:?}", bucket7);
let key24 = btable.keys.get(&24u32).unwrap();
let bucket24 = btable.decrypt_bucket_id(24, key24, &reachability_pub)?;
println!("bucket 24 = {:?}", bucket24);
let key12 = btable.keys.get(&12u32).unwrap();
let res = btable
.decrypt_bucket_id(15, key12, &reachability_pub)
.unwrap_err();
println!("bucket key mismatch = {:?}", res);
Ok(())
}
}
pub fn to_scalar(id: u32, key: &[u8; 16]) -> Scalar {
let mut b: [u8; 32] = [0; 32];
b[0..16].copy_from_slice(key);
b[16..20].copy_from_slice(&id.to_le_bytes());
Scalar::from_canonical_bytes(b).unwrap()
}
pub fn from_scalar(s: Scalar) -> Result<(u32, [u8; 16]), aead::Error> {
let sbytes = s.as_bytes();
if sbytes[20..].ct_eq(&[0u8; 12]).unwrap_u8() == 0 {
return Err(aead::Error);
}
let id = u32::from_le_bytes(sbytes[16..20].try_into().unwrap());
let mut key: [u8; 16] = [0; 16];
key.copy_from_slice(&sbytes[..16]);
Ok((id, key))
}