This commit is contained in:
Hendrik Eeckhaut
2023-10-20 17:15:17 +02:00
commit 5eb52b824f
10 changed files with 9745 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

3287
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

45
Cargo.toml Normal file
View File

@@ -0,0 +1,45 @@
[package]
name = "progcrypto_workshop"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# Simple Notary server
tlsn-core = { git = "https://github.com/tlsnotary/tlsn", branch = "dev" }
tlsn-verifier = { git = "https://github.com/tlsnotary/tlsn", branch = "dev" }
p256 = { version = "0.13", features = ["ecdsa"] }
tokio = { version = "1.33.0", features = [
"rt",
"rt-multi-thread",
"macros",
"net",
"io-std",
"fs",
] }
tokio-util = { version = "0.7.9", features = ["compat"] }
tracing-subscriber = "0.3.17"
# Prover
tlsn-prover = { git = "https://github.com/tlsnotary/tlsn", branch = "dev" }
hyper = { version = "0.14", features = ["client", "http1"] }
serde_json = "1.0.107"
futures = "0.3.28"
# Verifier
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
chrono = "0.4.31"
[[example]]
name = "simple_prover"
path = "src/simple_prover.rs"
[[example]]
name = "simple_verifier"
path = "src/simple_verifier.rs"
[[example]]
name = "simple_notary"
path = "src/simple_notary.rs"

3
src/main.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

5
src/notary/notary.key Normal file
View File

@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEvBc/VMWn3E4PGfe
ETc/ekdTRmRwNN9J6eKDPxJ98ZmhRANCAAQG/foUjhkWzMlrQNAUnfBYJe9UsWtx
HMwbmRpN4cahLMO7pwWrHe4RZikUajoLQQ5SB/6YSBuS0utehy/nIfMq
-----END PRIVATE KEY-----

4
src/notary/notary.pub Normal file
View File

@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr
cRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg==
-----END PUBLIC KEY-----

6088
src/proof.json Normal file

File diff suppressed because it is too large Load Diff

57
src/simple_notary.rs Normal file
View File

@@ -0,0 +1,57 @@
/// This is a simple implementation of the notary server with minimal functionalities (without TLS, does not support WebSocket and configuration etc.)
/// For a more functional notary server implementation, please use the notary server in `../../notary-server`
use p256::pkcs8::DecodePrivateKey;
use std::env;
use tokio::net::TcpListener;
use tokio_util::compat::TokioAsyncReadCompatExt;
use tlsn_verifier::tls::{Verifier, VerifierConfig};
const NOTARY_SIGNING_KEY_PATH: &str = "./notary/notary.key";
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
// Allow passing an address to listen on as the first argument of this
// program, but otherwise we'll just set up our TCP listener on
// 127.0.0.1:8080 for connections.
let addr = env::args()
.nth(1)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
// Next up we create a TCP listener which will listen for incoming
// connections. This TCP listener is bound to the address we determined
// above and must be associated with an event loop.
let listener = TcpListener::bind(&addr).await.unwrap();
println!("Listening on: {}", addr);
// Load the notary signing key
let signing_key =
p256::ecdsa::SigningKey::read_pkcs8_pem_file(NOTARY_SIGNING_KEY_PATH).unwrap();
loop {
// Asynchronously wait for an inbound socket.
let (socket, socket_addr) = listener.accept().await.unwrap();
println!("Accepted connection from: {}", socket_addr);
{
let signing_key = signing_key.clone();
// Spawn notarization task to be run concurrently
tokio::spawn(async move {
// Setup default config. Normally a different ID would be generated
// for each notarization.
let config = VerifierConfig::builder().id("example").build().unwrap();
Verifier::new(config)
.notarize::<_, p256::ecdsa::Signature>(socket.compat(), &signing_key)
.await
.unwrap();
});
}
}
}

182
src/simple_prover.rs Normal file
View File

@@ -0,0 +1,182 @@
/// Runs a simple Prover which connects to the Notary and notarizes a request/response from
/// example.com. The Prover then generates a proof and writes it to disk.
///
/// The example uses the notary server implemented in ./simple_notary.rs
use futures::AsyncWriteExt;
use hyper::{Body, Request, StatusCode};
use std::ops::Range;
use tlsn_core::proof::TlsProof;
use tokio::io::AsyncWriteExt as _;
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
use tlsn_prover::tls::{Prover, ProverConfig};
// Setting of the application server
const SERVER_DOMAIN: &str = "example.com";
const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36";
// Setting of the notary server — make sure these are the same with those in ./simple_notary.rs
const NOTARY_HOST: &str = "127.0.0.1";
const NOTARY_PORT: u16 = 8080;
#[tokio::main]
async fn main() {
// Initialize logging
tracing_subscriber::fmt::init();
// A Prover configuration
let config = ProverConfig::builder()
.id("example")
.server_dns(SERVER_DOMAIN)
.build()
.unwrap();
// Connect to the Notary
let notary_socket = tokio::net::TcpStream::connect((NOTARY_HOST, NOTARY_PORT))
.await
.unwrap();
println!("Connected to the Notary");
// Create a Prover and set it up with the Notary
// This will set up the MPC backend prior to connecting to the server.
let prover = Prover::new(config)
.setup(notary_socket.compat())
.await
.unwrap();
// Connect to the Server via TCP. This is the TLS client socket.
let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443))
.await
.unwrap();
// Bind the Prover to the server connection.
// The returned `mpc_tls_connection` is an MPC TLS connection to the Server: all data written
// to/read from it will be encrypted/decrypted using MPC with the Notary.
let (mpc_tls_connection, prover_fut) = prover.connect(client_socket.compat()).await.unwrap();
// Spawn the Prover task to be run concurrently
let prover_task = tokio::spawn(prover_fut);
// Attach the hyper HTTP client to the MPC TLS connection
let (mut request_sender, connection) =
hyper::client::conn::handshake(mpc_tls_connection.compat())
.await
.unwrap();
// Spawn the HTTP task to be run concurrently
let connection_task = tokio::spawn(connection.without_shutdown());
// Build a simple HTTP request with common headers
let request = Request::builder()
.uri("/")
.header("Host", SERVER_DOMAIN)
.header("Accept", "*/*")
// Using "identity" instructs the Server not to use compression for its HTTP response.
// TLSNotary tooling does not support compression.
.header("Accept-Encoding", "identity")
.header("Connection", "close")
.header("User-Agent", USER_AGENT)
.body(Body::empty())
.unwrap();
println!("Starting an MPC TLS connection with the server");
// Send the request to the Server and get a response via the MPC TLS connection
let response = request_sender.send_request(request).await.unwrap();
println!("Got a response from the server");
assert!(response.status() == StatusCode::OK);
// Close the connection to the server
let mut client_socket = connection_task.await.unwrap().unwrap().io.into_inner();
client_socket.close().await.unwrap();
// The Prover task should be done now, so we can grab the Prover.
let prover = prover_task.await.unwrap().unwrap();
// Prepare for notarization.
let mut prover = prover.start_notarize();
// Identify the ranges in the outbound data which contain data which we want to disclose
let (public_ranges, _) = find_ranges(
prover.sent_transcript().data(),
&[
// Redact the value of the "User-Agent" header. It will NOT be disclosed.
USER_AGENT.as_bytes(),
],
);
let recv_len = prover.recv_transcript().data().len();
let builder = prover.commitment_builder();
// Commit to each range of the public outbound data which we want to disclose
let sent_commitments: Vec<_> = public_ranges
.iter()
.map(|r| builder.commit_sent(r.clone()).unwrap())
.collect();
// Commit to all inbound data in one shot, as we don't need to redact anything in it
let recv_commitment = builder.commit_recv(0..recv_len).unwrap();
// Finalize, returning the notarized session
let notarized_session = prover.finalize().await.unwrap();
// Create a proof for all committed data in this session
let mut proof_builder = notarized_session.data().build_substrings_proof();
// Reveal all the public ranges
for commitment_id in sent_commitments {
proof_builder.reveal(commitment_id).unwrap();
}
proof_builder.reveal(recv_commitment).unwrap();
let substrings_proof = proof_builder.build().unwrap();
let proof = TlsProof {
session: notarized_session.session_proof(),
substrings: substrings_proof,
};
// Write the proof to a file
let mut file = tokio::fs::File::create("proof.json").await.unwrap();
file.write_all(serde_json::to_string_pretty(&proof).unwrap().as_bytes())
.await
.unwrap();
println!("Notarization completed successfully!");
println!("The proof has been written to proof.json");
}
/// Find the ranges of the public and private parts of a sequence.
///
/// Returns a tuple of `(public, private)` ranges.
fn find_ranges(seq: &[u8], private_seq: &[&[u8]]) -> (Vec<Range<usize>>, Vec<Range<usize>>) {
let mut private_ranges = Vec::new();
for s in private_seq {
for (idx, w) in seq.windows(s.len()).enumerate() {
if w == *s {
private_ranges.push(idx..(idx + w.len()));
}
}
}
let mut sorted_ranges = private_ranges.clone();
sorted_ranges.sort_by_key(|r| r.start);
let mut public_ranges = Vec::new();
let mut last_end = 0;
for r in sorted_ranges {
if r.start > last_end {
public_ranges.push(last_end..r.start);
}
last_end = r.end;
}
if last_end < seq.len() {
public_ranges.push(last_end..seq.len());
}
(public_ranges, private_ranges)
}

73
src/simple_verifier.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::time::Duration;
use elliptic_curve::pkcs8::DecodePublicKey;
use tlsn_core::proof::{SessionProof, TlsProof};
/// A simple verifier which reads a proof generated by `simple_prover.rs` from "proof.json", verifies
/// it and prints the verified data to the console.
fn main() {
// Deserialize the proof
let proof = std::fs::read_to_string("proof.json").unwrap();
let proof: TlsProof = serde_json::from_str(proof.as_str()).unwrap();
let TlsProof {
// The session proof establishes the identity of the server and the commitments
// to the TLS transcript.
session,
// The substrings proof proves select portions of the transcript, while redacting
// anything the Prover chose not to disclose.
substrings,
} = proof;
// Verify the session proof against the Notary's public key
//
// This verifies the identity of the server using a default certificate verifier which trusts
// the root certificates from the `webpki-roots` crate.
session
.verify_with_default_cert_verifier(notary_pubkey())
.unwrap();
let SessionProof {
// The session header that was signed by the Notary is a succinct commitment to the TLS transcript.
header,
// This is the server name, checked against the certificate chain shared in the TLS handshake.
server_name,
..
} = session;
// The time at which the session was recorded
let time = chrono::DateTime::UNIX_EPOCH + Duration::from_secs(header.time());
// Verify the substrings proof against the session header.
//
// This returns the redacted transcripts
let (mut sent, mut recv) = substrings.verify(&header).unwrap();
// Replace the bytes which the Prover chose not to disclose with 'X'
sent.set_redacted(b'X');
recv.set_redacted(b'X');
println!("-------------------------------------------------------------------");
println!(
"Successfully verified that the bytes below came from a session with {:?} at {}.",
server_name, time
);
println!("Note that the bytes which the Prover chose not to disclose are shown as X.");
println!();
println!("Bytes sent:");
println!();
print!("{}", String::from_utf8(sent.data().to_vec()).unwrap());
println!();
println!("Bytes received:");
println!();
println!("{}", String::from_utf8(recv.data().to_vec()).unwrap());
println!("-------------------------------------------------------------------");
}
/// Returns a Notary pubkey trusted by this Verifier
fn notary_pubkey() -> p256::PublicKey {
let pem_file_path = "./notary/notary.pub";
p256::PublicKey::read_public_key_pem_file(pem_file_path).unwrap()
}