Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt to Using X-Wing Keys #74

Open
wants to merge 7 commits into
base: o5-transport
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions crates/lyrebird/src/fwd/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,16 +442,14 @@ where
let mut listeners = Vec::new();

let mut builder = Obfs4PT::server_builder();
let server = if params.is_some() {
builder.options(&params.unwrap())?.build()
} else {
builder.build()
};
if params.is_some() {
builder.options(&params.unwrap())?;
}

info!(
"({obfs4_name}) client params: \"{}\"",
builder.get_client_params()
);
let client_options = builder.get_client_params();
let server = builder.build();

info!("({obfs4_name}) client params: \"{}\"", client_options);

let listener = tokio::net::TcpListener::bind(listen_addrs).await?;
listeners.push(server_listen_loop::<TcpStream, _, _>(
Expand Down
6 changes: 3 additions & 3 deletions crates/lyrebird/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,10 @@ async fn server_setup(
}

let mut builder = Obfs4PT::server_builder();
let server = builder
builder
.statefile_location(statedir)?
.options(&bind_addr.options)?
.build();
.options(&bind_addr.options)?;
let server = builder.build();

let listener = tokio::net::TcpListener::bind(bind_addr.addr).await?;
listeners.push(server_listen_loop::<TcpStream, _>(
Expand Down
1 change: 1 addition & 0 deletions crates/o5/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ hkdf = "0.12.3"
crypto_secretbox = { version="0.1.1", features=["chacha20"]}
subtle = "2.5.0"
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom", "reusable_secrets"]}
x-wing = "0.0.1-alpha"

## Utils
pin-project = "1.1.3"
Expand Down
21 changes: 11 additions & 10 deletions crates/o5/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,30 @@ date elements without worrying about being backward compatible.
- the concept of "packets" is now called "messages"
- a frame can contain multiple messages
- update from xsalsa20poly1305 -> chacha20poly1305
- padding is given an explicit message type different than that of a payload and uses the mesage length header field
- padding is given an explicit message type different than that of a payload message and uses the mesage length header field
- (In obfs4 a frame that decodes to a payload packet type `\x00` with packet length 0 is asummed to all be padding)
- move payload to message type `\x01`
- padding takes message type `\x00`
- (Maybe) add bidirectional heartbeat messages

- Handshake
- x25519 key-exchange -> Kyber1024X25519 key-exchange
- x25519 key-exchange -> [X-Wing](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem#name-with-hpke-x25519kyber768dra) (ML-KEM + X25519) Hybrid Public Key Exchange
- the overhead padding of the current obfs4 handshake (resulting in paket length in [4096:8192]) is mostly unused
we exchange some of this unused padding for a kyber key to provide post-quantum security to the handshake.
- Are Kyber1024 keys uniform random? I assume not.
- NTor V3 handshake
- the obfs4 handshake uses (a custom version of) the ntor handshake to derive key materials
- (Maybe) change mark and MAC from sha256-128 to sha256
- handshake parameters encrypted under the key exchange public keys
- [Kemeleon Encoding](https://docs.rs/kemeleon/latest/kemeleon/) for obfuscating ML-KEM public keys and ciphertext on the wire.
- [Elligator2](https://docs.rs/curve25519-elligator2/latest/curve25519_elligator2/) for obfuscating X25519 public keys.
- [PQ-Obfs handshake](https://eprint.iacr.org/2024/1086.pdf)
- Adapted from the [NTor V3 handshake](https://spec.torproject.org/proposals/332-ntor-v3-with-extra-data.html)
- Change mark and MAC from sha256-128 to sha3-256
- Allow messages extra data to be sent with the handshake, encrypted under the key exchange public keys
- the client can provide initial parameters during the handshake, knowing that they are not forward secure.
- the server can provide messages with parameters / extensions in the handshake response (like prngseed)
- like the kyber key, this takes space out of the padding already used in the client handshake.
- This takes space out of the padding already used in the client handshake.
- (Maybe) session tickets and resumption
- (Maybe) handshake complete frame type

### Goals
* Post Quantum Forward Secrecy - traffic captured today cannot be decrypted by a quantum computer tomorrow.
* Stick closer to Codec / Framed implementation for all packets (hadshake included)
* use the tor/arti ntor v3 implementation

### Features to keep
- once a session is established, unrecognized frame types are ignored
Expand Down
31 changes: 17 additions & 14 deletions crates/o5/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unused)]

use crate::{
common::{colorize, mlkem1024_x25519, HmacSha256},
common::{colorize, xwing, HmacSha256},
constants::*,
framing::{FrameError, Marshall, O5Codec, TryParse, KEY_LENGTH, KEY_MATERIAL_LENGTH},
handshake::IdentityPublicKey,
Expand All @@ -26,17 +26,16 @@ use std::{

#[derive(Clone, Debug)]
pub struct ClientBuilder {
pub station_pubkey: [u8; PUBLIC_KEY_LEN],
pub station_id: [u8; NODE_ID_LENGTH],
pub node_details: IdentityPublicKey,
pub statefile_path: Option<String>,
pub(crate) handshake_timeout: MaybeTimeout,
}

impl Default for ClientBuilder {
fn default() -> Self {
Self {
station_pubkey: [0u8; PUBLIC_KEY_LEN],
station_id: [0_u8; NODE_ID_LENGTH],
node_details: IdentityPublicKey::new([0u8; PUBLIC_KEY_LEN], [0u8; NODE_ID_LENGTH])
.expect("default identitykey is broken - shouldn't be used anyways"),
statefile_path: None,
handshake_timeout: MaybeTimeout::Default_,
}
Expand All @@ -46,26 +45,31 @@ impl Default for ClientBuilder {
impl ClientBuilder {
/// TODO: implement client builder from statefile
pub fn from_statefile(location: &str) -> Result<Self> {
todo!("this is not implemented");
Ok(Self {
station_pubkey: [0_u8; PUBLIC_KEY_LEN],
station_id: [0_u8; NODE_ID_LENGTH],
node_details: IdentityPublicKey::new([0u8; PUBLIC_KEY_LEN], [0u8; NODE_ID_LENGTH])?,
statefile_path: Some(location.into()),
handshake_timeout: MaybeTimeout::Default_,
})
}

/// TODO: implement client builder from string args
pub fn from_params(param_strs: Vec<impl AsRef<[u8]>>) -> Result<Self> {
todo!("this is not implemented");
Ok(Self {
station_pubkey: [0_u8; PUBLIC_KEY_LEN],
station_id: [0_u8; NODE_ID_LENGTH],
node_details: IdentityPublicKey::new([0u8; PUBLIC_KEY_LEN], [0u8; NODE_ID_LENGTH])?,
statefile_path: None,
handshake_timeout: MaybeTimeout::Default_,
})
}

pub fn with_node_pubkey(&mut self, pubkey: [u8; PUBLIC_KEY_LEN]) -> &mut Self {
self.station_pubkey = pubkey;
pub fn with_node_pubkey(&mut self, pubkey: [u8; PUBLIC_KEY_LEN]) -> Result<&mut Self> {
self.node_details.ek = xwing::EncapsulationKey::try_from(&pubkey[..])?;
Ok(self)
}

pub(crate) fn with_node(&mut self, pubkey: IdentityPublicKey) -> &mut Self {
self.node_details = pubkey;
self
}

Expand All @@ -75,7 +79,7 @@ impl ClientBuilder {
}

pub fn with_node_id(&mut self, id: [u8; NODE_ID_LENGTH]) -> &mut Self {
self.station_id = id;
self.node_details.id = id.into();
self
}

Expand All @@ -96,8 +100,7 @@ impl ClientBuilder {

pub fn build(&self) -> Client {
Client {
station_pubkey: IdentityPublicKey::new(self.station_pubkey, self.station_id)
.expect("failed to build client - bad options."),
station_pubkey: self.node_details.clone(),
handshake_timeout: self.handshake_timeout.duration(),
}
}
Expand Down
Loading