mirror of
https://github.com/tlsnotary/tlsn-js.git
synced 2026-01-10 21:18:08 -05:00
Compare commits
12 Commits
config_log
...
fix/panic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faba8c6b3c | ||
|
|
b0e99cd3b1 | ||
|
|
84398d81dd | ||
|
|
c83d97e41b | ||
|
|
e51877e6cf | ||
|
|
1868aa3ea9 | ||
|
|
4cf350b040 | ||
|
|
292b4263d7 | ||
|
|
58c7132d6c | ||
|
|
353b3981ca | ||
|
|
3159dc9b3b | ||
|
|
2d7016ede2 |
@@ -1,6 +1,6 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { prove, verify } from 'tlsn-js';
|
||||
import { prove, verify, set_logging_filter } from 'tlsn-js';
|
||||
import { Proof } from 'tlsn-js/build/types';
|
||||
import { Watch } from 'react-loader-spinner';
|
||||
|
||||
@@ -21,6 +21,7 @@ function App(): ReactElement {
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
setProcessing(true);
|
||||
await set_logging_filter('info,tlsn_extension_rs=debug');
|
||||
const p = await prove('https://swapi.dev/api/people/1', {
|
||||
method: 'GET',
|
||||
maxTranscriptSize: 16384,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"tlsn-js": "0.1.0-alpha.5.2"
|
||||
"tlsn-js": "../../"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.26",
|
||||
@@ -27,4 +27,4 @@
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tlsn-js",
|
||||
"version": "v0.1.0-alpha.5.2",
|
||||
"version": "v0.1.0-alpha.6.0",
|
||||
"description": "",
|
||||
"repository": "https://github.com/tlsnotary/tlsn-js",
|
||||
"main": "build/index.js",
|
||||
|
||||
@@ -8,14 +8,22 @@ There is a simple react/typescript demo app in `./demo/react-ts-webpack`. The di
|
||||
|
||||
Since a web browser doesn't have the ability to make TCP connection, we need to use a websocket proxy server.
|
||||
|
||||
To run your own websockify proxy **locally**, run:
|
||||
To run your own websocket proxy for `https://swapi.dev` **locally**:
|
||||
|
||||
1. Install [websocat](https://github.com/vi/websocat):
|
||||
|
||||
| tool | command |
|
||||
| ------ | ------------------------------ |
|
||||
| cargo | `cargo install websocat` |
|
||||
| brew | `brew install websocat` |
|
||||
| source | https://github.com/vi/websocat |
|
||||
|
||||
2. Run a websocket proxy for `https://swapi.dev`:
|
||||
```sh
|
||||
git clone https://github.com/novnc/websockify && cd websockify
|
||||
./docker/build.sh
|
||||
docker run -it --rm -p 55688:80 novnc/websockify 80 swapi.dev:443
|
||||
websocat --binary -v ws-l:0.0.0.0:55688 tcp:swapi.dev:443
|
||||
```
|
||||
|
||||
Note the `swapi.dev:443` argument on the last line, this is the server we will use in this quick start.
|
||||
Note the `tcp:swapi.dev:443` argument on the last line, this is the server we will use in this quick start.
|
||||
|
||||
### Run a Local Notary Server <a name="local-notary"></a>
|
||||
|
||||
@@ -23,7 +31,7 @@ For this demo, we also need to run a local notary server.
|
||||
|
||||
1. Clone the TLSNotary repository:
|
||||
```shell
|
||||
git clone https://github.com/tlsnotary/tlsn.git --branch "v0.1.0-alpha.5"
|
||||
git clone https://github.com/tlsnotary/tlsn.git --branch "v0.1.0-alpha.6"
|
||||
```
|
||||
2. Edit the notary server config file (`notary-server/config/config.yaml`) to turn off TLS so that the browser extension can connect to the local notary server without requiring extra steps to accept self-signed certificates in the browser.
|
||||
```yaml
|
||||
|
||||
36
readme.md
36
readme.md
@@ -1,11 +1,25 @@
|
||||
![MIT licensed][mit-badge]
|
||||
![Apache licensed][apache-badge]
|
||||
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[apache-badge]: https://img.shields.io/github/license/saltstack/salt
|
||||
|
||||
# tlsn-js
|
||||
|
||||
NPM Modules for proving and verifying using TLSNotary in the browser.
|
||||
|
||||
The prover requires a [notary-server](https://github.com/tlsnotary/notary-server) and websockify proxy
|
||||
The prover requires a [notary-server](https://github.com/tlsnotary/notary-server) and a websocket proxy
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `tlsn-js` is developped for the usage of TLSNotary **in the Browser**. This module does not work in `nodejs`.
|
||||
> `tlsn-js` is developed for the usage of TLSNotary **in the Browser**. This module does not work in `nodejs`.
|
||||
|
||||
## License
|
||||
This repository is licensed under either of
|
||||
|
||||
- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- [MIT license](http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
## Example
|
||||
```ts
|
||||
@@ -30,11 +44,19 @@ const result = await verify(proof);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
## Running a local websockify proxy for `https://swapi.dev`
|
||||
```
|
||||
git clone https://github.com/novnc/websockify && cd websockify
|
||||
./docker/build.sh
|
||||
docker run -it --rm -p 55688:80 novnc/websockify 80 swapi.dev:443
|
||||
## Running a local websocket proxy for `https://swapi.dev`
|
||||
|
||||
1. Install [websocat](https://github.com/vi/websocat):
|
||||
|
||||
| tool | command |
|
||||
|--------|-------------------------------|
|
||||
| cargo | `cargo install websocat` |
|
||||
| brew | `brew install websocat` |
|
||||
| source | https://github.com/vi/websocat|
|
||||
|
||||
2. Run a websocket proxy for `https://swapi.dev`:
|
||||
```sh
|
||||
websocat --binary -v ws-l:0.0.0.0:55688 tcp:swapi.dev:443
|
||||
```
|
||||
|
||||
## Install as NPM Package
|
||||
|
||||
21
src/index.ts
21
src/index.ts
@@ -1,15 +1,30 @@
|
||||
import TLSN from './tlsn';
|
||||
import { DEFAULT_LOGGING_FILTER } from './tlsn';
|
||||
import { Proof } from './types';
|
||||
|
||||
let _tlsn: TLSN;
|
||||
const current_logging_filter = DEFAULT_LOGGING_FILTER;
|
||||
|
||||
async function getTLSN(): Promise<TLSN> {
|
||||
if (_tlsn) return _tlsn;
|
||||
async function getTLSN(logging_filter?: string): Promise<TLSN> {
|
||||
const logging_filter_changed =
|
||||
logging_filter && logging_filter == current_logging_filter;
|
||||
|
||||
if (!logging_filter_changed && _tlsn) return _tlsn;
|
||||
// @ts-ignore
|
||||
_tlsn = await new TLSN();
|
||||
if (logging_filter) _tlsn = await new TLSN(logging_filter);
|
||||
else _tlsn = await new TLSN();
|
||||
return _tlsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* If you want to change the default logging filter, call this method before calling prove or verify
|
||||
* For the filter syntax consult: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax
|
||||
* @param logging_filter
|
||||
*/
|
||||
export const set_logging_filter = async (logging_filter: string) => {
|
||||
getTLSN(logging_filter);
|
||||
};
|
||||
|
||||
export const prove = async (
|
||||
url: string,
|
||||
options: {
|
||||
|
||||
21
src/tlsn.ts
21
src/tlsn.ts
@@ -2,13 +2,25 @@ import init, {
|
||||
initThreadPool,
|
||||
prover,
|
||||
verify,
|
||||
setup_tracing_web,
|
||||
} from '../wasm/prover/pkg/tlsn_extension_rs';
|
||||
|
||||
export default class TLSN {
|
||||
private startPromise: any;
|
||||
private resolveStart: any;
|
||||
export const DEFAULT_LOGGING_FILTER: string = 'info,tlsn_extension_rs=debug';
|
||||
|
||||
export default class TLSN {
|
||||
private startPromise: Promise<void>;
|
||||
private resolveStart!: () => void;
|
||||
private logging_filter: string;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the TLSN class.
|
||||
*
|
||||
* @param logging_filter - Optional logging filter string.
|
||||
* Defaults to DEFAULT_LOGGING_FILTER
|
||||
*/
|
||||
constructor(logging_filter: string = DEFAULT_LOGGING_FILTER) {
|
||||
this.logging_filter = logging_filter;
|
||||
|
||||
constructor() {
|
||||
this.startPromise = new Promise((resolve) => {
|
||||
this.resolveStart = resolve;
|
||||
});
|
||||
@@ -20,6 +32,7 @@ export default class TLSN {
|
||||
const numConcurrency = navigator.hardwareConcurrency;
|
||||
// console.log('!@# navigator.hardwareConcurrency=', numConcurrency);
|
||||
await init();
|
||||
setup_tracing_web(this.logging_filter);
|
||||
// const res = await init();
|
||||
// console.log('!@# res.memory=', res.memory);
|
||||
// 6422528 ~= 6.12 mb
|
||||
|
||||
@@ -12,7 +12,7 @@ import { assert } from '../utils';
|
||||
maxTranscriptSize: 16384,
|
||||
notaryUrl: process.env.LOCAL_NOTARY
|
||||
? 'http://localhost:7047'
|
||||
: 'https://notary.pse.dev/v0.1.0-alpha.5',
|
||||
: 'https://notary.pse.dev/v0.1.0-alpha.6',
|
||||
websocketProxyUrl: process.env.LOCAL_WS
|
||||
? 'ws://localhost:55688'
|
||||
: 'wss://notary.pse.dev/proxy?token=swapi.dev',
|
||||
|
||||
@@ -20,7 +20,7 @@ else
|
||||
fi
|
||||
|
||||
# Checkout the specific tag
|
||||
git checkout "v0.1.0-alpha.5"
|
||||
git checkout "v0.1.0-alpha.6"
|
||||
|
||||
for dir in "tlsn/tlsn-server-fixture/" "notary-server"; do
|
||||
# Change to the specific subdirectory
|
||||
|
||||
@@ -28,11 +28,15 @@ wasm-bindgen = "0.2.87"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
wasm-bindgen-rayon = "1.0"
|
||||
pin-project-lite = "0.2.4"
|
||||
anyhow = "1.0"
|
||||
gloo-net = "0.5"
|
||||
|
||||
parking_lot = { version = "0.12", features = ["nightly"] }
|
||||
|
||||
http-body-util = "0.1"
|
||||
hyper = { version = "1.1", features = ["client", "http1"] }
|
||||
hyper-util = { version = "0.1", features = ["http1"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["time"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["time", "env-filter"] }
|
||||
tracing-web = "0.1.2"
|
||||
|
||||
ring = { version = "0.17", features = ["wasm32_unknown_unknown_js"] }
|
||||
@@ -44,9 +48,9 @@ time = { version = "0.3.34", features = ["wasm-bindgen"] }
|
||||
# Used to calculate elapsed time.
|
||||
web-time = "1.0"
|
||||
|
||||
tlsn-core = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.5", package = "tlsn-core" }
|
||||
tlsn-prover = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.5", package = "tlsn-prover", features = [
|
||||
"tracing",
|
||||
tlsn-core = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.6", package = "tlsn-core" }
|
||||
tlsn-prover = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.6", package = "tlsn-prover", features = [
|
||||
"force-st",
|
||||
] }
|
||||
|
||||
web-sys = { version = "0.3.4", features = [
|
||||
|
||||
@@ -3,6 +3,8 @@ mod request_opt;
|
||||
mod requests;
|
||||
|
||||
pub mod prover;
|
||||
use futures::channel::oneshot;
|
||||
use futures::Future;
|
||||
pub use prover::prover;
|
||||
|
||||
pub mod verify;
|
||||
@@ -10,34 +12,55 @@ use tracing::error;
|
||||
pub use verify::verify;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
pub use crate::request_opt::{RequestOptions, VerifyResult};
|
||||
|
||||
pub use wasm_bindgen_rayon::init_thread_pool;
|
||||
|
||||
use js_sys::JSON;
|
||||
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, Response};
|
||||
|
||||
use std::panic;
|
||||
use tracing::debug;
|
||||
use tracing_subscriber::fmt::format::Pretty;
|
||||
use tracing_subscriber::fmt::time::UtcTime;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use tracing_web::{performance_layer, MakeWebConsoleWriter};
|
||||
|
||||
extern crate console_error_panic_hook;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn setup_tracing_web() {
|
||||
#[derive(Debug)]
|
||||
pub struct Error(Box<dyn std::error::Error + Send + Sync + 'static>);
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Error {
|
||||
pub fn new<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>>(error: E) -> Self {
|
||||
Self(error.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn setup_tracing_web(logging_filter: &str) {
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(false) // Only partially supported across browsers
|
||||
.with_timer(UtcTime::rfc_3339()) // std::time is not available in browsers
|
||||
// .with_thread_ids(true)
|
||||
// .with_thread_names(true)
|
||||
.with_writer(MakeWebConsoleWriter::new()); // write events to the console
|
||||
let perf_layer = performance_layer().with_details_from_fields(Pretty::default());
|
||||
|
||||
let filter_layer = EnvFilter::builder()
|
||||
.parse(logging_filter)
|
||||
.unwrap_or_default();
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::filter::LevelFilter::DEBUG)
|
||||
.with(filter_layer)
|
||||
.with(fmt_layer)
|
||||
.with(perf_layer)
|
||||
.init(); // Install these as subscribers to tracing events
|
||||
@@ -47,17 +70,32 @@ pub fn setup_tracing_web() {
|
||||
error!("panic occurred: {:?}", info);
|
||||
console_error_panic_hook::hook(info);
|
||||
}));
|
||||
|
||||
debug!("🪵 Logging set up 🪵")
|
||||
}
|
||||
|
||||
pub async fn fetch_as_json_string(url: &str, opts: &RequestInit) -> Result<String, JsValue> {
|
||||
let request = Request::new_with_str_and_init(url, opts)?;
|
||||
let window = web_sys::window().expect("Window object");
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
assert!(resp_value.is_instance_of::<Response>());
|
||||
let resp: Response = resp_value.dyn_into()?;
|
||||
let json = JsFuture::from(resp.json()?).await?;
|
||||
let stringified = JSON::stringify(&json)?;
|
||||
stringified
|
||||
.as_string()
|
||||
.ok_or_else(|| JsValue::from_str("Could not stringify JSON"))
|
||||
fn spawn_with_handle<F: Future<Output = R> + Send + 'static, R: Send + 'static>(
|
||||
f: F,
|
||||
) -> impl Future<Output = R> + Send + 'static {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
spawn_local(async move {
|
||||
_ = sender.send(f.await);
|
||||
});
|
||||
async move { receiver.await.unwrap() }
|
||||
}
|
||||
|
||||
fn spawn_rayon_with_handle<
|
||||
F: FnOnce() -> Fut + Send + 'static,
|
||||
Fut: Future<Output = R> + 'static,
|
||||
R: Send + 'static,
|
||||
>(
|
||||
f: F,
|
||||
) -> impl Future<Output = R> + Send + 'static {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
rayon::spawn(move || {
|
||||
futures::executor::block_on(async move {
|
||||
_ = sender.send(f().await);
|
||||
})
|
||||
});
|
||||
async move { receiver.await.unwrap() }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use futures::channel::oneshot;
|
||||
use anyhow::bail;
|
||||
use futures::AsyncWriteExt;
|
||||
use gloo_net::http::{Headers, Request as BrowserRequest};
|
||||
use std::ops::Range;
|
||||
use tlsn_prover::tls::{Prover, ProverConfig};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_time::Instant;
|
||||
|
||||
use ws_stream_wasm::*;
|
||||
@@ -9,23 +10,26 @@ use ws_stream_wasm::*;
|
||||
use crate::hyper_io::FuturesIo;
|
||||
use crate::request_opt::RequestOptions;
|
||||
use crate::requests::{ClientType, NotarizationSessionRequest, NotarizationSessionResponse};
|
||||
use crate::spawn_with_handle;
|
||||
|
||||
pub use wasm_bindgen_rayon::init_thread_pool;
|
||||
|
||||
pub use crate::request_opt::VerifyResult;
|
||||
use crate::{fetch_as_json_string, setup_tracing_web};
|
||||
use futures::AsyncWriteExt;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{body::Bytes, Request, StatusCode};
|
||||
|
||||
use js_sys::Array;
|
||||
use strum::EnumMessage;
|
||||
use tlsn_core::proof::TlsProof;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Headers, RequestInit, RequestMode};
|
||||
use web_sys::RequestMode;
|
||||
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
type Result<T, Error = JsError> = std::result::Result<T, Error>;
|
||||
|
||||
trait Io: futures::AsyncRead + futures::AsyncWrite + Send + Unpin + 'static {}
|
||||
impl<T> Io for T where T: futures::AsyncRead + futures::AsyncWrite + Send + Unpin + 'static {}
|
||||
|
||||
#[derive(strum_macros::EnumMessage, Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
@@ -76,174 +80,77 @@ pub async fn prover(
|
||||
val: JsValue,
|
||||
secret_headers: JsValue,
|
||||
secret_body: JsValue,
|
||||
) -> Result<String, JsValue> {
|
||||
info!("target_url: {}", target_url_str);
|
||||
let target_url = Url::parse(target_url_str)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not parse target_url: {:?}", e)))?;
|
||||
) -> Result<String, JsError> {
|
||||
info!("new version");
|
||||
|
||||
info!(
|
||||
"target_url.host: {}",
|
||||
target_url
|
||||
.host()
|
||||
.ok_or(JsValue::from_str("Could not get target host"))?
|
||||
);
|
||||
let options: RequestOptions = serde_wasm_bindgen::from_value(val)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not deserialize options: {:?}", e)))?;
|
||||
info!("options.notary_url: {}", options.notary_url.as_str());
|
||||
debug!("target_url: {}", target_url_str);
|
||||
let target_url = Url::parse(target_url_str)?;
|
||||
let target_host = target_url
|
||||
.host()
|
||||
.ok_or(JsError::new("target url missing host"))?
|
||||
.to_string();
|
||||
|
||||
debug!("target_url.host: {}", &target_host);
|
||||
|
||||
let options: RequestOptions = serde_wasm_bindgen::from_value(val)?;
|
||||
debug!("options.notary_url: {}", options.notary_url.as_str());
|
||||
|
||||
let secret_headers: Vec<String> = serde_wasm_bindgen::from_value(secret_headers)?;
|
||||
let secret_body: Vec<String> = serde_wasm_bindgen::from_value(secret_body)?;
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let (session_id, notary_io) = connect_notary(
|
||||
&options.notary_url,
|
||||
options.max_sent_data,
|
||||
options.max_recv_data,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||
|
||||
// Basic default prover config
|
||||
let mut builder = ProverConfig::builder();
|
||||
|
||||
/*
|
||||
* Connect Notary with websocket
|
||||
*/
|
||||
builder.id(session_id).server_dns(target_host);
|
||||
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("POST");
|
||||
// opts.method("GET");
|
||||
opts.mode(RequestMode::Cors);
|
||||
if let Some(max_sent_data) = options.max_sent_data {
|
||||
builder.max_sent_data(max_sent_data);
|
||||
}
|
||||
if let Some(max_recv_data) = options.max_recv_data {
|
||||
builder.max_recv_data(max_recv_data);
|
||||
}
|
||||
|
||||
// set headers
|
||||
let headers = Headers::new()
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not create headers: {:?}", e)))?;
|
||||
let notary_url = Url::parse(options.notary_url.as_str())
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not parse notary_url: {:?}", e)))?;
|
||||
let notary_ssl = notary_url.scheme() == "https" || notary_url.scheme() == "wss";
|
||||
let notary_host = notary_url.authority();
|
||||
let notary_path = notary_url.path();
|
||||
let notary_path_str = if notary_path == "/" { "" } else { notary_path };
|
||||
let prover = Prover::new(builder.build()?).setup(notary_io).await?;
|
||||
|
||||
headers
|
||||
.append("Host", notary_host)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not append Host header: {:?}", e)))?;
|
||||
headers
|
||||
.append("Content-Type", "application/json")
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Could not append Content-Type header: {:?}", e))
|
||||
})?;
|
||||
opts.headers(&headers);
|
||||
|
||||
info!("notary_host: {}", notary_host);
|
||||
// set body
|
||||
let payload = serde_json::to_string(&NotarizationSessionRequest {
|
||||
client_type: ClientType::Websocket,
|
||||
max_sent_data: options.max_sent_data,
|
||||
max_recv_data: options.max_recv_data,
|
||||
})
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not serialize request: {:?}", e)))?;
|
||||
opts.body(Some(&JsValue::from_str(&payload)));
|
||||
|
||||
// url
|
||||
let url = format!(
|
||||
"{}://{}{}/session",
|
||||
if notary_ssl { "https" } else { "http" },
|
||||
notary_host,
|
||||
notary_path_str
|
||||
);
|
||||
debug!("Request: {}", url);
|
||||
let rust_string = fetch_as_json_string(&url, &opts)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not fetch session: {:?}", e)))?;
|
||||
let notarization_response =
|
||||
serde_json::from_str::<NotarizationSessionResponse>(&rust_string)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not deserialize response: {:?}", e)))?;
|
||||
debug!("Response: {}", rust_string);
|
||||
|
||||
info!("Notarization response: {:?}", notarization_response,);
|
||||
let notary_wss_url = format!(
|
||||
"{}://{}{}/notarize?sessionId={}",
|
||||
if notary_ssl { "wss" } else { "ws" },
|
||||
notary_host,
|
||||
notary_path_str,
|
||||
notarization_response.session_id
|
||||
);
|
||||
let (_, notary_ws_stream) = WsMeta::connect(notary_wss_url, None)
|
||||
.await
|
||||
.expect_throw("assume the notary ws connection succeeds");
|
||||
let notary_ws_stream_into = notary_ws_stream.into_io();
|
||||
|
||||
log_phase(ProverPhases::BuildProverConfig);
|
||||
|
||||
let target_host = target_url
|
||||
.host_str()
|
||||
.ok_or(JsValue::from_str("Could not get target host"))?;
|
||||
|
||||
// Basic default prover config
|
||||
let mut builder = ProverConfig::builder();
|
||||
|
||||
if let Some(max_sent_data) = options.max_sent_data {
|
||||
builder.max_sent_data(max_sent_data);
|
||||
}
|
||||
if let Some(max_recv_data) = options.max_recv_data {
|
||||
builder.max_recv_data(max_recv_data);
|
||||
}
|
||||
let config = builder
|
||||
.id(notarization_response.session_id)
|
||||
.server_dns(target_host)
|
||||
.build()
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not build prover config: {:?}", e)))?;
|
||||
|
||||
|
||||
|
||||
// Create a Prover and set it up with the Notary
|
||||
// This will set up the MPC backend prior to connecting to the server.
|
||||
log_phase(ProverPhases::SetUpProver);
|
||||
let prover = Prover::new(config)
|
||||
.setup(notary_ws_stream_into)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not set up prover: {:?}", e)))?;
|
||||
|
||||
/*
|
||||
Connect Application Server with websocket proxy
|
||||
*/
|
||||
log_phase(ProverPhases::ConnectWsProxy);
|
||||
|
||||
let (_, client_ws_stream) = WsMeta::connect(options.websocket_proxy_url, None)
|
||||
debug!("connecting to server");
|
||||
let (_, server_io) = WsMeta::connect(&options.websocket_proxy_url, None)
|
||||
.await
|
||||
.expect_throw("assume the client ws connection succeeds");
|
||||
debug!("connected to server");
|
||||
|
||||
// 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.
|
||||
log_phase(ProverPhases::BindProverToConnection);
|
||||
let (mpc_tls_connection, prover_fut) =
|
||||
prover.connect(client_ws_stream.into_io()).await.unwrap();
|
||||
let mpc_tls_connection = unsafe { FuturesIo::new(mpc_tls_connection) };
|
||||
let (tls_connection, prover_fut) = prover.connect(server_io.into_io()).await?;
|
||||
let tls_connection = unsafe { FuturesIo::new(tls_connection) };
|
||||
|
||||
let prover_ctrl = prover_fut.control();
|
||||
|
||||
log_phase(ProverPhases::SpawnProverThread);
|
||||
let (prover_sender, prover_receiver) = oneshot::channel();
|
||||
let handled_prover_fut = async {
|
||||
let result = prover_fut.await;
|
||||
let _ = prover_sender.send(result);
|
||||
};
|
||||
spawn_local(handled_prover_fut);
|
||||
let prover_handle = spawn_with_handle(prover_fut);
|
||||
|
||||
// Attach the hyper HTTP client to the TLS connection
|
||||
log_phase(ProverPhases::AttachHttpClient);
|
||||
let (mut request_sender, connection) =
|
||||
hyper::client::conn::http1::handshake(mpc_tls_connection)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not handshake: {:?}", e)))?;
|
||||
hyper::client::conn::http1::handshake(tls_connection).await?;
|
||||
|
||||
// Spawn the HTTP task to be run concurrently
|
||||
log_phase(ProverPhases::SpawnHttpTask);
|
||||
let (connection_sender, connection_receiver) = oneshot::channel();
|
||||
let connection_fut = connection.without_shutdown();
|
||||
let handled_connection_fut = async {
|
||||
let result = connection_fut.await;
|
||||
let _ = connection_sender.send(result);
|
||||
};
|
||||
spawn_local(handled_connection_fut);
|
||||
let connection_handle = spawn_with_handle(connection.without_shutdown());
|
||||
|
||||
log_phase(ProverPhases::BuildRequest);
|
||||
let mut req_with_header = Request::builder()
|
||||
.uri(target_url_str)
|
||||
.uri(target_url.as_str())
|
||||
.method(options.method.as_str());
|
||||
|
||||
for (key, value) in options.headers {
|
||||
for (key, value) in &options.headers {
|
||||
info!("adding header: {} - {}", key.as_str(), value.as_str());
|
||||
req_with_header = req_with_header.header(key.as_str(), value.as_str());
|
||||
}
|
||||
@@ -253,80 +160,47 @@ pub async fn prover(
|
||||
req_with_header.body(Full::new(Bytes::default()))
|
||||
} else {
|
||||
info!("added body - {}", options.body.as_str());
|
||||
req_with_header.body(Full::from(options.body))
|
||||
req_with_header.body(Full::from(options.body.clone()))
|
||||
};
|
||||
|
||||
let unwrapped_request = req_with_body
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not build request: {:?}", e)))?;
|
||||
let unwrapped_request = req_with_body?;
|
||||
|
||||
log_phase(ProverPhases::StartMpcConnection);
|
||||
|
||||
// Defer decryption of the response.
|
||||
prover_ctrl
|
||||
.defer_decryption()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("failed to enable deferred decryption: {}", e)))?;
|
||||
prover_ctrl.defer_decryption().await?;
|
||||
|
||||
// Send the request to the Server and get a response via the MPC TLS connection
|
||||
let response = request_sender
|
||||
.send_request(unwrapped_request)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not send request: {:?}", e)))?;
|
||||
let response = request_sender.send_request(unwrapped_request).await?;
|
||||
|
||||
log_phase(ProverPhases::ReceivedResponse);
|
||||
if response.status() != StatusCode::OK {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
JsError::new(&format!(
|
||||
"Response status is not OK: {:?}",
|
||||
response.status()
|
||||
)));
|
||||
));
|
||||
}
|
||||
|
||||
log_phase(ProverPhases::ParseResponse);
|
||||
// Pretty printing :)
|
||||
let payload = response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not get response body: {:?}", e)))?
|
||||
.to_bytes();
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload))
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not parse response: {:?}", e)))?;
|
||||
let response_pretty = serde_json::to_string_pretty(&parsed)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not serialize response: {:?}", e)))?;
|
||||
let payload = response.into_body().collect().await?.to_bytes();
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload))?;
|
||||
let response_pretty = serde_json::to_string_pretty(&parsed)?;
|
||||
info!("Response: {}", response_pretty);
|
||||
|
||||
// Close the connection to the server
|
||||
log_phase(ProverPhases::CloseConnection);
|
||||
let mut client_socket = connection_receiver
|
||||
.await
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!(
|
||||
"Could not receive from connection_receiver: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not get TlsConnection: {:?}", e)))?
|
||||
.io
|
||||
.into_inner();
|
||||
client_socket
|
||||
.close()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not close socket: {:?}", e)))?;
|
||||
let mut tls_connection = connection_handle.await?.io.into_inner();
|
||||
tls_connection.close().await?;
|
||||
|
||||
// The Prover task should be done now, so we can grab it.
|
||||
log_phase(ProverPhases::StartNotarization);
|
||||
let prover = prover_receiver
|
||||
.await
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Could not receive from prover_receiver: {:?}", e))
|
||||
})?
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not get Prover: {:?}", e)))?;
|
||||
let prover = prover_handle.await?;
|
||||
let mut prover = prover.start_notarize();
|
||||
|
||||
let secret_headers_vecs = string_list_to_bytes_vec(&secret_headers)?;
|
||||
let secret_headers_slices: Vec<&[u8]> = secret_headers_vecs
|
||||
let secret_headers_slices: Vec<&[u8]> = secret_headers
|
||||
.iter()
|
||||
.map(|vec| vec.as_slice())
|
||||
.map(|header| header.as_bytes())
|
||||
.collect();
|
||||
|
||||
// Identify the ranges in the transcript that contain revealed_headers
|
||||
@@ -335,9 +209,7 @@ pub async fn prover(
|
||||
secret_headers_slices.as_slice(),
|
||||
);
|
||||
|
||||
let secret_body_vecs = string_list_to_bytes_vec(&secret_body)?;
|
||||
let secret_body_slices: Vec<&[u8]> =
|
||||
secret_body_vecs.iter().map(|vec| vec.as_slice()).collect();
|
||||
let secret_body_slices: Vec<&[u8]> = secret_body.iter().map(|body| body.as_bytes()).collect();
|
||||
|
||||
// Identify the ranges in the transcript that contain the only data we want to reveal later
|
||||
let (recv_public_ranges, recv_private_ranges) = find_ranges(
|
||||
@@ -354,46 +226,25 @@ pub async fn prover(
|
||||
// Commit to the outbound and inbound transcript, isolating the data that contain secrets
|
||||
let sent_pub_commitment_ids = sent_public_ranges
|
||||
.iter()
|
||||
.map(|range| {
|
||||
builder.commit_sent(range).map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing sent pub range: {:?}", e))
|
||||
})
|
||||
})
|
||||
.map(|range| builder.commit_sent(range))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
sent_private_ranges.iter().try_for_each(|range| {
|
||||
builder
|
||||
.commit_sent(range)
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing sent private range: {:?}", e))
|
||||
})
|
||||
.map(|_| ())
|
||||
})?;
|
||||
sent_private_ranges
|
||||
.iter()
|
||||
.try_for_each(|range| builder.commit_sent(range).map(|_| ()))?;
|
||||
|
||||
let recv_pub_commitment_ids = recv_public_ranges
|
||||
.iter()
|
||||
.map(|range| {
|
||||
builder.commit_recv(range).map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing recv public ranges: {:?}", e))
|
||||
})
|
||||
})
|
||||
.map(|range| builder.commit_recv(range))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
recv_private_ranges.iter().try_for_each(|range| {
|
||||
builder
|
||||
.commit_recv(range)
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing recv private range: {:?}", e))
|
||||
})
|
||||
.map(|_| ())
|
||||
})?;
|
||||
recv_private_ranges
|
||||
.iter()
|
||||
.try_for_each(|range| builder.commit_recv(range).map(|_| ()))?;
|
||||
|
||||
// Finalize, returning the notarized session
|
||||
log_phase(ProverPhases::Finalize);
|
||||
let notarized_session = prover
|
||||
.finalize()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Error finalizing prover: {:?}", e)))?;
|
||||
let notarized_session = prover.finalize().await?;
|
||||
|
||||
log_phase(ProverPhases::NotarizationComplete);
|
||||
|
||||
@@ -407,31 +258,85 @@ pub async fn prover(
|
||||
sent_pub_commitment_ids
|
||||
.iter()
|
||||
.chain(recv_pub_commitment_ids.iter())
|
||||
.try_for_each(|id| {
|
||||
proof_builder
|
||||
.reveal_by_id(*id)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not reveal commitment: {:?}", e)))
|
||||
.map(|_| ())
|
||||
})?;
|
||||
.try_for_each(|id| proof_builder.reveal_by_id(*id).map(|_| ()))?;
|
||||
|
||||
let substrings_proof = proof_builder
|
||||
.build()
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not build proof: {:?}", e)))?;
|
||||
let substrings_proof = proof_builder.build()?;
|
||||
|
||||
let proof = TlsProof {
|
||||
session: session_proof,
|
||||
substrings: substrings_proof,
|
||||
};
|
||||
|
||||
let res = serde_json::to_string_pretty(&proof)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not serialize proof: {:?}", e)))?;
|
||||
let res = serde_json::to_string_pretty(&proof)?;
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
|
||||
info!("!@# request took {} seconds", duration.as_secs());
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn connect_notary(
|
||||
url: &str,
|
||||
max_sent_data: Option<usize>,
|
||||
max_recv_data: Option<usize>,
|
||||
) -> Result<(String, impl Io), anyhow::Error> {
|
||||
// set headers
|
||||
let notary_url = Url::parse(url)?;
|
||||
let notary_ssl = notary_url.scheme() == "https" || notary_url.scheme() == "wss";
|
||||
let notary_host = notary_url.authority();
|
||||
let notary_path = notary_url.path();
|
||||
let notary_path_str = if notary_path == "/" { "" } else { notary_path };
|
||||
|
||||
let headers = Headers::new();
|
||||
headers.append("Host", notary_host);
|
||||
headers.append("Content-Type", "application/json");
|
||||
|
||||
debug!("sending notarization request");
|
||||
|
||||
let url = format!(
|
||||
"{}://{}{}/session",
|
||||
if notary_ssl { "https" } else { "http" },
|
||||
notary_host,
|
||||
notary_path_str
|
||||
);
|
||||
|
||||
let response = BrowserRequest::post(&url)
|
||||
.mode(RequestMode::Cors)
|
||||
.headers(headers)
|
||||
.body(serde_json::to_string(&NotarizationSessionRequest {
|
||||
client_type: ClientType::Websocket,
|
||||
max_sent_data,
|
||||
max_recv_data,
|
||||
})?)?
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.ok() {
|
||||
debug!("Notarization request succeeded");
|
||||
} else {
|
||||
error!("Notarization request failed: {:?}", response.status());
|
||||
bail!("Notarization request failed: {:?}", response.status());
|
||||
}
|
||||
|
||||
let payload: NotarizationSessionResponse = response.json().await?;
|
||||
debug!("Notarization response: {:?}", &payload);
|
||||
|
||||
let notary_wss_url = format!(
|
||||
"{}://{}{}/notarize?sessionId={}",
|
||||
if notary_ssl { "wss" } else { "ws" },
|
||||
notary_host,
|
||||
notary_path_str,
|
||||
&payload.session_id
|
||||
);
|
||||
|
||||
let (_, notary_ws_stream) = WsMeta::connect(notary_wss_url, None)
|
||||
.await
|
||||
.expect_throw("assume the notary ws connection succeeds");
|
||||
|
||||
Ok((payload.session_id, notary_ws_stream.into_io()))
|
||||
}
|
||||
|
||||
/// Find the ranges of the public and private parts of a sequence.
|
||||
///
|
||||
/// Returns a tuple of `(public, private)` ranges.
|
||||
@@ -463,19 +368,3 @@ fn find_ranges(seq: &[u8], private_seq: &[&[u8]]) -> (Vec<Range<usize>>, Vec<Ran
|
||||
|
||||
(public_ranges, private_ranges)
|
||||
}
|
||||
|
||||
fn string_list_to_bytes_vec(secrets: &JsValue) -> Result<Vec<Vec<u8>>, JsValue> {
|
||||
let array: Array = Array::from(secrets);
|
||||
let length = array.length();
|
||||
let mut byte_slices: Vec<Vec<u8>> = Vec::new();
|
||||
|
||||
for i in 0..length {
|
||||
let secret_js: JsValue = array.get(i);
|
||||
let secret_str: String = secret_js
|
||||
.as_string()
|
||||
.ok_or(JsValue::from_str("Could not convert secret to string"))?;
|
||||
let secret_bytes = secret_str.into_bytes();
|
||||
byte_slices.push(secret_bytes);
|
||||
}
|
||||
Ok(byte_slices)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use tracing::info;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{request_opt::VerifyResult, setup_tracing_web};
|
||||
use crate::request_opt::VerifyResult;
|
||||
|
||||
use elliptic_curve::pkcs8::DecodePublicKey;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -6,7 +6,6 @@ extern crate wasm_bindgen_test;
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, str};
|
||||
use wasm_bindgen_test::*;
|
||||
use web_sys::RequestInit;
|
||||
|
||||
extern crate tlsn_extension_rs;
|
||||
use tlsn_extension_rs::*;
|
||||
@@ -19,19 +18,6 @@ macro_rules! log {
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_fetch() {
|
||||
let url = "https://swapi.info/api/";
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
|
||||
let rust_string: String = tlsn_extension_rs::fetch_as_json_string(&url, &opts)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(rust_string.contains("starships"));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn verify() {
|
||||
let pem = str::from_utf8(include_bytes!("../../../test/assets/notary.pem")).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user