Generated
+209
-12
@@ -2,6 +2,16 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99"
|
||||
dependencies = [
|
||||
"crypto-common 0.2.2",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
@@ -108,6 +118,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.5.1"
|
||||
@@ -145,12 +164,58 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.1",
|
||||
"crypto-common 0.2.2",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmov"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -160,6 +225,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -170,6 +244,26 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"hybrid-array",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctutils"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
|
||||
dependencies = [
|
||||
"cmov",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
@@ -177,10 +271,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"digest 0.10.7",
|
||||
"fiat-crypto 0.2.9",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.1",
|
||||
"curve25519-dalek-derive",
|
||||
"fiat-crypto 0.3.0",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
@@ -209,7 +318,7 @@ version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"const-oid 0.9.6",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -219,8 +328,19 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.1",
|
||||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -239,11 +359,11 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"curve25519-dalek 4.1.3",
|
||||
"ed25519",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -255,15 +375,19 @@ dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"bs58",
|
||||
"chacha20poly1305",
|
||||
"curve25519-dalek 5.0.0",
|
||||
"ed25519-dalek",
|
||||
"futures-util",
|
||||
"rand 0.8.7",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"uuid",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -284,6 +408,12 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.11"
|
||||
@@ -393,6 +523,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -464,6 +595,15 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.11.0"
|
||||
@@ -499,6 +639,15 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@@ -612,6 +761,16 @@ version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c"
|
||||
dependencies = [
|
||||
"cpufeatures 0.3.1",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -710,6 +869,12 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.31.0"
|
||||
@@ -824,8 +989,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -835,8 +1000,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.1",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1113,6 +1289,16 @@ version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
|
||||
dependencies = [
|
||||
"crypto-common 0.2.2",
|
||||
"ctutils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.24.1"
|
||||
@@ -1217,6 +1403,17 @@ version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e8131a03190127fb2263afc72b322ecadae46b6ff8c6f399ff5d02f5559af6"
|
||||
dependencies = [
|
||||
"curve25519-dalek 5.0.0",
|
||||
"rand_core 0.10.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.56"
|
||||
|
||||
@@ -16,3 +16,7 @@ tower-http = { version = "0.7.0", features = ["fs", "cors"] }
|
||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||
uuid = { version = "1.24.1", features = ["v4"] }
|
||||
futures-util = "0.3.34"
|
||||
x25519-dalek = { version = "3.0.0", features = ["static_secrets"] }
|
||||
curve25519-dalek = "5.0.0"
|
||||
sha2 = "0.11.0"
|
||||
chacha20poly1305 = "0.11.0"
|
||||
|
||||
+121
-1
@@ -1,7 +1,18 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use axum::extract::ws::WebSocket;
|
||||
use chacha20poly1305::{
|
||||
ChaCha20Poly1305, Key, Nonce,
|
||||
aead::{Aead, KeyInit},
|
||||
};
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::{Digest, Sha512};
|
||||
use tokio::sync::Mutex;
|
||||
use x25519_dalek::{PublicKey as X25519Public, SharedSecret, StaticSecret as X25519Secret};
|
||||
|
||||
use crate::{server::Server, ws::EnclaveWebSocket};
|
||||
|
||||
pub async fn get() -> anyhow::Result<SigningKey> {
|
||||
let private_key_path = PathBuf::from("private.key");
|
||||
@@ -45,3 +56,112 @@ pub fn from_string_sig(signature: &str) -> anyhow::Result<Signature> {
|
||||
.map_err(|_| anyhow::anyhow!("Invalid signature"))?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn ed25519_signing_key_to_x25519(signing_key: &SigningKey) -> X25519Secret {
|
||||
let hash = Sha512::digest(signing_key.as_bytes());
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
scalar_bytes.copy_from_slice(&hash[..32]);
|
||||
X25519Secret::from(scalar_bytes) // clamping happens internally
|
||||
}
|
||||
|
||||
pub fn ed25519_verifying_key_to_x25519(verifying_key: &VerifyingKey) -> Option<X25519Public> {
|
||||
let compressed = CompressedEdwardsY(verifying_key.to_bytes());
|
||||
let edwards_point = compressed.decompress()?;
|
||||
Some(X25519Public::from(edwards_point.to_montgomery().to_bytes()))
|
||||
}
|
||||
|
||||
pub struct SessionCipher {
|
||||
cipher: ChaCha20Poly1305,
|
||||
send_counter: u64,
|
||||
recv_counter: u64,
|
||||
}
|
||||
|
||||
impl SessionCipher {
|
||||
pub fn new(shared_secret: &SharedSecret) -> anyhow::Result<Self> {
|
||||
// SharedSecret's raw bytes ARE suitable for direct use as a ChaCha20Poly1305 key
|
||||
// (both are 32 bytes), though in a hardened design you'd typically run this
|
||||
// through a KDF (e.g. HKDF) rather than using the raw ECDH output directly.
|
||||
let key = Key::try_from(shared_secret.as_bytes().as_slice())?;
|
||||
|
||||
Ok(Self {
|
||||
cipher: ChaCha20Poly1305::new(&key),
|
||||
send_counter: 0,
|
||||
recv_counter: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_send_nonce(&mut self) -> [u8; 12] {
|
||||
let mut nonce = [0u8; 12];
|
||||
nonce[..8].copy_from_slice(&self.send_counter.to_be_bytes());
|
||||
// top bit distinguishes "send" direction from "recv" direction,
|
||||
// so client-send and server-send counters never collide even if
|
||||
// both happened to reach the same numeric value
|
||||
nonce[11] |= 0b1000_0000;
|
||||
self.send_counter += 1;
|
||||
nonce
|
||||
}
|
||||
|
||||
pub fn next_recv_nonce(&mut self) -> [u8; 12] {
|
||||
let mut nonce = [0u8; 12];
|
||||
nonce[..8].copy_from_slice(&self.recv_counter.to_be_bytes());
|
||||
self.recv_counter += 1;
|
||||
nonce
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, plaintext: &[u8]) -> anyhow::Result<Vec<u8>> {
|
||||
let nonce_bytes = self.next_send_nonce();
|
||||
let nonce = Nonce::try_from(nonce_bytes)?;
|
||||
|
||||
let ciphertext = self
|
||||
.cipher
|
||||
.encrypt(&nonce, plaintext)
|
||||
.map_err(|_| anyhow::anyhow!("encryption failed"))?;
|
||||
|
||||
// prepend the nonce so the other side can reconstruct it on decrypt
|
||||
let mut out = nonce_bytes.to_vec();
|
||||
out.extend(ciphertext);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn decrypt(&mut self, data: &[u8]) -> anyhow::Result<Vec<u8>> {
|
||||
if data.len() < 12 {
|
||||
anyhow::bail!("message too short to contain a nonce");
|
||||
}
|
||||
let (nonce_bytes, ciphertext) = data.split_at(12);
|
||||
let nonce = Nonce::try_from(nonce_bytes)?;
|
||||
|
||||
self.cipher
|
||||
.decrypt(&nonce, ciphertext)
|
||||
.map_err(|_| anyhow::anyhow!("decryption failed"))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn crypto_handshake(
|
||||
server: &Arc<Server>,
|
||||
mut socket: WebSocket,
|
||||
) -> anyhow::Result<EnclaveWebSocket> {
|
||||
socket
|
||||
.send(axum::extract::ws::Message::Binary(
|
||||
server.x_keypair.0.to_bytes().to_vec().into(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
let axum::extract::ws::Message::Binary(raw_pubkey) = socket
|
||||
.recv()
|
||||
.await
|
||||
.transpose()?
|
||||
.ok_or(anyhow::anyhow!("Failed to get client x key"))?
|
||||
else {
|
||||
return Err(anyhow::anyhow!("WS message is not binary"));
|
||||
};
|
||||
|
||||
let client_pubkey = X25519Public::from(*raw_pubkey.as_array().ok_or(anyhow::anyhow!(
|
||||
"Failed to get proper length of client x key"
|
||||
))?);
|
||||
|
||||
let shared_secret = server.x_keypair.1.diffie_hellman(&client_pubkey);
|
||||
|
||||
let cipher = Arc::new(Mutex::new(SessionCipher::new(&shared_secret)?));
|
||||
|
||||
Ok(EnclaveWebSocket::new(socket, cipher))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::types::{Channel, ChannelKind, ServerMeta};
|
||||
pub struct Config {
|
||||
pub meta: ServerMeta,
|
||||
pub port: u16,
|
||||
pub public_hostname: String,
|
||||
pub hostnames: HashSet<String>,
|
||||
}
|
||||
|
||||
@@ -49,8 +48,6 @@ impl Config {
|
||||
|
||||
port: 3415,
|
||||
|
||||
public_hostname: "localhost:3415".to_string(),
|
||||
|
||||
hostnames: HashSet::from_iter(["localhost:3415".to_string()]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ use crate::server::UserConnections;
|
||||
impl UserConnections {
|
||||
pub async fn initialize(
|
||||
server: &Arc<Server>,
|
||||
socket: Arc<EnclaveWebSocket>,
|
||||
) -> anyhow::Result<(Arc<EnclaveWebSocket>, VerifyingKey, ClientMeta)> {
|
||||
socket: &EnclaveWebSocket,
|
||||
) -> anyhow::Result<(VerifyingKey, ClientMeta)> {
|
||||
let Some(ServerMethod::Initialize {
|
||||
public_key: public_key_string,
|
||||
signature,
|
||||
@@ -48,11 +48,10 @@ impl UserConnections {
|
||||
return Err(anyhow::anyhow!("Client tampstamp wasn't correct"));
|
||||
}
|
||||
|
||||
if hostname != server.config.public_hostname || !server.config.hostnames.contains(&hostname)
|
||||
{
|
||||
if !server.config.hostnames.contains(&hostname) {
|
||||
socket.send(
|
||||
&ClientMethod::Error {
|
||||
error: Cow::Owned(format!("Invalid Hostname, to avoid man-in-the-middle attacks, please use the correct hostname: {}", server.config.public_hostname)),
|
||||
error: Cow::Owned(format!("Invalid Hostname, to avoid man-in-the-middle attacks, please use the correct hostname(s): {}", server.config.hostnames.clone().into_iter().collect::<Vec<_>>().join(", "))),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
@@ -112,6 +111,6 @@ impl UserConnections {
|
||||
));
|
||||
};
|
||||
|
||||
Ok((socket, public_key, meta))
|
||||
Ok((public_key, meta))
|
||||
}
|
||||
}
|
||||
|
||||
+26
-6
@@ -18,11 +18,12 @@ use tokio::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
crypto::SessionCipher,
|
||||
data::{config::Config, messages::MessageStore, users::UserMetaStore},
|
||||
protocol::{ClientMethod, read_loop},
|
||||
types::ClientMeta,
|
||||
ws::EnclaveWebSocket,
|
||||
};
|
||||
use x25519_dalek::{PublicKey as X25519Public, StaticSecret as X25519Secret};
|
||||
|
||||
pub struct VoiceConnection {
|
||||
pub addr: SocketAddr,
|
||||
@@ -35,11 +36,13 @@ pub struct UserConnections {
|
||||
pub counter: AtomicU16,
|
||||
pub public_key: VerifyingKey,
|
||||
pub connections: Mutex<HashMap<u16, Arc<crate::ws::EnclaveWebSocket>>>,
|
||||
pub cihper: Arc<Mutex<SessionCipher>>,
|
||||
pub voice: Mutex<Option<VoiceConnection>>,
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
pub key: SigningKey,
|
||||
pub x_keypair: (X25519Public, X25519Secret),
|
||||
pub config: Config,
|
||||
pub clients: Mutex<HashMap<VerifyingKey, Arc<UserConnections>>>,
|
||||
pub voice_pins: Mutex<HashMap<u64, (VerifyingKey, String)>>,
|
||||
@@ -50,8 +53,14 @@ pub struct Server {
|
||||
|
||||
impl Server {
|
||||
pub async fn new() -> anyhow::Result<Arc<Self>> {
|
||||
let key = crate::crypto::get().await?;
|
||||
Ok(Arc::new(Self {
|
||||
key: crate::crypto::get().await?,
|
||||
x_keypair: (
|
||||
crate::crypto::ed25519_verifying_key_to_x25519(&key.verifying_key())
|
||||
.ok_or(anyhow::anyhow!("Failed to convert ed pubkey to x"))?,
|
||||
crate::crypto::ed25519_signing_key_to_x25519(&key),
|
||||
),
|
||||
key,
|
||||
config: Config::get().await?,
|
||||
clients: Mutex::new(HashMap::new()),
|
||||
voice_pins: Mutex::new(HashMap::new()),
|
||||
@@ -67,8 +76,16 @@ impl Server {
|
||||
let s = self.clone();
|
||||
|
||||
ws.on_upgrade(move |socket: WebSocket| async move {
|
||||
match UserConnections::initialize(&s, Arc::new(EnclaveWebSocket::new(socket))).await {
|
||||
Ok((client, public_key, meta)) => {
|
||||
let mut client = match crate::crypto::crypto_handshake(&s, socket).await {
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to initialize crypto: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match UserConnections::initialize(&s, &client).await {
|
||||
Ok((public_key, meta)) => {
|
||||
if let Err(e) = s
|
||||
.user_store
|
||||
.upsert_user(&crate::crypto::to_string(&public_key), &meta)
|
||||
@@ -88,10 +105,15 @@ impl Server {
|
||||
counter: AtomicU16::new(0),
|
||||
connections: Mutex::new(HashMap::new()),
|
||||
voice: Mutex::new(None),
|
||||
cihper: client.cipher.clone(),
|
||||
})
|
||||
})
|
||||
.clone();
|
||||
|
||||
client.cipher = clients.cihper.clone();
|
||||
|
||||
let client = Arc::new(client);
|
||||
|
||||
let conid = clients
|
||||
.counter
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
@@ -106,8 +128,6 @@ impl Server {
|
||||
|
||||
if let Err(e) = read_loop(&s, public_key, &client).await {
|
||||
eprintln!("Failed to handle client: {e}");
|
||||
} else {
|
||||
println!("Client connection closed")
|
||||
}
|
||||
|
||||
let mut clients_meta = s.clients.lock().await;
|
||||
|
||||
+46
-10
@@ -2,9 +2,9 @@ use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use ed25519_dalek::VerifyingKey;
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::{net::UdpSocket, sync::Mutex};
|
||||
|
||||
use crate::{protocol::ClientMethod, server::Server};
|
||||
use crate::{crypto::SessionCipher, protocol::ClientMethod, server::Server};
|
||||
|
||||
use tokio::time::Instant;
|
||||
|
||||
@@ -31,16 +31,18 @@ impl Server {
|
||||
let pin_bytes: [u8; 8] = buf[..8].try_into().unwrap();
|
||||
let pin = u64::from_be_bytes(pin_bytes);
|
||||
|
||||
let (sender_pubkey, channel_id, payload) = {
|
||||
// is_first_packet distinguishes the plaintext pin-bootstrap packet
|
||||
// from subsequent encrypted audio packets.
|
||||
let (sender_pubkey, channel_id, payload, is_first_packet) = {
|
||||
let mut pins = self.voice_pins.lock().await;
|
||||
|
||||
if let Some((pubkey, channel_id)) = pins.remove(&pin) {
|
||||
(pubkey, channel_id, &buf[8..len])
|
||||
(pubkey, channel_id, &buf[8..len], true)
|
||||
} else {
|
||||
drop(pins);
|
||||
|
||||
match self.find_voice_sender(&addr).await {
|
||||
Some((pubkey, channel_id)) => (pubkey, channel_id, &buf[..]),
|
||||
Some((pubkey, channel_id)) => (pubkey, channel_id, &buf[..len], false),
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
@@ -69,8 +71,31 @@ impl Server {
|
||||
|
||||
drop(voice);
|
||||
|
||||
self.relay_voice(&sender_pubkey, &channel_id, payload)
|
||||
.await?;
|
||||
// The pin-bearing bootstrap packet carries no payload to decrypt —
|
||||
// it's purely "here's my pin, bind my address." Everything after
|
||||
// this first packet is the real, encrypted audio stream.
|
||||
if is_first_packet {
|
||||
continue;
|
||||
}
|
||||
|
||||
let decrypted_payload = match user.cihper.lock().await.decrypt(payload) {
|
||||
Ok(pt) => pt,
|
||||
Err(e) => {
|
||||
eprintln!("[vc] dropping packet: decryption failed: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let s = self.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = s
|
||||
.relay_voice(&sender_pubkey, &channel_id, &decrypted_payload)
|
||||
.await
|
||||
{
|
||||
eprintln!("{e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +146,11 @@ impl Server {
|
||||
voice.last_speaking_sent = now;
|
||||
}
|
||||
|
||||
let _ = self.udp_send_to(&voice.addr, payload).await;
|
||||
if *sender == user.public_key {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = self.udp_send_to(&user.cihper, &voice.addr, payload).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -133,10 +162,17 @@ impl Server {
|
||||
.context("Failed to get voice socket")
|
||||
}
|
||||
|
||||
pub async fn udp_send_to(&self, addr: &SocketAddr, payload: &[u8]) -> anyhow::Result<()> {
|
||||
pub async fn udp_send_to(
|
||||
&self,
|
||||
cipher: &Arc<Mutex<SessionCipher>>,
|
||||
addr: &SocketAddr,
|
||||
payload: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let socket = self.get_voice_socket()?;
|
||||
|
||||
socket.send_to(payload, addr).await?;
|
||||
socket
|
||||
.send_to(&cipher.lock().await.encrypt(payload)?, addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use axum::extract::ws::{Message, Utf8Bytes, WebSocket};
|
||||
use axum::extract::ws::{Message, WebSocket};
|
||||
use futures_util::{
|
||||
SinkExt, StreamExt,
|
||||
stream::{SplitSink, SplitStream},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::protocol::{ClientMethod, ServerMethod};
|
||||
use crate::{
|
||||
crypto::SessionCipher,
|
||||
protocol::{ClientMethod, ServerMethod},
|
||||
};
|
||||
|
||||
pub struct EnclaveWebSocket {
|
||||
tx: Mutex<SplitSink<WebSocket, Message>>,
|
||||
rx: Mutex<SplitStream<WebSocket>>,
|
||||
pub cipher: Arc<Mutex<SessionCipher>>,
|
||||
}
|
||||
|
||||
impl EnclaveWebSocket {
|
||||
pub fn new(ws: WebSocket) -> Self {
|
||||
pub fn new(ws: WebSocket, cipher: Arc<Mutex<SessionCipher>>) -> Self {
|
||||
let (tx, rx) = ws.split();
|
||||
|
||||
Self {
|
||||
tx: Mutex::new(tx),
|
||||
rx: Mutex::new(rx),
|
||||
cipher,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +44,23 @@ impl EnclaveWebSocket {
|
||||
}
|
||||
},
|
||||
|
||||
Some(Message::Binary(encrypted)) => {
|
||||
let text = String::from_utf8(self.cipher.lock().await.decrypt(&encrypted)?)?;
|
||||
|
||||
match serde_json::from_str(&text.to_string()) {
|
||||
Ok(msg) => Ok(Some(msg)),
|
||||
|
||||
Err(e) => {
|
||||
self.send(&ClientMethod::Error {
|
||||
error: Cow::Owned(format!("Unable to parse message: {e}")),
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Message::Ping(v)) => {
|
||||
self.tx.lock().await.send(Message::Pong(v)).await?;
|
||||
|
||||
@@ -52,12 +74,14 @@ impl EnclaveWebSocket {
|
||||
}
|
||||
|
||||
pub async fn send(&self, message: &ClientMethod) -> anyhow::Result<()> {
|
||||
let text = serde_json::to_string(message)?;
|
||||
|
||||
let encrypted = self.cipher.lock().await.encrypt(text.as_bytes())?;
|
||||
|
||||
self.tx
|
||||
.lock()
|
||||
.await
|
||||
.send(Message::Text(Utf8Bytes::from(serde_json::to_string(
|
||||
message,
|
||||
)?)))
|
||||
.send(Message::Binary(encrypted.into()))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user