Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.

Commit 3998beb

Browse files
committed
add health care example
1 parent 3554db8 commit 3998beb

3 files changed

Lines changed: 307 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
USER_AGENT=
2+
AUTHORIZATION=
3+
PAGE_ID="IHKE3301S01"
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// This example shows how to notarize Discord DMs.
2+
//
3+
// The example uses the notary server implemented in ../../../notary/server
4+
5+
use http_body_util::{BodyExt, Empty};
6+
use hyper::{body::Bytes, Request, StatusCode};
7+
use hyper_util::rt::TokioIo;
8+
use notary_client::{Accepted, NotarizationRequest, NotaryClient};
9+
use std::{env, ops::Range, str};
10+
use tlsn_core::proof::TlsProof;
11+
use tlsn_prover::tls::{Prover, ProverConfig};
12+
use tokio::io::AsyncWriteExt as _;
13+
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
14+
use tracing::debug;
15+
16+
// Setting of the application server
17+
const SERVER_DOMAIN: &str = "myhealthbank.nhi.gov.tw";
18+
const PAGE_ID: &str = "IHKE3301S01";
19+
20+
// Setting of the notary server — make sure these are the same with the config in ../../../notary/server
21+
const NOTARY_HOST: &str = "127.0.0.1";
22+
const NOTARY_PORT: u16 = 7047;
23+
24+
// P/S: If the following limits are increased, please ensure max-transcript-size of
25+
// the notary server's config (../../../notary/server) is increased too, where
26+
// max-transcript-size = MAX_SENT_DATA + MAX_RECV_DATA
27+
//
28+
// Maximum number of bytes that can be sent from prover to server
29+
const MAX_SENT_DATA: usize = 1 << 12;
30+
// Maximum number of bytes that can be received by prover from server
31+
const MAX_RECV_DATA: usize = 1 << 14;
32+
33+
#[tokio::main]
34+
async fn main() {
35+
tracing_subscriber::fmt::init();
36+
37+
// Load secret variables frome environment for discord server connection
38+
dotenv::dotenv().ok();
39+
let auth_token = env::var("AUTHORIZATION").unwrap();
40+
let user_agent = env::var("USER_AGENT").unwrap();
41+
42+
// Build a client to connect to the notary server.
43+
let notary_client = NotaryClient::builder()
44+
.host(NOTARY_HOST)
45+
.port(NOTARY_PORT)
46+
// WARNING: Always use TLS to connect to notary server, except if notary is running locally
47+
// e.g. this example, hence `enable_tls` is set to False (else it always defaults to True).
48+
.enable_tls(false)
49+
.build()
50+
.unwrap();
51+
52+
// Send requests for configuration and notarization to the notary server.
53+
let notarization_request = NotarizationRequest::builder()
54+
.max_sent_data(MAX_SENT_DATA)
55+
.max_recv_data(MAX_RECV_DATA)
56+
.build()
57+
.unwrap();
58+
59+
let Accepted {
60+
io: notary_connection,
61+
id: session_id,
62+
..
63+
} = notary_client
64+
.request_notarization(notarization_request)
65+
.await
66+
.unwrap();
67+
68+
// Configure a new prover with the unique session id returned from notary client.
69+
let prover_config = ProverConfig::builder()
70+
.id(session_id)
71+
.server_dns(SERVER_DOMAIN)
72+
.max_sent_data(MAX_SENT_DATA)
73+
.max_recv_data(MAX_RECV_DATA)
74+
.build()
75+
.unwrap();
76+
77+
// Create a new prover and set up the MPC backend.
78+
let prover = Prover::new(prover_config)
79+
.setup(notary_connection.compat())
80+
.await
81+
.unwrap();
82+
83+
// Open a new socket to the application server.
84+
let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443))
85+
.await
86+
.unwrap();
87+
88+
// Bind the Prover to server connection
89+
let (tls_connection, prover_fut) = prover.connect(client_socket.compat()).await.unwrap();
90+
91+
// Spawn the Prover to be run concurrently
92+
let prover_task = tokio::spawn(prover_fut);
93+
94+
// Attach the hyper HTTP client to the TLS connection
95+
let (mut request_sender, connection) =
96+
hyper::client::conn::http1::handshake(TokioIo::new(tls_connection.compat()))
97+
.await
98+
.unwrap();
99+
100+
// Spawn the HTTP task to be run concurrently
101+
tokio::spawn(connection);
102+
103+
// Build the HTTP request to fetch the DMs
104+
let request = Request::builder()
105+
.uri(format!("/api/ihke3000/{PAGE_ID}/page_load"))
106+
.header("Host", SERVER_DOMAIN)
107+
.header("Accept", "*/*")
108+
.header("Accept-Language", "en-US,en;q=0.5")
109+
.header("Accept-Encoding", "identity")
110+
.header("User-Agent", user_agent)
111+
.header("Authorization", &auth_token)
112+
.header("Connection", "close")
113+
.body(Empty::<Bytes>::new())
114+
.unwrap();
115+
116+
debug!("Sending request");
117+
118+
let response = request_sender.send_request(request).await.unwrap();
119+
120+
debug!("Sent request");
121+
122+
assert!(response.status() == StatusCode::OK, "{}", response.status());
123+
124+
debug!("Request OK");
125+
126+
// Pretty printing :)
127+
let payload = response.into_body().collect().await.unwrap().to_bytes();
128+
let parsed =
129+
serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload)).unwrap();
130+
debug!("{}", serde_json::to_string_pretty(&parsed).unwrap());
131+
132+
// The Prover task should be done now, so we can grab it.
133+
let prover = prover_task.await.unwrap().unwrap();
134+
135+
// Prepare for notarization
136+
let mut prover = prover.start_notarize();
137+
138+
// Identify the ranges in the transcript that contain secrets
139+
let (public_ranges, private_ranges) =
140+
find_ranges(prover.sent_transcript().data(), &[auth_token.as_bytes()]);
141+
142+
let recv_len = prover.recv_transcript().data().len();
143+
144+
let builder = prover.commitment_builder();
145+
146+
// Collect commitment ids for the outbound transcript
147+
let mut commitment_ids = public_ranges
148+
.iter()
149+
.chain(private_ranges.iter())
150+
.map(|range| builder.commit_sent(range).unwrap())
151+
.collect::<Vec<_>>();
152+
153+
// Commit to the full received transcript in one shot, as we don't need to redact anything
154+
commitment_ids.push(builder.commit_recv(&(0..recv_len)).unwrap());
155+
156+
// Finalize, returning the notarized session
157+
let notarized_session = prover.finalize().await.unwrap();
158+
159+
debug!("Notarization complete!");
160+
161+
// Dump the notarized session to a file
162+
let mut file = tokio::fs::File::create("health_care_notarized_session.json")
163+
.await
164+
.unwrap();
165+
file.write_all(
166+
serde_json::to_string_pretty(&notarized_session)
167+
.unwrap()
168+
.as_bytes(),
169+
)
170+
.await
171+
.unwrap();
172+
173+
let session_proof = notarized_session.session_proof();
174+
175+
let mut proof_builder = notarized_session.data().build_substrings_proof();
176+
177+
// Reveal everything but the auth token (which was assigned commitment id 2)
178+
proof_builder.reveal_by_id(commitment_ids[0]).unwrap();
179+
proof_builder.reveal_by_id(commitment_ids[1]).unwrap();
180+
proof_builder.reveal_by_id(commitment_ids[3]).unwrap();
181+
182+
let substrings_proof = proof_builder.build().unwrap();
183+
184+
let proof = TlsProof {
185+
session: session_proof,
186+
substrings: substrings_proof,
187+
};
188+
189+
// Dump the proof to a file.
190+
let mut file = tokio::fs::File::create("health_care_proof.json")
191+
.await
192+
.unwrap();
193+
file.write_all(serde_json::to_string_pretty(&proof).unwrap().as_bytes())
194+
.await
195+
.unwrap();
196+
}
197+
198+
/// Find the ranges of the public and private parts of a sequence.
199+
///
200+
/// Returns a tuple of `(public, private)` ranges.
201+
fn find_ranges(seq: &[u8], sub_seq: &[&[u8]]) -> (Vec<Range<usize>>, Vec<Range<usize>>) {
202+
let mut private_ranges = Vec::new();
203+
for s in sub_seq {
204+
for (idx, w) in seq.windows(s.len()).enumerate() {
205+
if w == *s {
206+
private_ranges.push(idx..(idx + w.len()));
207+
}
208+
}
209+
}
210+
211+
let mut sorted_ranges = private_ranges.clone();
212+
sorted_ranges.sort_by_key(|r| r.start);
213+
214+
let mut public_ranges = Vec::new();
215+
let mut last_end = 0;
216+
for r in sorted_ranges {
217+
if r.start > last_end {
218+
public_ranges.push(last_end..r.start);
219+
}
220+
last_end = r.end;
221+
}
222+
223+
if last_end < seq.len() {
224+
public_ranges.push(last_end..seq.len());
225+
}
226+
227+
(public_ranges, private_ranges)
228+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use std::{str, time::Duration};
2+
3+
use elliptic_curve::pkcs8::DecodePublicKey;
4+
5+
use tlsn_core::proof::{SessionProof, TlsProof};
6+
7+
/// A simple verifier which reads a proof generated by `health_care.rs` from "health_care_proof.json", verifies
8+
/// it and prints the verified data to the console.
9+
fn main() {
10+
// Deserialize the proof
11+
let proof = std::fs::read_to_string("health_care_proof.json").unwrap();
12+
let proof: TlsProof = serde_json::from_str(proof.as_str()).unwrap();
13+
14+
let TlsProof {
15+
// The session proof establishes the identity of the server and the commitments
16+
// to the TLS transcript.
17+
session,
18+
// The substrings proof proves select portions of the transcript, while redacting
19+
// anything the Prover chose not to disclose.
20+
substrings,
21+
} = proof;
22+
23+
// Verify the session proof against the Notary's public key
24+
//
25+
// This verifies the identity of the server using a default certificate verifier which trusts
26+
// the root certificates from the `webpki-roots` crate.
27+
session
28+
.verify_with_default_cert_verifier(notary_pubkey())
29+
.unwrap();
30+
31+
let SessionProof {
32+
// The session header that was signed by the Notary is a succinct commitment to the TLS transcript.
33+
header,
34+
// This is the session_info, which contains the server_name, that is checked against the
35+
// certificate chain shared in the TLS handshake.
36+
session_info,
37+
..
38+
} = session;
39+
40+
// The time at which the session was recorded
41+
let time = chrono::DateTime::UNIX_EPOCH + Duration::from_secs(header.time());
42+
43+
// Verify the substrings proof against the session header.
44+
//
45+
// This returns the redacted transcripts
46+
let (mut sent, mut recv) = substrings.verify(&header).unwrap();
47+
48+
// Replace the bytes which the Prover chose not to disclose with 'X'
49+
sent.set_redacted(b'X');
50+
recv.set_redacted(b'X');
51+
52+
println!("-------------------------------------------------------------------");
53+
println!(
54+
"Successfully verified that the bytes below came from a session with {:?} at {}.",
55+
session_info.server_name, time
56+
);
57+
println!("Note that the bytes which the Prover chose not to disclose are shown as X.");
58+
println!();
59+
println!("Bytes sent:");
60+
println!();
61+
print!("{}", String::from_utf8(sent.data().to_vec()).unwrap());
62+
println!();
63+
println!("Bytes received:");
64+
println!();
65+
println!("{}", String::from_utf8(recv.data().to_vec()).unwrap());
66+
println!("-------------------------------------------------------------------");
67+
}
68+
69+
/// Returns a Notary pubkey trusted by this Verifier
70+
fn notary_pubkey() -> p256::PublicKey {
71+
let pem_file = str::from_utf8(include_bytes!(
72+
"../../../notary/server/fixture/notary/notary.pub"
73+
))
74+
.unwrap();
75+
p256::PublicKey::from_public_key_pem(pem_file).unwrap()
76+
}

0 commit comments

Comments
 (0)