mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-08 21:08:04 -05:00
feat: added plugin-core crate
This commit is contained in:
375
Cargo.lock
generated
375
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ members = [
|
||||
"crates/data-fixtures",
|
||||
"crates/examples",
|
||||
"crates/formats",
|
||||
"crates/plugin-core",
|
||||
"crates/server-fixture/certs",
|
||||
"crates/server-fixture/server",
|
||||
"crates/tls/backend",
|
||||
@@ -53,6 +54,7 @@ tlsn-formats = { path = "crates/formats" }
|
||||
tlsn-hmac-sha256 = { path = "crates/components/hmac-sha256" }
|
||||
tlsn-key-exchange = { path = "crates/components/key-exchange" }
|
||||
tlsn-mpc-tls = { path = "crates/mpc-tls" }
|
||||
tlsn-plugin-core = { path = "crates/plugin-core" }
|
||||
tlsn-server-fixture = { path = "crates/server-fixture/server" }
|
||||
tlsn-server-fixture-certs = { path = "crates/server-fixture/certs" }
|
||||
tlsn-tls-backend = { path = "crates/tls/backend" }
|
||||
@@ -83,9 +85,10 @@ mpz-ideal-vm = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-
|
||||
|
||||
rangeset = { version = "0.2" }
|
||||
serio = { version = "0.2" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "304b910" }
|
||||
uid-mux = { version = "0.2" }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "304b910" }
|
||||
futures-plex = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "304b910" }
|
||||
|
||||
aead = { version = "0.4" }
|
||||
aes = { version = "0.8" }
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
transcript::{Direction, Transcript},
|
||||
webpki::CertificateDer,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tls_core::msgs::{
|
||||
alert::AlertMessagePayload,
|
||||
codec::{Codec, Reader},
|
||||
@@ -15,7 +16,7 @@ use tls_core::msgs::{
|
||||
};
|
||||
|
||||
/// A transcript of TLS records sent and received by the prover.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TlsTranscript {
|
||||
time: u64,
|
||||
version: TlsVersion,
|
||||
@@ -291,7 +292,7 @@ impl TlsTranscript {
|
||||
}
|
||||
|
||||
/// A TLS record.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Record {
|
||||
/// Sequence number.
|
||||
pub seq: u64,
|
||||
|
||||
@@ -3,4 +3,4 @@ Cookie: very-secret-cookie
|
||||
Content-Length: 44
|
||||
Content-Type: application/json
|
||||
|
||||
{"foo": "bar", "bazz": 123, "buzz": [1,"5"]}
|
||||
{"foo": "bar", "bazz": 123, "buzz": [1,"5"]}
|
||||
22
crates/plugin-core/Cargo.toml
Normal file
22
crates/plugin-core/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "tlsn-plugin-core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tlsn = { workspace = true }
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn-formats = { workspace = true }
|
||||
|
||||
http-body-util = { workspace = true }
|
||||
hyper = { workspace = true, features = ["client", "http1"] }
|
||||
rangeset = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
spansy = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tlsn-data-fixtures = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
105
crates/plugin-core/src/lib.rs
Normal file
105
crates/plugin-core/src/lib.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! Core types of the prover and verifier plugin.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tlsn_core::{
|
||||
hash::HashAlgId,
|
||||
transcript::{Direction, TranscriptCommitmentKind},
|
||||
};
|
||||
|
||||
mod prover;
|
||||
mod verifier;
|
||||
|
||||
pub use prover::{
|
||||
Config as ProverPluginConfig, ConfigError as ProverPLuginConfigError,
|
||||
Output as ProverPluginOutput,
|
||||
};
|
||||
pub use verifier::{
|
||||
Config as VerifierPluginConfig, ConfigError as VerifierPluginConfigError,
|
||||
Output as VerifierPluginOutput,
|
||||
};
|
||||
|
||||
/// A rule for disclosing HTTP data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DisclosureRule {
|
||||
http: HttpHandle,
|
||||
policy: DisclosurePolicy,
|
||||
}
|
||||
|
||||
/// Handle for a part of an HTTP message.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HttpHandle {
|
||||
typ: MessageType,
|
||||
part: MessagePart,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MessageType {
|
||||
Request,
|
||||
Response,
|
||||
}
|
||||
|
||||
impl From<&MessageType> for Direction {
|
||||
fn from(mt: &MessageType) -> Self {
|
||||
match mt {
|
||||
MessageType::Request => Direction::Sent,
|
||||
MessageType::Response => Direction::Received,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Disclosure policy.
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum DisclosurePolicy {
|
||||
/// Reveals data.
|
||||
Reveal,
|
||||
/// Creates a hiding commitment.
|
||||
Commit(Alg),
|
||||
}
|
||||
|
||||
/// Commitment algorithm.
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Alg {
|
||||
EncodingSha256,
|
||||
EncodingBlake3,
|
||||
EncodingKeccak256,
|
||||
Sha256,
|
||||
Blake3,
|
||||
}
|
||||
|
||||
impl From<&Alg> for TranscriptCommitmentKind {
|
||||
fn from(alg: &Alg) -> Self {
|
||||
match alg {
|
||||
Alg::EncodingSha256 | Alg::EncodingBlake3 | Alg::EncodingKeccak256 => {
|
||||
TranscriptCommitmentKind::Encoding
|
||||
}
|
||||
Alg::Sha256 => TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::SHA256,
|
||||
},
|
||||
Alg::Blake3 => TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::BLAKE3,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The part of an HTTP message.
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MessagePart {
|
||||
All,
|
||||
StartLine,
|
||||
Header(HeaderParams),
|
||||
Body(BodyParams),
|
||||
}
|
||||
|
||||
/// Parameters for an HTTP header.
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HeaderParams {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
/// Parameters for a part of an HTTP body.
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum BodyParams {
|
||||
JsonPath(String),
|
||||
XPath(String),
|
||||
}
|
||||
34
crates/plugin-core/src/prover.rs
Normal file
34
crates/plugin-core/src/prover.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Core types of the prover plugin.
|
||||
|
||||
use crate::HttpHandle;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tlsn_core::ProverOutput;
|
||||
|
||||
mod config;
|
||||
|
||||
pub use config::{Config, ConfigError};
|
||||
|
||||
/// Output of the prover plugin.
|
||||
#[allow(dead_code)]
|
||||
pub struct Output {
|
||||
output: ProverOutput,
|
||||
/// Plaintext exposed to the host.
|
||||
plaintext: Vec<(HttpHandle, Vec<u8>)>,
|
||||
}
|
||||
|
||||
/// Params for protocol prover.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProverParams {
|
||||
max_recv_data: usize,
|
||||
max_sent_data: usize,
|
||||
prove_server_identity: bool,
|
||||
pub server_dns: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HttpRequest {
|
||||
url: String,
|
||||
method: String,
|
||||
body: Option<Vec<u8>>,
|
||||
pub headers: Vec<(String, String)>,
|
||||
}
|
||||
463
crates/plugin-core/src/prover/config.rs
Normal file
463
crates/plugin-core/src/prover/config.rs
Normal file
@@ -0,0 +1,463 @@
|
||||
use crate::{
|
||||
BodyParams, DisclosurePolicy, DisclosureRule, HttpHandle, MessagePart, MessageType,
|
||||
prover::{HttpRequest, ProverParams},
|
||||
};
|
||||
|
||||
use crate::prover::Output;
|
||||
use http_body_util::Full;
|
||||
use hyper::{Request as HyperRequest, body::Bytes};
|
||||
use rangeset::RangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tlsn::{
|
||||
config::ProtocolConfig,
|
||||
prover::{ProverConfig, TlsConfig},
|
||||
};
|
||||
use tlsn_core::{
|
||||
ProveConfig, ProveConfigBuilder, ProverOutput,
|
||||
connection::{DnsName, ServerName},
|
||||
transcript::{Transcript, TranscriptCommitConfig, TranscriptCommitConfigBuilder},
|
||||
webpki::RootCertStore,
|
||||
};
|
||||
use tlsn_formats::{
|
||||
http::{Body, Request, Requests, Response, Responses},
|
||||
json::JsonValue,
|
||||
spansy,
|
||||
spansy::Spanned,
|
||||
};
|
||||
|
||||
/// Prover plugin config.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub prover_params: ProverParams,
|
||||
pub request: HttpRequest,
|
||||
/// Data which will be disclosed to the verifier.
|
||||
pub disclose: Vec<DisclosureRule>,
|
||||
/// Data which will be exposed in the plugin output.
|
||||
pub expose: Vec<HttpHandle>,
|
||||
pub root_store: RootCertStore,
|
||||
pub verifier_endpoint: String,
|
||||
/// Proxy endpoint for connecting to the server.
|
||||
pub proxy_endpoint: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Returns the verifier endpoint.
|
||||
pub fn prover_endpoint(&self) -> &String {
|
||||
&self.verifier_endpoint
|
||||
}
|
||||
|
||||
/// Builds and returns [ProverConfig].
|
||||
pub fn prover_config(&self) -> Result<ProverConfig, ConfigError> {
|
||||
let dns_name: DnsName = self
|
||||
.prover_params
|
||||
.server_dns
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| ConfigError("prover_config error".to_string()))?;
|
||||
|
||||
let mut builder = TlsConfig::builder();
|
||||
builder.root_store(self.root_store.clone());
|
||||
let tls_config = builder.build().unwrap();
|
||||
|
||||
let config = ProverConfig::builder()
|
||||
.server_name(ServerName::Dns(dns_name))
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
.max_sent_data(self.prover_params.max_sent_data)
|
||||
.max_recv_data(self.prover_params.max_recv_data)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Returns the HTTP request.
|
||||
pub fn http_request(&self) -> Result<HyperRequest<Full<Bytes>>, ConfigError> {
|
||||
let mut request = HyperRequest::builder()
|
||||
.uri(self.request.url.clone())
|
||||
.header("Host", self.prover_params.server_dns.clone());
|
||||
|
||||
for (k, v) in &self.request.headers {
|
||||
request = request.header(k, v);
|
||||
}
|
||||
|
||||
request = request.method(self.request.method.as_str());
|
||||
let body = match &self.request.body {
|
||||
Some(data) => Full::<Bytes>::from(data.clone()),
|
||||
None => Full::<Bytes>::from(vec![]),
|
||||
};
|
||||
|
||||
request
|
||||
.body(body)
|
||||
.map_err(|_| ConfigError("http_request error".to_string()))
|
||||
}
|
||||
|
||||
/// Creates a [ProveConfig] for the given `transcript`.
|
||||
pub fn prove_config(&self, transcript: &Transcript) -> Result<ProveConfig, ConfigError> {
|
||||
let mut prove_cfg = ProveConfig::builder(transcript);
|
||||
let mut commit_cfg = TranscriptCommitConfig::builder(transcript);
|
||||
|
||||
if self.prover_params.prove_server_identity {
|
||||
prove_cfg.server_identity();
|
||||
}
|
||||
|
||||
let reqs = Requests::new_from_slice(transcript.sent())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| ConfigError("prove_config error".to_string()))?;
|
||||
let resps = Responses::new_from_slice(transcript.received())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| ConfigError("prove_config error".to_string()))?;
|
||||
|
||||
let req = reqs.first().expect("at least one request");
|
||||
let resp = resps.first().expect("at least one response");
|
||||
|
||||
let req_rules = self
|
||||
.disclose
|
||||
.iter()
|
||||
.filter(|h| h.http.typ == MessageType::Request);
|
||||
let resp_rules = self
|
||||
.disclose
|
||||
.iter()
|
||||
.filter(|h| h.http.typ == MessageType::Response);
|
||||
|
||||
disclose_req(req, req_rules, &mut commit_cfg, &mut prove_cfg);
|
||||
disclose_resp(resp, resp_rules, &mut commit_cfg, &mut prove_cfg);
|
||||
|
||||
prove_cfg.transcript_commit(commit_cfg.build().unwrap());
|
||||
Ok(prove_cfg.build().unwrap())
|
||||
}
|
||||
|
||||
/// Returns the output of the plugin.
|
||||
pub fn output(
|
||||
&self,
|
||||
transcript: Transcript,
|
||||
prover_output: ProverOutput,
|
||||
) -> Result<Output, ConfigError> {
|
||||
let reqs = Requests::new_from_slice(transcript.sent())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| ConfigError("output error".to_string()))?;
|
||||
let resps = Responses::new_from_slice(transcript.received())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| ConfigError("output error".to_string()))?;
|
||||
|
||||
let req = reqs.first().expect("at least one request");
|
||||
let resp = resps.first().expect("at least one response");
|
||||
|
||||
let mut exposed = Vec::new();
|
||||
|
||||
// Extract the to-be-exposed data from the transcript.
|
||||
for h in self.expose.iter() {
|
||||
let range = if h.typ == MessageType::Request {
|
||||
req_part_range(req, h)
|
||||
} else {
|
||||
resp_part_range(resp, h)
|
||||
};
|
||||
|
||||
let seq = transcript
|
||||
.get((&h.typ).into(), &range)
|
||||
.ok_or(ConfigError("range not found in transcript".to_string()))?;
|
||||
|
||||
exposed.push((h.clone(), seq.data().to_vec()));
|
||||
}
|
||||
|
||||
Ok(Output {
|
||||
output: prover_output,
|
||||
plaintext: exposed,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("config error: {0}")]
|
||||
pub struct ConfigError(String);
|
||||
|
||||
/// Processes disclosure rules for the request.
|
||||
fn disclose_req<'a, I>(
|
||||
req: &Request,
|
||||
rules: I,
|
||||
commit_cfg: &mut TranscriptCommitConfigBuilder<'_>,
|
||||
prove_cfg: &mut ProveConfigBuilder<'_>,
|
||||
) where
|
||||
I: Iterator<Item = &'a DisclosureRule>,
|
||||
{
|
||||
for r in rules {
|
||||
let range = req_part_range(req, &r.http);
|
||||
|
||||
if range.is_empty() {
|
||||
// TODO: maybe return an error here when the part was not found.
|
||||
return;
|
||||
}
|
||||
|
||||
match &r.policy {
|
||||
DisclosurePolicy::Commit(alg) => {
|
||||
commit_cfg
|
||||
.commit_with_kind(&range, (&r.http.typ).into(), alg.into())
|
||||
.expect("range is in the transcript");
|
||||
}
|
||||
DisclosurePolicy::Reveal => {
|
||||
prove_cfg
|
||||
.reveal_sent(&range)
|
||||
.expect("range is in the transcript");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes disclosure rules for the response.
|
||||
fn disclose_resp<'a, I>(
|
||||
resp: &Response,
|
||||
rules: I,
|
||||
commit_cfg: &mut TranscriptCommitConfigBuilder<'_>,
|
||||
prove_cfg: &mut ProveConfigBuilder<'_>,
|
||||
) where
|
||||
I: Iterator<Item = &'a DisclosureRule>,
|
||||
{
|
||||
for r in rules {
|
||||
let range = resp_part_range(resp, &r.http);
|
||||
|
||||
if range.is_empty() {
|
||||
// TODO: maybe return an error here when the part was not found.
|
||||
return;
|
||||
}
|
||||
|
||||
match &r.policy {
|
||||
DisclosurePolicy::Commit(alg) => {
|
||||
commit_cfg
|
||||
.commit_with_kind(&range, (&r.http.typ).into(), alg.into())
|
||||
.expect("range is in the transcript");
|
||||
}
|
||||
DisclosurePolicy::Reveal => {
|
||||
prove_cfg
|
||||
.reveal_recv(&range)
|
||||
.expect("range is in the transcript");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the range for the given `part` of the HTTP request,
|
||||
fn req_part_range(req: &Request, part: &HttpHandle) -> RangeSet<usize> {
|
||||
match &part.part {
|
||||
MessagePart::All => {
|
||||
(req.span().indices().min().unwrap()..req.span().indices().end().unwrap()).into()
|
||||
}
|
||||
|
||||
MessagePart::StartLine => req.request.span().indices().clone(),
|
||||
|
||||
MessagePart::Header(params) => req
|
||||
.headers_with_name(params.key.as_str())
|
||||
.map(|h| h.span().indices())
|
||||
.fold(RangeSet::default(), |acc, r| acc | r),
|
||||
|
||||
MessagePart::Body(params) => match &req.body {
|
||||
Some(body) => {
|
||||
// Body offset from the start of an HTTP message.
|
||||
let body_offset = body
|
||||
.span()
|
||||
.indices()
|
||||
.min()
|
||||
.expect("body span cannot be empty");
|
||||
let mut range = body_params_range(body, params);
|
||||
range.shift_right(&body_offset);
|
||||
range
|
||||
}
|
||||
None => RangeSet::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the range for the given `part` of the HTTP response,
|
||||
fn resp_part_range(resp: &Response, part: &HttpHandle) -> RangeSet<usize> {
|
||||
match &part.part {
|
||||
MessagePart::All => {
|
||||
(resp.span().indices().min().unwrap()..resp.span().indices().end().unwrap()).into()
|
||||
}
|
||||
MessagePart::StartLine => resp.status.span().indices().clone(),
|
||||
MessagePart::Header(params) => resp
|
||||
.headers_with_name(params.key.as_str())
|
||||
.map(|h| h.span().indices())
|
||||
.fold(RangeSet::default(), |acc, r| acc | r),
|
||||
MessagePart::Body(params) => match &resp.body {
|
||||
Some(body) => {
|
||||
// Body offset from the start of an HTTP message.
|
||||
let body_offset = body.span().indices().min().expect("body cannot be empty");
|
||||
let mut range = body_params_range(body, params);
|
||||
range.shift_right(&body_offset);
|
||||
range
|
||||
}
|
||||
None => RangeSet::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the byte range of the `params` in the given `body`.
|
||||
fn body_params_range(body: &Body, params: &BodyParams) -> RangeSet<usize> {
|
||||
match params {
|
||||
BodyParams::JsonPath(path) => {
|
||||
// TODO: use a better approach than re-parsing the entire
|
||||
// json for each path.
|
||||
match spansy::json::parse(body.as_bytes().to_vec().into()) {
|
||||
Ok(json) => json_path_range(&json, path),
|
||||
Err(_) => RangeSet::default(),
|
||||
}
|
||||
}
|
||||
_ => unimplemented!("only json parsing is currently supported"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the byte range of the key–value pair corresponding to the given
|
||||
/// `path` in a JSON value `source`.
|
||||
///
|
||||
/// If the path points to an array element, only the range of the **value**
|
||||
/// of the element is returned.
|
||||
fn json_path_range(source: &JsonValue, path: &String) -> RangeSet<usize> {
|
||||
let val = match source.get(path) {
|
||||
Some(val) => val,
|
||||
None => return RangeSet::default(),
|
||||
};
|
||||
|
||||
let dot = ".";
|
||||
let last = path.split(dot).last().unwrap();
|
||||
// Whether `path` is a top-level key.
|
||||
let is_top_level = last == path;
|
||||
|
||||
if last.parse::<usize>().is_ok() {
|
||||
// The path points to an array element, so we only need the range of
|
||||
// the **value**.
|
||||
val.span().indices().clone()
|
||||
} else {
|
||||
let parent_val = if is_top_level {
|
||||
source
|
||||
} else {
|
||||
source
|
||||
.get(&path[..path.len() - last.len() - dot.len()])
|
||||
.expect("path is valid")
|
||||
};
|
||||
let JsonValue::Object(parent_obj) = parent_val else {
|
||||
unreachable!("parent value is always an object");
|
||||
};
|
||||
|
||||
// We need the range of the **key-value** pair.
|
||||
let kv = parent_obj
|
||||
.elems
|
||||
.iter()
|
||||
.find(|kv| kv.value == *val)
|
||||
.expect("element exists");
|
||||
|
||||
kv.without_separator()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::HeaderParams;
|
||||
|
||||
use super::*;
|
||||
use spansy::http::parse_response;
|
||||
use tlsn_data_fixtures::http::{request, response};
|
||||
use tlsn_formats::spansy::http::parse_request;
|
||||
|
||||
#[test]
|
||||
fn test_req_part_range() {
|
||||
let data = request::POST_JSON;
|
||||
let req = parse_request(data).unwrap();
|
||||
let s = std::str::from_utf8(data).unwrap();
|
||||
|
||||
//===============All
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::All,
|
||||
typ: MessageType::Request,
|
||||
};
|
||||
let range = req_part_range(&req, &part);
|
||||
assert_eq!(range, 0..data.len());
|
||||
|
||||
//===============StartLine
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::StartLine,
|
||||
typ: MessageType::Request,
|
||||
};
|
||||
let range = req_part_range(&req, &part);
|
||||
let end = s.find("\r\n").unwrap() + 2;
|
||||
assert_eq!(range, 0..end);
|
||||
|
||||
//===============Header
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::Header(HeaderParams {
|
||||
key: "Content-Length".to_string(),
|
||||
}),
|
||||
typ: MessageType::Request,
|
||||
};
|
||||
let range = req_part_range(&req, &part);
|
||||
|
||||
let target: &'static str = "Content-Length: 44";
|
||||
let start = s.find(target).unwrap();
|
||||
let end = start + target.len() + 2;
|
||||
assert_eq!(range, start..end);
|
||||
|
||||
//===============Body
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::Body(BodyParams::JsonPath("bazz".to_string())),
|
||||
typ: MessageType::Request,
|
||||
};
|
||||
let range = req_part_range(&req, &part);
|
||||
|
||||
let target: &'static str = "\"bazz\": 123";
|
||||
let start = s.find(target).unwrap();
|
||||
let end = start + target.len();
|
||||
assert_eq!(range, start..end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resp_part_range() {
|
||||
let data = response::OK_JSON;
|
||||
let resp = parse_response(data).unwrap();
|
||||
let s = std::str::from_utf8(data).unwrap();
|
||||
|
||||
//===============All
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::All,
|
||||
typ: MessageType::Response,
|
||||
};
|
||||
let range = resp_part_range(&resp, &part);
|
||||
assert_eq!(range, 0..data.len());
|
||||
|
||||
//===============StartLine
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::StartLine,
|
||||
typ: MessageType::Response,
|
||||
};
|
||||
let range = resp_part_range(&resp, &part);
|
||||
let end = s.find("\r\n").unwrap() + 2;
|
||||
assert_eq!(range, 0..end);
|
||||
|
||||
//===============Header
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::Header(HeaderParams {
|
||||
key: "Content-Length".to_string(),
|
||||
}),
|
||||
typ: MessageType::Response,
|
||||
};
|
||||
let range = resp_part_range(&resp, &part);
|
||||
|
||||
let target: &'static str = "Content-Length: 44";
|
||||
let start = s.find(target).unwrap();
|
||||
let end = start + target.len() + 2;
|
||||
assert_eq!(range, start..end);
|
||||
|
||||
//===============Body
|
||||
let part = HttpHandle {
|
||||
part: MessagePart::Body(BodyParams::JsonPath("bazz".to_string())),
|
||||
typ: MessageType::Request,
|
||||
};
|
||||
let range = resp_part_range(&resp, &part);
|
||||
|
||||
let target: &'static str = "\"bazz\": 123";
|
||||
let start = s.find(target).unwrap();
|
||||
let end = start + target.len();
|
||||
assert_eq!(range, start..end);
|
||||
}
|
||||
}
|
||||
20
crates/plugin-core/src/verifier.rs
Normal file
20
crates/plugin-core/src/verifier.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Core types of the verifier plugin.
|
||||
|
||||
use tlsn_core::VerifierOutput;
|
||||
|
||||
mod config;
|
||||
|
||||
pub use config::{Config, ConfigError};
|
||||
|
||||
/// Output of the verifier plugin.
|
||||
#[allow(dead_code)]
|
||||
pub struct Output {
|
||||
output: VerifierOutput,
|
||||
}
|
||||
|
||||
/// Params for protocol verifier.
|
||||
pub struct VerifierParams {
|
||||
pub max_sent_data: usize,
|
||||
pub max_recv_data: usize,
|
||||
pub prover_endpoint: String,
|
||||
}
|
||||
56
crates/plugin-core/src/verifier/config.rs
Normal file
56
crates/plugin-core/src/verifier/config.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use crate::{
|
||||
DisclosureRule,
|
||||
verifier::{Output, VerifierParams},
|
||||
};
|
||||
use tlsn::{
|
||||
config::{ProtocolConfig, RootCertStore},
|
||||
verifier::VerifierConfig,
|
||||
};
|
||||
use tlsn_core::VerifierOutput;
|
||||
|
||||
/// Verifier plugin config.
|
||||
#[allow(dead_code)]
|
||||
pub struct Config {
|
||||
pub verifier_params: VerifierParams,
|
||||
/// Data which the prover is expected to disclose.
|
||||
pub disclose: Vec<DisclosureRule>,
|
||||
pub root_store: RootCertStore,
|
||||
pub prover_endpoint: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Returns the prover endpoint.
|
||||
pub fn prover_endpoint(&self) -> &String {
|
||||
&self.verifier_params.prover_endpoint
|
||||
}
|
||||
|
||||
/// Builds and returns [VerifierConfig].
|
||||
pub fn verifier_config(&self) -> VerifierConfig {
|
||||
VerifierConfig::builder()
|
||||
.root_store(self.root_store.clone())
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Validates the given protocol `config`.
|
||||
pub fn validate_protocol_config(&self, config: &ProtocolConfig) -> Result<(), ConfigError> {
|
||||
if config.max_recv_data() > self.verifier_params.max_recv_data
|
||||
|| config.max_sent_data() > self.verifier_params.max_sent_data
|
||||
{
|
||||
Err(ConfigError(
|
||||
"failed to validate protocol config".to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns verifier plugin output.
|
||||
pub fn output(&self, output: VerifierOutput) -> Output {
|
||||
Output { output }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("config error: {0}")]
|
||||
pub struct ConfigError(String);
|
||||
Reference in New Issue
Block a user