Skip to content

Commit

Permalink
interim commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jmwample committed Jun 11, 2024
1 parent 4c295ec commit 7627be5
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 69 deletions.
18 changes: 14 additions & 4 deletions crates/obfs4/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ debug = ["ptrs/debug"]
name = "obfs4"
crate-type = ["cdylib", "rlib"]

[[bench]]
name = "nacl"
harness = false

[dependencies]
## Local
ptrs = { path="../ptrs", version="0.1.0" }
Expand All @@ -34,9 +38,10 @@ siphasher = "1.0.0"
sha2 = "0.10.8"
hmac = { version="0.12.1", features=["reset"]}
hkdf = "0.12.3"
crypto_secretbox = { version="0.1.1", features=["salsa20"]}
crypto_secretbox = { version="0.1.1", features=["salsa20", "heapless"]}
subtle = "2.5.0"
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom", "reusable_secrets", "elligator2"], git = "https://github.com/jmwample/curve25519-dalek.git", branch = "elligator2-ntor"}
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom", "reusable_secrets"]}
# x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom", "reusable_secrets", "elligator2"], git = "https://github.com/jmwample/curve25519-dalek.git", branch = "interim-crate"}

## Utils
hex = "0.4.3"
Expand Down Expand Up @@ -64,7 +69,9 @@ thiserror = "1.0.56"

## transitive dependencies that break things when versions are too low
## i.e. any lower than the exact versions here.
curve25519-dalek = { version="4.1.2", optional=true}
curve25519-dalek = { version="4.1", optional=true}
curve25519-elligator2 = { version="4.1.2", features=["elligator2"], path = "../../../../../dalek-crypto/curve25519-dalek/curve25519-elligator2"} # , git = "https://github.com/jmwample/curve25519-dalek.git", branch = "interim-crate"

anyhow = { version="1.0.20", optional=true}
async-trait = { version="0.1.9", optional=true}
num-bigint = { version="0.4.2", optional=true}
Expand All @@ -80,7 +87,10 @@ tracing-subscriber = "0.3.18"
hex-literal = "0.4.1"
tor-basic-utils = "0.18.0"

# o5 pqc test
# benches
criterion = "0.5"

# # o5 pqc test
# pqc_kyber = {version="0.7.1", features=["kyber1024", "std"]}
# ml-kem = "0.1.0"

194 changes: 139 additions & 55 deletions crates/obfs4/src/common/curve25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,132 @@
//! key-agreement trait, but for now we are just re-using the APIs from
//! [`x25519_dalek`].

// TODO: We may want eventually want to expose ReusableSecret instead of
// StaticSecret, for use in places where we need to use a single secret
// twice in one handshake, but we do not need that secret to be persistent.
//
// The trouble here is that if we use ReusableSecret in these cases, we
// cannot easily construct it for testing purposes. We could in theory
// kludge something together using a fake Rng, but that might be more
// trouble than we want to go looking for.
#[allow(unused)]
pub use x25519_dalek::{
EphemeralSecret, PublicKey, PublicRepresentative, ReusableSecret, SharedSecret, StaticSecret,
};
pub use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
pub use curve25519_elligator2::elligator2::representative_from_privkey;

pub(crate) struct EphemeralSecret (x25519_dalek::EphemeralSecret, u8);

impl EphemeralSecret {
pub(crate) fn random() -> Self {}

pub(crate) fn random_from_rng<T: RngCore + CryptoRng>(csprng: T) -> Self {}

pub(crate) fn diffie_hellman() -> Self {}

pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret {}
}

impl From<EphemeralSecret> for PublicKey {
fn from(value: EphemeralSecret) -> Self {

}
}

/// [`PublicKey`] transformation to a format indistinguishable from uniform
/// random. Requires feature `elligator2`.
///
/// This allows public keys to be sent over an insecure channel without
/// revealing that an x25519 public key is being shared.
///
/// # Example
#[cfg_attr(feature = "elligator2", doc = "```")]
#[cfg_attr(not(feature = "elligator2"), doc = "```ignore")]
/// use rand_core::OsRng;
/// use rand_core::RngCore;
///
/// use x25519_dalek::x25519;
/// use x25519_ephemeralSecret;
/// use x25519_dalek::{PublicKey, PublicRepresentative};
///
/// // ~50% of points are not encodable as elligator representatives, but we
/// // want to ensure we select a keypair that is.
/// fn get_representable_ephemeral() -> EphemeralSecret {
/// for i in 0_u8..255 {
/// let secret = EphemeralSecret::random_from_rng(&mut OsRng);
/// match Option::<PublicRepresentative>::from(&secret) {
/// Some(_) => return secret,
/// None => continue,
/// }
/// }
/// panic!("we should definitely have found a key by now")
/// }
///
/// // Generate Alice's key pair.
/// let alice_secret = get_representable_ephemeral();
/// let alice_representative = Option::<PublicRepresentative>::from(&alice_secret).unwrap();
///
/// // Generate Bob's key pair.
/// let bob_secret = get_representable_ephemeral();
/// let bob_representative = Option::<PublicRepresentative>::from(&bob_secret).unwrap();
///
/// // Alice and Bob should now exchange their representatives and reveal the
/// // public key from the other person.
/// let bob_public = PublicKey::from(&bob_representative);
///
/// let alice_public = PublicKey::from(&alice_representative);
///
/// // Once they've done so, they may generate a shared secret.
/// let alice_shared = alice_secret.diffie_hellman(&bob_public);
/// let bob_shared = bob_secret.diffie_hellman(&alice_public);
///
/// assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
/// ```
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct PublicRepresentative([u8; 32]);

impl PublicRepresentative {
/// View this public representative as a byte array.
#[inline]
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}

/// Extract this representative's bytes for serialization.
#[inline]
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
}

impl AsRef<[u8]> for PublicRepresentative {
/// View this shared secret key as a byte array.
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}

impl From<[u8; 32]> for PublicRepresentative {
/// Build a Elligator2 Public key Representative from bytes
fn from(r: [u8; 32]) -> PublicRepresentative {
PublicRepresentative(r)
}
}

impl<'a> From<&'a [u8; 32]> for PublicRepresentative {
/// Build a Elligator2 Public key Representative from bytes by reference
fn from(r: &'a [u8; 32]) -> PublicRepresentative {
PublicRepresentative(*r)
}
}

impl<'a> From<&'a EphemeralSecret> for Option<PublicRepresentative> {
/// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicRepresentative`].
fn from(secret: &'a EphemeralSecret) -> Option<PublicRepresentative> {
let repres = representative_from_privkey(&secret.0, secret.1);
let res: Option<[u8; 32]> = repres;
Some(PublicRepresentative(res?))
}
}

impl<'a> From<&'a PublicRepresentative> for PublicKey {
/// Given an elligator2 [`PublicRepresentative`], compute its corresponding [`PublicKey`].
fn from(representative: &'a PublicRepresentative) -> PublicKey {
let point = curve25519_elligator2::MontgomeryPoint::map_to_point(&representative.0);
PublicKey::from(*point.as_bytes())
}
}

use rand_core::{CryptoRng, RngCore};

Expand All @@ -28,18 +142,22 @@ pub const REPRESENTATIVE_LENGTH: usize = 32;
/// The probablility that a key does not have a representable elligator2 encoding
/// is ~50%, so we are (statistiscally) guaranteed to find a representable key
/// in relatively few iterations.
pub struct Representable;
pub struct Keys;

trait RetryLimit {
const RETRY_LIMIT: usize = 128;
}

const RETRY_LIMIT: usize = 128;
impl RetryLimit for Keys {}

#[allow(unused)]
impl Representable {
impl Keys {
/// Generate a new Elligator2 representable ['StaticSecret'] with the supplied RNG.
pub fn static_from_rng<R: RngCore + CryptoRng>(mut rng: R) -> StaticSecret {
let mut private = StaticSecret::random_from_rng(&mut rng);
let mut repres: Option<PublicRepresentative> = (&private).into();

for _ in 0..RETRY_LIMIT {
for _ in 0..Self::RETRY_LIMIT {
if repres.is_some() {
return private;
}
Expand All @@ -50,28 +168,12 @@ impl Representable {
panic!("failed to generate representable secret, bad RNG provided");
}

/// Generate a new Elligator2 representable ['ReusableSecret'] with the supplied RNG.
pub fn reusable_from_rng<R: RngCore + CryptoRng>(mut rng: R) -> ReusableSecret {
let mut private = ReusableSecret::random_from_rng(&mut rng);
let mut repres: Option<PublicRepresentative> = (&private).into();

for _ in 0..RETRY_LIMIT {
if repres.is_some() {
return private;
}
private = ReusableSecret::random_from_rng(&mut rng);
repres = (&private).into();
}

panic!("failed to generate representable secret, bad RNG provided");
}

/// Generate a new Elligator2 representable ['EphemeralSecret'] with the supplied RNG.
pub fn ephemeral_from_rng<R: RngCore + CryptoRng>(mut rng: R) -> EphemeralSecret {
let mut private = EphemeralSecret::random_from_rng(&mut rng);
let mut repres: Option<PublicRepresentative> = (&private).into();

for _ in 0..RETRY_LIMIT {
for _ in 0..Self::RETRY_LIMIT {
if repres.is_some() {
return private;
}
Expand All @@ -87,7 +189,7 @@ impl Representable {
let mut private = StaticSecret::random();
let mut repres: Option<PublicRepresentative> = (&private).into();

for _ in 0..RETRY_LIMIT {
for _ in 0..Self::RETRY_LIMIT {
if repres.is_some() {
return private;
}
Expand All @@ -98,28 +200,12 @@ impl Representable {
panic!("failed to generate representable secret, getrandom failed");
}

/// Generate a new Elligator2 representable ['ReusableSecret'].
pub fn random_reusable() -> ReusableSecret {
let mut private = ReusableSecret::random();
let mut repres: Option<PublicRepresentative> = (&private).into();

for _ in 0..RETRY_LIMIT {
if repres.is_some() {
return private;
}
private = ReusableSecret::random();
repres = (&private).into();
}

panic!("failed to generate representable secret, getrandom failed");
}

/// Generate a new Elligator2 representable ['EphemeralSecret'].
pub fn random_ephemeral() -> EphemeralSecret {
let mut private = EphemeralSecret::random();
let mut repres: Option<PublicRepresentative> = (&private).into();

for _ in 0..RETRY_LIMIT {
for _ in 0..Self::RETRY_LIMIT {
if repres.is_some() {
return private;
}
Expand All @@ -131,24 +217,22 @@ impl Representable {
}
}



#[cfg(test)]
mod test {
use super::*;
use hex::FromHex;

#[test]
fn representative_match() {
let mut repres = <[u8; 32]>::from_hex(
let repres = <[u8; 32]>::from_hex(
"8781b04fefa49473ca5943ab23a14689dad56f8118d5869ad378c079fd2f4079",
)
.unwrap();
let incorrect = "1af2d7ac95b5dd1ab2b5926c9019fa86f211e77dd796f178f3fe66137b0d5d15";
let expected = "a946c3dd16d99b8c38972584ca599da53e32e8b13c1e9a408ff22fdb985c2d79";

// we are not clearing the high order bits before translating the representative to a
// public key.
repres[31] &= 0x3f;

let r = PublicRepresentative::from(repres);
let p = PublicKey::from(&r);
assert_ne!(incorrect, hex::encode(p.as_bytes()));
Expand Down
2 changes: 1 addition & 1 deletion crates/obfs4/src/framing/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl ClientHandshakeMessage {
pub fn marshall(&mut self, buf: &mut impl BufMut, mut h: HmacSha256) -> Result<()> {
trace!("serializing client handshake");

h.reset(); // disambiguate reset() implementations Mac v digest
h.reset();
h.update(self.repres.as_bytes().as_ref());
let mark: &[u8] = &h.finalize_reset().into_bytes()[..MARK_LENGTH];

Expand Down
10 changes: 4 additions & 6 deletions crates/obfs4/src/handshake/handshake_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,12 @@ fn try_parse(
let mut h = HmacSha256::new_from_slice(&key[..]).unwrap();
h.reset(); // disambiguate reset() implementations Mac v digest

let mut r_bytes: [u8; 32] = buf[0..REPRESENTATIVE_LENGTH].try_into().unwrap();
let r_bytes: [u8; 32] = buf[0..REPRESENTATIVE_LENGTH].try_into().unwrap();
h.update(&r_bytes);

// clear the inconsistent elligator2 bits of the representative after
// using the wire format for deriving the mark
r_bytes[31] &= 0x3f;
// The elligator library internally clears the high-order bits of the
// representative to force a LSR value, but we use the wire format for
// deriving the mark (i.e. without cleared bits).
let server_repres = PublicRepresentative::from(r_bytes);
let server_auth: [u8; AUTHCODE_LENGTH] =
buf[REPRESENTATIVE_LENGTH..REPRESENTATIVE_LENGTH + AUTHCODE_LENGTH].try_into()?;
Expand Down Expand Up @@ -223,8 +223,6 @@ fn try_parse(
hex::encode(mac_received)
);
if mac_calculated.ct_eq(mac_received).into() {
let mut r_bytes = server_repres.to_bytes();
r_bytes[31] &= 0x3f;
return Ok((
ServerHandshakeMessage::new(server_repres, server_auth, state.epoch_hr.clone()),
pos + MARK_LENGTH + MAC_LENGTH,
Expand Down
7 changes: 4 additions & 3 deletions crates/obfs4/src/handshake/handshake_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,14 @@ impl Server {
Err(Error::HandshakeErr(RelayHandshakeError::EAgain))?;
}

let mut r_bytes: [u8; 32] = buf[0..REPRESENTATIVE_LENGTH].try_into().unwrap();
let r_bytes: [u8; 32] = buf[0..REPRESENTATIVE_LENGTH].try_into().unwrap();

// derive the mark based on the literal bytes on the wire
h.update(&r_bytes[..]);

// clear the bits that are unreliable (and randomized) for elligator2
r_bytes[31] &= 0x3f;
// The elligator library internally clears the high-order bits of the
// representative to force a LSR value, but we use the wire format for
// deriving the mark (i.e. without cleared bits).
let repres = PublicRepresentative::from(&r_bytes);

let m = h.finalize_reset().into_bytes();
Expand Down

0 comments on commit 7627be5

Please sign in to comment.