From ed506c3f4a8cf6a8737f537d65e0d94bd5779422 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 19 Nov 2020 23:48:56 -0800 Subject: [PATCH] Initial Commit with Primitives Identity and Transcript --- .gitignore | 2 + .idea/.gitignore | 8 ++++ .idea/misc.xml | 6 +++ .idea/modules.xml | 8 ++++ .idea/tapir-rs.iml | 11 +++++ .idea/vcs.xml | 6 +++ Cargo.toml | 16 +++++++ README.md | 17 ++++++++ src/applications/mod.rs | 10 +++++ src/applications/transcript_app.rs | 43 +++++++++++++++++++ src/lib.rs | 14 +++++++ src/networks/mod.rs | 1 + src/networks/tor/mod.rs | 31 ++++++++++++++ src/primitives/identity.rs | 67 ++++++++++++++++++++++++++++++ src/primitives/mod.rs | 2 + src/primitives/transcript.rs | 63 ++++++++++++++++++++++++++++ 16 files changed, 305 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/tapir-rs.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/applications/mod.rs create mode 100644 src/applications/transcript_app.rs create mode 100644 src/lib.rs create mode 100644 src/networks/mod.rs create mode 100644 src/networks/tor/mod.rs create mode 100644 src/primitives/identity.rs create mode 100644 src/primitives/mod.rs create mode 100644 src/primitives/transcript.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..38ff096 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/tapir-rs.iml b/.idea/tapir-rs.iml new file mode 100644 index 0000000..c16b88f --- /dev/null +++ b/.idea/tapir-rs.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..70b53af --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tapir-rs" +version = "0.1.0" +authors = ["Sarah Jamie Lewis "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +torut = "0.1.6" +rand = "0.7.3" +curve25519-dalek = "3.0.0" +x25519-dalek = "1.1" +ed25519-dalek = "1.0.1" +merlin = "2.0.0" +hex = "0.4.2" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a26844 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Tapir-rs: Tiny Anonymous Peer (in Rust) + +A prototype for the inevitable transition of Cwtch and other Open Privacy projects from Golang to Rust! + +Very WIP: + +## Primitives + +* Identity - Done! +* Transcript - Done! + +## Applications +* TranscriptApp +* AuthenticationApp + +## Networks +* Base Onion Service \ No newline at end of file diff --git a/src/applications/mod.rs b/src/applications/mod.rs new file mode 100644 index 0000000..46adf55 --- /dev/null +++ b/src/applications/mod.rs @@ -0,0 +1,10 @@ +use crate::primitives::transcript::Transcript; + +pub mod transcript_app; + +pub trait Application { + fn new_instance(&self) -> Box; + fn init(&mut self); + fn transcript(&mut self) -> Option<&mut Transcript>; + fn propagate_transcript(&mut self, transcript: &Transcript); +} \ No newline at end of file diff --git a/src/applications/transcript_app.rs b/src/applications/transcript_app.rs new file mode 100644 index 0000000..7603d24 --- /dev/null +++ b/src/applications/transcript_app.rs @@ -0,0 +1,43 @@ +use crate::primitives::transcript::Transcript; +use crate::applications::Application; + +#[derive(Clone)] +pub struct TranscriptApp { + transcript: Option +} + +impl Application for TranscriptApp { + fn new_instance(&self) -> Box { + Box::new(TranscriptApp { + transcript: None + }) + } + + fn init(&mut self) { + self.transcript = Some(Transcript::new_transcript("primitives-transcript".as_ref())) + } + + fn transcript(&mut self) -> Option<&mut Transcript> { + return self.transcript.as_mut() + } + + fn propagate_transcript(&mut self, transcript: &Transcript) { + match self.transcript { + None => self.transcript = Some(transcript.clone()), + _ => panic!("this is a bug...we should find a nicer rust way of doing this") + } + } +} + +#[cfg(test)] +mod tests { + + + #[test] + fn test_transcript_app() { + //let mut transcript_app = TranscriptApp::NewInstance(); + // transcript_app.Init(); + // transcript_app.Transcript(); + //println!("Transcript App Initial Commit: {:x}", transcript_app.Transcript().challenge_bytes()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1c19e7f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +#![feature(array_methods)] + +pub mod applications; +pub mod networks; +pub mod primitives; + + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/src/networks/mod.rs b/src/networks/mod.rs new file mode 100644 index 0000000..e570ed3 --- /dev/null +++ b/src/networks/mod.rs @@ -0,0 +1 @@ +pub mod tor; \ No newline at end of file diff --git a/src/networks/tor/mod.rs b/src/networks/tor/mod.rs new file mode 100644 index 0000000..53aa3cf --- /dev/null +++ b/src/networks/tor/mod.rs @@ -0,0 +1,31 @@ +use std::net::TcpStream; +use std::io::Error; + +#[derive(Debug)] +pub struct TorProcess { + +} + +impl TorProcess { + pub fn connect() -> Result { + match TcpStream::connect(&format!("127.0.0.1:{}", 9051)) { + Ok(_conn) => { + Ok(TorProcess{}) + }, + Err(err) => { + return Err(err) + } + } + } +} + + +#[cfg(test)] +mod tests { + use crate::networks::tor::TorProcess; + + #[test] + fn connect_tor() { + println!("{:?}", TorProcess::connect()); + } +} diff --git a/src/primitives/identity.rs b/src/primitives/identity.rs new file mode 100644 index 0000000..d6657b0 --- /dev/null +++ b/src/primitives/identity.rs @@ -0,0 +1,67 @@ +use ed25519_dalek::{Keypair, PublicKey, ExpandedSecretKey}; +use x25519_dalek::{StaticSecret, SharedSecret}; +use x25519_dalek::PublicKey as X25519PublicKey ; +use rand::rngs::OsRng; +use std::intrinsics::transmute; + +/// Identity - An ed25519 keypair, required for established a Tor v3 onion service and used to +/// maintain a consistent cryptographic identity for a peer. +pub struct Identity { + keypair: Keypair +} + +impl Identity { + + /// Initialize a persistent identity + pub fn initialize(keypair: Keypair) ->Identity { + Identity { + keypair + } + } + + /// Initialize an ephemeral identity - used for both ephemeral diffie hellman key exchanges + /// in addition to anonymous primitives connections to various onion services. + pub fn initialize_ephemeral_identity() -> Identity { + let mut csprng = OsRng{}; + let keypair = ed25519_dalek::Keypair::generate(&mut csprng); + Identity { + keypair + } + } + + /// Perform a diffie hellman exchange between `self` and the `remote_pub_key` + /// Implementation: this method converts the secret key of `self` into a `StaticSecret` + /// and the `remote_pub_key` into a `x25519_dalek::PublicKey` such that they can be used + /// as part of the exchange. + /// This is used as part of the ephemeral diffie hellman 3-way exchange. + pub fn edh(&self, remote_pub_key: PublicKey) -> SharedSecret { + let esk = ExpandedSecretKey::from(&self.keypair.secret).to_bytes(); + let hsk : *const [u8;32] = unsafe{transmute(esk.as_ptr())}; + let private_x25519 = StaticSecret::from(unsafe{**&hsk}); + let public_x25519: X25519PublicKey = Identity::to_x25519(remote_pub_key); + return private_x25519.diffie_hellman(&public_x25519); + } + + /// helper function that converts an Edwards PublicKey into a Montgomery PublicKey + /// i.e. ed25519 to x25519 + fn to_x25519(edwards_pub_key: PublicKey) -> X25519PublicKey { + let edwards = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(edwards_pub_key.to_bytes().as_slice()); + X25519PublicKey::from(edwards.decompress().unwrap().to_montgomery().to_bytes()) + } + +} + +#[cfg(test)] +mod tests { + use crate::primitives::identity::Identity; + + #[test] + fn test_identity() { + let alice = Identity::initialize_ephemeral_identity(); + let bob = Identity::initialize_ephemeral_identity(); + + assert_eq!(alice.edh(bob.keypair.public).to_bytes(), bob.edh(alice.keypair.public).to_bytes()); + println!("Alice Shared Secret: {:?}", alice.edh(bob.keypair.public).to_bytes()); + println!("Bob Shared Secret: {:?}", bob.edh(alice.keypair.public).to_bytes()); + } +} diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs new file mode 100644 index 0000000..f1fac7a --- /dev/null +++ b/src/primitives/mod.rs @@ -0,0 +1,2 @@ +pub mod identity; +pub mod transcript; \ No newline at end of file diff --git a/src/primitives/transcript.rs b/src/primitives/transcript.rs new file mode 100644 index 0000000..a2f24f1 --- /dev/null +++ b/src/primitives/transcript.rs @@ -0,0 +1,63 @@ +/// Transcript is a wrapper around Merlin Transcripts to provide some structure for +/// more complex protocol-driven applications as seen in Tapir +#[derive(Clone)] +pub struct Transcript { + mtranscript: merlin::Transcript, + transcript: String +} + +impl Transcript { + pub fn new_transcript(label: &'static str) -> Transcript{ + Transcript { + mtranscript: merlin::Transcript::new(label.as_ref()), + transcript: String::new() + } + } + + pub fn add_to_transcript(&mut self, label: &'static str, b :&[u8]) { + let op = format!("{} ({}) {};", label, b.len(), hex::encode(b)); + self.transcript = format!("{}\n{}", self.transcript, op); + self.mtranscript.append_message(label.as_ref(), b); + } + + pub fn output_transcript_to_audit(&self) -> String { + self.transcript.clone() + } + + pub fn new_protocol(&mut self, label: &str) { + let op = format!("---- new-protocol: {} ----", label); + self.transcript = format!("{}\n{}", self.transcript, op); + self.mtranscript.append_message("protocol".as_ref(), label.as_ref()); + } + + pub fn commit_to_transcript(&mut self, label: &'static str) -> [u8;64] { + let mut result = [0u8;64]; + self.mtranscript.challenge_bytes(label.as_ref(),result.as_mut_slice()); + self.transcript = format!("{}\nextract {}: {}", self.transcript, label, hex::encode(result)); + result + } +} + +#[cfg(test)] +mod tests { + use crate::primitives::transcript::Transcript; + + #[test] + fn test_transcript() { + let mut t = Transcript::new_transcript("label"); + t.add_to_transcript("action","test data".as_bytes()); + if t.output_transcript_to_audit() != t.output_transcript_to_audit() { + panic!("Multiple Audit Calls should not impact underlying Transcript") + } + + println!("{}",t.output_transcript_to_audit()); + println!("{:?}", t.commit_to_transcript("first commit")); + println!("{}",t.output_transcript_to_audit()); + println!("{:?}", t.commit_to_transcript("second commit")); + t.add_to_transcript("action","test data".as_bytes()); + + // Test vector from golang Tapir tests... + let test_vector :[u8;64] = [239,5,86,166,139,70,247,187,137,37,36,183,53,51,114,115,24,103,110,65,82,190,88,106,4,114,91,194,90,114,107,19,3,33,103,187,122,54,156,213,203,40,132,26,203,130,117,55,161,245,30,102,127,151,233,148,116,212,235,186,46,59,1,8]; + assert_eq!(test_vector, t.commit_to_transcript("third commit")) + } +} \ No newline at end of file