use std::net::SocketAddr;
use chacha20poly1305::{
aead::{Aead, NewAead},
Nonce,
};
use chacha20poly1305::{ChaCha20Poly1305, Key};
use serde::{Deserialize, Serialize};
pub type BinderResult<T> = Result<T, BinderError>;
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum BinderRequestData {
GetEpochKey { level: String, epoch: u16 },
Authenticate {
username: String,
password: String,
level: String,
epoch: u16,
blinded_digest: Vec<u8>,
},
Validate {
level: String,
unblinded_digest: Vec<u8>,
unblinded_signature: mizaru::UnblindedSignature,
},
GetCaptcha,
RegisterUser {
username: String,
password: String,
captcha_id: String,
captcha_soln: String,
},
ChangePassword {
username: String,
old_password: String,
new_password: String,
},
DeleteUser { username: String, password: String },
GetExits,
AddBridgeRoute {
sosistab_pubkey: x25519_dalek::PublicKey,
bridge_address: SocketAddr,
bridge_group: String,
exit_hostname: String,
route_unixtime: u64,
exit_signature: ed25519_dalek::Signature,
},
GetBridges {
level: String,
unblinded_digest: Vec<u8>,
unblinded_signature: mizaru::UnblindedSignature,
exit_hostname: String,
},
GetFreeExits,
}
impl BinderRequestData {
pub fn encrypt(
&self,
my_esk: x25519_dalek::EphemeralSecret,
recipient: x25519_dalek::PublicKey,
) -> (EncryptedBinderRequestData, [u8; 32]) {
let plain = bincode::serialize(self).unwrap();
let sender_epk = x25519_dalek::PublicKey::from(&my_esk);
let shared_sec = my_esk.diffie_hellman(&recipient);
let up_key = blake3::keyed_hash(blake3::hash(b"request").as_bytes(), shared_sec.as_bytes());
let up_key = Key::from_slice(up_key.as_bytes());
let ciphertext = ChaCha20Poly1305::new(up_key)
.encrypt(Nonce::from_slice(&[0u8; 12]), plain.as_slice())
.unwrap();
(
EncryptedBinderRequestData {
sender_epk,
ciphertext,
},
*blake3::keyed_hash(blake3::hash(b"response").as_bytes(), shared_sec.as_bytes())
.as_bytes(),
)
}
#[allow(clippy::match_like_matches_macro)]
pub fn is_idempotent(&self) -> bool {
match self {
BinderRequestData::GetEpochKey { .. } => true,
BinderRequestData::GetCaptcha { .. } => true,
BinderRequestData::GetExits { .. } => true,
BinderRequestData::GetFreeExits { .. } => true,
BinderRequestData::GetBridges { .. } => true,
_ => false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct EncryptedBinderRequestData {
sender_epk: x25519_dalek::PublicKey,
ciphertext: Vec<u8>,
}
impl EncryptedBinderRequestData {
pub fn decrypt(
&self,
my_lsk: &x25519_dalek::StaticSecret,
) -> Option<(BinderRequestData, [u8; 32])> {
let shared_sec = my_lsk.diffie_hellman(&self.sender_epk);
let up_key = blake3::keyed_hash(blake3::hash(b"request").as_bytes(), shared_sec.as_bytes());
let plaintext = ChaCha20Poly1305::new(Key::from_slice(up_key.as_bytes()))
.decrypt(Nonce::from_slice(&[0u8; 12]), self.ciphertext.as_slice())
.ok()?;
Some((
bincode::deserialize(&plaintext).ok()?,
*blake3::keyed_hash(blake3::hash(b"response").as_bytes(), shared_sec.as_bytes())
.as_bytes(),
))
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum BinderResponse {
Okay,
GetEpochKeyResp(rsa::RSAPublicKey),
AuthenticateResp {
user_info: UserInfo,
blind_signature: mizaru::BlindedSignature,
},
ValidateResp(bool),
GetCaptchaResp {
captcha_id: String,
png_data: Vec<u8>,
},
GetExitsResp(Vec<ExitDescriptor>),
GetBridgesResp(Vec<BridgeDescriptor>),
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct ExitDescriptor {
pub hostname: String,
pub signing_key: ed25519_dalek::PublicKey,
pub country_code: String,
pub city_code: String,
pub sosistab_key: x25519_dalek::PublicKey,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct BridgeDescriptor {
pub endpoint: SocketAddr,
pub sosistab_key: x25519_dalek::PublicKey,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct UserInfo {
pub userid: i32,
pub username: String,
pub pwdhash: String,
pub subscription: Option<SubscriptionInfo>,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct SubscriptionInfo {
pub level: String,
pub expires_unix: i64,
}
pub fn encrypt_binder_response(
this: &BinderResult<BinderResponse>,
reply_key: [u8; 32],
) -> EncryptedBinderResponse {
let plain = bincode::serialize(this).unwrap();
EncryptedBinderResponse(
ChaCha20Poly1305::new(Key::from_slice(&reply_key))
.encrypt(Nonce::from_slice(&[0u8; 12]), plain.as_slice())
.unwrap(),
)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedBinderResponse(Vec<u8>);
impl EncryptedBinderResponse {
pub fn decrypt(&self, reply_key: [u8; 32]) -> Option<BinderResult<BinderResponse>> {
bincode::deserialize(
&ChaCha20Poly1305::new(Key::from_slice(&reply_key))
.decrypt(Nonce::from_slice(&[0u8; 12]), self.0.as_slice())
.ok()?,
)
.ok()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum BinderError {
#[error("no user found")]
NoUserFound,
#[error("user already exists")]
UserAlreadyExists,
#[error("wrong password")]
WrongPassword,
#[error("incorrect captcha")]
WrongCaptcha,
#[error("incorrect account level")]
WrongLevel,
#[error("database failed: `{0}`")]
DatabaseFailed(String),
#[error("other failure `{0}`")]
Other(String),
#[error("network failure `{0}`")]
Network(String),
}
impl From<std::io::Error> for BinderError {
fn from(value: std::io::Error) -> Self {
BinderError::Network(value.to_string())
}
}