mirror of
https://github.com/tlsnotary/proof_viz.git
synced 2026-01-08 22:48:21 -05:00
Refactored viewFile into a function component
This commit is contained in:
@@ -1 +1,2 @@
|
||||
pub mod redactedBytesComponent;
|
||||
pub mod viewFile;
|
||||
|
||||
146
src/components/viewFile.rs
Normal file
146
src/components/viewFile.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
extern crate base64;
|
||||
use elliptic_curve::pkcs8::DecodePublicKey;
|
||||
use std::str;
|
||||
use web_time::Duration;
|
||||
|
||||
use yew::{function_component, html, Html, Properties};
|
||||
|
||||
use tlsn_core::proof::{SessionProof, TlsProof};
|
||||
|
||||
use crate::components::redactedBytesComponent::Direction;
|
||||
use crate::components::redactedBytesComponent::RedactedBytesComponent;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
pub name: String,
|
||||
pub file_type: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn ViewFile(props: &Props) -> Html {
|
||||
fn notary_pubkey() -> p256::PublicKey {
|
||||
// from https://github.com/tlsnotary/notary-server/tree/main/src/fixture/notary/notary.key
|
||||
// converted with `openssl ec -in notary.key -pubout -outform PEM`
|
||||
|
||||
let pem = "-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr
|
||||
cRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg==
|
||||
-----END PUBLIC KEY-----";
|
||||
|
||||
p256::PublicKey::from_public_key_pem(pem).unwrap()
|
||||
}
|
||||
|
||||
// Verify the session proof against the Notary's public key
|
||||
fn verify_proof(session: &SessionProof) -> Result<(), String> {
|
||||
// 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())
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
fn parse_tls_proof(json_str: &str) -> Html {
|
||||
let tls_proof: Result<TlsProof, serde_json::Error> = serde_json::from_str(json_str);
|
||||
|
||||
// info!("Parsing");
|
||||
|
||||
match tls_proof {
|
||||
Ok(tls_proof) => {
|
||||
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,
|
||||
} = tls_proof;
|
||||
|
||||
let proof_verification = verify_proof(&session);
|
||||
|
||||
if proof_verification.is_err() {
|
||||
return html! {
|
||||
<>
|
||||
<div role="alert">
|
||||
<div class="bg-red-500 text-white font-bold rounded-t px-4 py-2">
|
||||
{"Invalid Proof"}
|
||||
</div>
|
||||
<div class="border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700">
|
||||
{ "❌ " }{proof_verification.unwrap_err().to_string()}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
};
|
||||
}
|
||||
|
||||
let proof_verification_feedback = "✅ Proof successfully verified ✅".to_string();
|
||||
|
||||
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 '\0'
|
||||
sent.set_redacted(b'\0');
|
||||
recv.set_redacted(b'\0');
|
||||
|
||||
let bytes_send = String::from_utf8(sent.data().to_vec()).unwrap();
|
||||
|
||||
let bytes_received = String::from_utf8(recv.data().to_vec()).unwrap();
|
||||
|
||||
html! {
|
||||
<div class="p-4 flex flex-col justify-center items-center w-full">
|
||||
<div class="p-4 w-5/6">
|
||||
<b>{"Server domain:" }</b>
|
||||
<div class="bg-black text-white p-4 rounded-md">
|
||||
<pre>{server_name.as_str().to_string()}</pre>
|
||||
</div>
|
||||
<b>{"Notarization time:" }</b>
|
||||
<div class="bg-black text-white p-4 rounded-md">
|
||||
<pre>{time.to_string()}</pre>
|
||||
</div>
|
||||
<b>{"Proof:" }</b>
|
||||
<div class="bg-black text-white p-4 rounded-md">
|
||||
<pre>{proof_verification_feedback}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RedactedBytesComponent direction={Direction::Send} bytes={bytes_send} />
|
||||
|
||||
<RedactedBytesComponent direction={Direction::Received} bytes={bytes_received} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Err(e) => html! {
|
||||
<div>{format!("Parsing failed {}", e.to_string())}</div>
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let json_str = str::from_utf8(&props.data).unwrap();
|
||||
html! {
|
||||
<div class="p-4 flex flex-col justify-center items-center bg-zinc-700 border border-white border-dashed rounded-2xl">
|
||||
<p class="text-center">{ format!("{}", &props.name) }</p>
|
||||
|
||||
<div class="flex-1 flex flex-col justify-center p-4">
|
||||
<div class="container mx-auto px-4">
|
||||
if props.file_type.contains("application/json") {
|
||||
{parse_tls_proof(json_str)}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
243
src/main.rs
243
src/main.rs
@@ -1,22 +1,16 @@
|
||||
extern crate base64;
|
||||
use elliptic_curve::pkcs8::DecodePublicKey;
|
||||
use gloo::file::callbacks::FileReader;
|
||||
use gloo::file::File;
|
||||
use std::collections::HashMap;
|
||||
use std::str;
|
||||
use web_time::Duration;
|
||||
|
||||
use web_sys::{DragEvent, Event, FileList, HtmlInputElement};
|
||||
use yew::html::TargetCast;
|
||||
use yew::{html, Callback, Component, Context, Html};
|
||||
|
||||
use tlsn_core::proof::{SessionProof, TlsProof};
|
||||
use tlsn_core::NotarizedSession;
|
||||
use yew::{html, Callback, Component, Context, Html, Properties};
|
||||
|
||||
mod components;
|
||||
use crate::components::redactedBytesComponent::Direction;
|
||||
use crate::components::redactedBytesComponent::RedactedBytesComponent;
|
||||
use crate::components::viewFile::ViewFile;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct FileDetails {
|
||||
name: String,
|
||||
file_type: String,
|
||||
@@ -87,6 +81,27 @@ impl Component for App {
|
||||
("Source", "https://github.com/tlsnotary/proof_viz"),
|
||||
// ("PSE", "https://pse.dev"),
|
||||
];
|
||||
|
||||
fn upload_files(files: Option<FileList>) -> Msg {
|
||||
if let Some(files) = files {
|
||||
let files = js_sys::try_iter(&files)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.map(|v| web_sys::File::from(v.unwrap()))
|
||||
.map(File::from)
|
||||
.collect();
|
||||
Msg::Files(files)
|
||||
} else {
|
||||
Msg::Files(Vec::with_capacity(0))
|
||||
}
|
||||
}
|
||||
|
||||
let upload_icon = html! {
|
||||
<svg class="w-16 h-16 text-white-50" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
||||
</svg>
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class="flex flex-col h-screen">
|
||||
<nav class="bg-zinc-700 h-16 px-8 py-2">
|
||||
@@ -107,24 +122,18 @@ impl Component for App {
|
||||
<div class="w-4/5 m-auto">
|
||||
// <p class="text-2xl text-center">{ "Upload Your TLSNotary Proof" }</p>
|
||||
|
||||
<label for="file-upload" class="cursor-pointer">
|
||||
<label for="file-upload">
|
||||
<div class="p-16 flex flex-col justify-center items-center bg-zinc-700 border border-white border-dashed rounded-2xl"
|
||||
id="drop-container"
|
||||
ondrop={ctx.link().callback(|event: DragEvent| {
|
||||
event.prevent_default();
|
||||
let files = event.data_transfer().unwrap().files();
|
||||
Self::upload_files(files)
|
||||
})}
|
||||
ondragover={Callback::from(|event: DragEvent| {
|
||||
event.prevent_default();
|
||||
})}
|
||||
ondragenter={Callback::from(|event: DragEvent| {
|
||||
event.prevent_default();
|
||||
upload_files(files)
|
||||
})}
|
||||
ondragover={Callback::from(|event: DragEvent| event.prevent_default())}
|
||||
ondragenter={Callback::from(|event: DragEvent| event.prevent_default())}
|
||||
>
|
||||
<svg class="w-16 h-16 text-white-50" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
||||
</svg>
|
||||
{upload_icon}
|
||||
<p class="text-base text-white-50"><span class="font-semibold">{"Drop your \""}<span class="font-mono">{"proof.json"}</span>{"\" file here"}</span>{" or click to select"}</p>
|
||||
<br/>
|
||||
<p class="text-sm text-gray-400 text-center">{"🕵️ Your proof is "}<strong>{"checked locally in the browser"}</strong>{" 🕵️"}<br />{"Your file will not be uploaded"}</p>
|
||||
@@ -138,12 +147,14 @@ impl Component for App {
|
||||
multiple={true}
|
||||
onchange={ctx.link().callback(move |e: Event| {
|
||||
let input: HtmlInputElement = e.target_unchecked_into();
|
||||
Self::upload_files(input.files())
|
||||
upload_files(input.files())
|
||||
})}
|
||||
/>
|
||||
|
||||
<div>
|
||||
{ for self.files.iter().rev().map(Self::view_file) }
|
||||
{for self.files.iter().rev().map(|file| html! {
|
||||
<ViewFile name={file.name.clone()} file_type={file.file_type.clone()} data={file.data.clone()} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,194 +162,6 @@ impl Component for App {
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn view_file(file: &FileDetails) -> Html {
|
||||
fn _parse_notarized_session(json_str: &str) -> Html {
|
||||
let notarized_session: Result<NotarizedSession, serde_json::Error> =
|
||||
serde_json::from_str(json_str);
|
||||
|
||||
match notarized_session {
|
||||
Ok(notarized_session) => {
|
||||
let header = notarized_session.header();
|
||||
let time = chrono::DateTime::UNIX_EPOCH
|
||||
+ Duration::from_secs(header.handshake_summary().time());
|
||||
|
||||
html! {
|
||||
<>
|
||||
<li>
|
||||
<b>{"domain: " }</b>{notarized_session.data().server_name().as_str().to_string()}
|
||||
</li>
|
||||
<li>
|
||||
<b>{"Notarization time: " }</b>{time}
|
||||
</li>
|
||||
</>
|
||||
}
|
||||
}
|
||||
Err(e) => html! {
|
||||
<div>{format!("Parsing failed {}", e.to_string())}</div>
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn notary_pubkey() -> p256::PublicKey {
|
||||
// from https://github.com/tlsnotary/notary-server/tree/main/src/fixture/notary/notary.key
|
||||
// converted with `openssl ec -in notary.key -pubout -outform PEM`
|
||||
|
||||
let pem = "-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr
|
||||
cRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg==
|
||||
-----END PUBLIC KEY-----";
|
||||
|
||||
p256::PublicKey::from_public_key_pem(pem).unwrap()
|
||||
}
|
||||
|
||||
// Verify the session proof against the Notary's public key
|
||||
fn verify_proof(session: &SessionProof) -> Result<(), String> {
|
||||
// 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())
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
fn parse_tls_proof(json_str: &str) -> Html {
|
||||
let tls_proof: Result<TlsProof, serde_json::Error> = serde_json::from_str(json_str);
|
||||
|
||||
// info!("Parsing");
|
||||
|
||||
match tls_proof {
|
||||
Ok(tls_proof) => {
|
||||
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,
|
||||
} = tls_proof;
|
||||
|
||||
let proof_verification = verify_proof(&session);
|
||||
|
||||
if proof_verification.is_err() {
|
||||
return html! {
|
||||
<>
|
||||
<div role="alert">
|
||||
<div class="bg-red-500 text-white font-bold rounded-t px-4 py-2">
|
||||
{"Invalid Proof"}
|
||||
</div>
|
||||
<div class="border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700">
|
||||
{ "❌ " }{proof_verification.unwrap_err().to_string()}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
};
|
||||
}
|
||||
|
||||
let proof_verification_feedback =
|
||||
"✅ Proof successfully verified ✅".to_string();
|
||||
|
||||
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 '\0'
|
||||
sent.set_redacted(b'\0');
|
||||
recv.set_redacted(b'\0');
|
||||
|
||||
let bytes_send = String::from_utf8(sent.data().to_vec()).unwrap();
|
||||
|
||||
let bytes_received = String::from_utf8(recv.data().to_vec()).unwrap();
|
||||
|
||||
html! {
|
||||
<div class="p-4 flex flex-col justify-center items-center w-full">
|
||||
<div class="p-4 w-5/6">
|
||||
<b>{"Server domain:" }</b>
|
||||
<div class="bg-black text-white p-4 rounded-md">
|
||||
<pre>{server_name.as_str().to_string()}</pre>
|
||||
</div>
|
||||
<b>{"Notarization time:" }</b>
|
||||
<div class="bg-black text-white p-4 rounded-md">
|
||||
<pre>{time.to_string()}</pre>
|
||||
</div>
|
||||
<b>{"Proof:" }</b>
|
||||
<div class="bg-black text-white p-4 rounded-md">
|
||||
<pre>{proof_verification_feedback}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RedactedBytesComponent direction={Direction::Send} bytes={bytes_send} />
|
||||
|
||||
<RedactedBytesComponent direction={Direction::Received} bytes={bytes_received} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Err(e) => html! {
|
||||
<div>{format!("Parsing failed {}", e.to_string())}</div>
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let json_str = str::from_utf8(&file.data).unwrap();
|
||||
html! {
|
||||
<div class="p-4 flex flex-col justify-center items-center bg-zinc-700 border border-white border-dashed rounded-2xl">
|
||||
<p class="text-center">{ format!("{}", file.name) }</p>
|
||||
|
||||
<div class="flex-1 flex flex-col justify-center p-4">
|
||||
<div class="container mx-auto px-4">
|
||||
if file.file_type.contains("application/json") {
|
||||
<div>
|
||||
<ul>
|
||||
{parse_tls_proof(json_str)}
|
||||
</ul>
|
||||
</div>
|
||||
// <div>
|
||||
// {"Notarized session:"}
|
||||
// <ul>
|
||||
// {parse_notarized_session(json_str)}
|
||||
// </ul>
|
||||
// </div>
|
||||
|
||||
// <div>
|
||||
// {"Raw json:"}
|
||||
// <div class="bg-black text-white p-4 rounded-md">
|
||||
// <pre id="logContent" class="whitespace-pre-wrap font-mono">{json_str}</pre>
|
||||
// </div>
|
||||
// </div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_files(files: Option<FileList>) -> Msg {
|
||||
if let Some(files) = files {
|
||||
let files = js_sys::try_iter(&files)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.map(|v| web_sys::File::from(v.unwrap()))
|
||||
.map(File::from)
|
||||
.collect();
|
||||
Msg::Files(files)
|
||||
} else {
|
||||
Msg::Files(Vec::with_capacity(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::default());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user