use super::cred;
use super::IssuerPrivKey;
use super::CMZ_B_TABLE;
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 curve25519_dalek::ristretto::CompressedRistretto;
use curve25519_dalek::ristretto::RistrettoBasepointTable;
use curve25519_dalek::scalar::Scalar;
use rand::RngCore;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::collections::{HashMap, HashSet};
use std::convert::{TryFrom, TryInto};
use subtle::ConstantTimeEq;
pub const BRIDGE_BYTES: usize = 250;
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,
#[serde_as(as = "[_; BRIDGE_BYTES - 26]")]
pub info: [u8; BRIDGE_BYTES - 26],
}
type Bucket = (
[BridgeLine; MAX_BRIDGES_PER_BUCKET],
Option<cred::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,
info: [0; BRIDGE_BYTES - 26],
}
}
}
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..].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.info.copy_from_slice(&data[26..]);
res
}
pub fn bucket_encode(
bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET],
reachable: &HashMap<BridgeLine, Vec<(u32, usize)>>,
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_key(bridge) {
num_reachable += 1;
}
pos += BRIDGE_BYTES;
}
if num_reachable >= MIN_BUCKET_REACHABILITY {
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
}
fn bucket_decode(data: &[u8; BUCKET_BYTES], bucket_attr: &Scalar) -> 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();
(
bridges,
Some(cred::BucketReachability {
P,
Q,
date: date_attr,
bucket: *bucket_attr,
}),
)
} else {
(bridges, None)
}
}
#[cfg(test)]
pub fn random() -> Self {
let mut rng = rand::thread_rng();
let mut res: Self = Default::default();
let mut addr: [u8; 4] = [0; 4];
rng.fill_bytes(&mut addr);
if addr[0] >= 224 {
rng.fill_bytes(&mut res.addr);
} else {
res.addr[10] = 255;
res.addr[11] = 255;
res.addr[12..16].copy_from_slice(&addr);
};
let ports: [u16; 4] = [443, 4433, 8080, 43079];
let portidx = (rng.next_u32() % 4) as usize;
res.port = ports[portidx];
res.uid_fingerprint = rng.next_u64();
let mut cert: [u8; 52] = [0; 52];
rng.fill_bytes(&mut cert);
let infostr: String = format!(
"obfs4 cert={}, iat-mode=0",
general_purpose::STANDARD_NO_PAD.encode(cert)
);
res.info[..infostr.len()].copy_from_slice(infostr.as_bytes());
res
}
}
#[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(feature = "bridgeauth")]
pub fn num_buckets(&self) -> usize {
self.buckets.len()
}
#[cfg(feature = "bridgeauth")]
pub fn new_bucket(&mut self, index: u32, bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
let mut rng = rand::thread_rng();
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(feature = "bridgeauth")]
pub fn encrypt_table(&mut self, today: u32, reachability_priv: &IssuerPrivKey) {
let mut rng = 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(
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,
) -> 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),
))
}
#[cfg(feature = "bridgeauth")]
pub fn decrypt_bucket_id(&self, id: u32, key: &[u8; 16]) -> 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)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_table() -> Result<(), aead::Error> {
let reachability_priv = IssuerPrivKey::new(2);
let mut btable: BridgeTable = Default::default();
for _ in 0..20 {
let bucket: [BridgeLine; 3] =
[BridgeLine::random(), Default::default(), Default::default()];
btable.counter += 1;
btable.new_bucket(btable.counter, &bucket);
}
for _ in 0..20 {
let bucket: [BridgeLine; 3] = [
BridgeLine::random(),
BridgeLine::random(),
BridgeLine::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)?;
println!("bucket 7 = {:?}", bucket7);
let key24 = btable.keys.get(&24u32).unwrap();
let bucket24 = btable.decrypt_bucket_id(24, key24)?;
println!("bucket 24 = {:?}", bucket24);
let key12 = btable.keys.get(&12u32).unwrap();
let res = btable.decrypt_bucket_id(15, key12).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))
}