mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 13:27:59 -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/data-fixtures",
|
||||||
"crates/examples",
|
"crates/examples",
|
||||||
"crates/formats",
|
"crates/formats",
|
||||||
|
"crates/plugin-core",
|
||||||
"crates/server-fixture/certs",
|
"crates/server-fixture/certs",
|
||||||
"crates/server-fixture/server",
|
"crates/server-fixture/server",
|
||||||
"crates/tls/backend",
|
"crates/tls/backend",
|
||||||
@@ -53,6 +54,7 @@ tlsn-formats = { path = "crates/formats" }
|
|||||||
tlsn-hmac-sha256 = { path = "crates/components/hmac-sha256" }
|
tlsn-hmac-sha256 = { path = "crates/components/hmac-sha256" }
|
||||||
tlsn-key-exchange = { path = "crates/components/key-exchange" }
|
tlsn-key-exchange = { path = "crates/components/key-exchange" }
|
||||||
tlsn-mpc-tls = { path = "crates/mpc-tls" }
|
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 = { path = "crates/server-fixture/server" }
|
||||||
tlsn-server-fixture-certs = { path = "crates/server-fixture/certs" }
|
tlsn-server-fixture-certs = { path = "crates/server-fixture/certs" }
|
||||||
tlsn-tls-backend = { path = "crates/tls/backend" }
|
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" }
|
rangeset = { version = "0.2" }
|
||||||
serio = { 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" }
|
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" }
|
aead = { version = "0.4" }
|
||||||
aes = { version = "0.8" }
|
aes = { version = "0.8" }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::{
|
|||||||
transcript::{Direction, Transcript},
|
transcript::{Direction, Transcript},
|
||||||
webpki::CertificateDer,
|
webpki::CertificateDer,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tls_core::msgs::{
|
use tls_core::msgs::{
|
||||||
alert::AlertMessagePayload,
|
alert::AlertMessagePayload,
|
||||||
codec::{Codec, Reader},
|
codec::{Codec, Reader},
|
||||||
@@ -15,7 +16,7 @@ use tls_core::msgs::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A transcript of TLS records sent and received by the prover.
|
/// A transcript of TLS records sent and received by the prover.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TlsTranscript {
|
pub struct TlsTranscript {
|
||||||
time: u64,
|
time: u64,
|
||||||
version: TlsVersion,
|
version: TlsVersion,
|
||||||
@@ -291,7 +292,7 @@ impl TlsTranscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A TLS record.
|
/// A TLS record.
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
/// Sequence number.
|
/// Sequence number.
|
||||||
pub seq: u64,
|
pub seq: u64,
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ Cookie: very-secret-cookie
|
|||||||
Content-Length: 44
|
Content-Length: 44
|
||||||
Content-Type: application/json
|
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