mirror of
https://github.com/AtHeartEngineer/progcrypto_workshop.git
synced 2026-01-09 18:07:55 -05:00
init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
3287
Cargo.lock
generated
Normal file
3287
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
Cargo.toml
Normal file
45
Cargo.toml
Normal 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
3
src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
5
src/notary/notary.key
Normal file
5
src/notary/notary.key
Normal 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
4
src/notary/notary.pub
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr
|
||||
cRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg==
|
||||
-----END PUBLIC KEY-----
|
||||
6088
src/proof.json
Normal file
6088
src/proof.json
Normal file
File diff suppressed because it is too large
Load Diff
57
src/simple_notary.rs
Normal file
57
src/simple_notary.rs
Normal 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
182
src/simple_prover.rs
Normal 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
73
src/simple_verifier.rs
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user