mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-11 15:47:58 -05:00
Compare commits
18 Commits
wasm_getra
...
interactiv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bca4bf52ce | ||
|
|
0d1c8a4bd0 | ||
|
|
52b7bfc9d9 | ||
|
|
e0ce1ad31a | ||
|
|
3b76877920 | ||
|
|
783355772a | ||
|
|
e5c59da90b | ||
|
|
f059c53c2d | ||
|
|
a1367b5428 | ||
|
|
9d8124ac9d | ||
|
|
5034366c72 | ||
|
|
afd8f44261 | ||
|
|
21086d2883 | ||
|
|
cca9a318a4 | ||
|
|
cb804a6025 | ||
|
|
9f849e7c18 | ||
|
|
389bceddef | ||
|
|
657838671a |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -21,7 +21,7 @@ env:
|
||||
# - https://github.com/privacy-scaling-explorations/mpz/issues/178
|
||||
# 32 seems to be big enough for the foreseeable future
|
||||
RAYON_NUM_THREADS: 32
|
||||
RUST_VERSION: 1.88.0
|
||||
RUST_VERSION: 1.89.0
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
@@ -198,4 +198,4 @@ jobs:
|
||||
draft: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
generate_release_notes: true
|
||||
|
||||
2256
Cargo.lock
generated
2256
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
38
Cargo.toml
@@ -39,6 +39,8 @@ opt-level = 1
|
||||
[profile.wasm]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.dependencies]
|
||||
tls-server-fixture = { path = "crates/tls/server-fixture" }
|
||||
@@ -64,19 +66,19 @@ tlsn-harness-runner = { path = "crates/harness/runner" }
|
||||
tlsn-wasm = { path = "crates/wasm" }
|
||||
tlsn = { path = "crates/tlsn" }
|
||||
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-zk = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-hash = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-zk = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
mpz-hash = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "6432a43" }
|
||||
|
||||
rangeset = { version = "0.2" }
|
||||
serio = { version = "0.2" }
|
||||
@@ -137,6 +139,8 @@ rs_merkle = { git = "https://github.com/tlsnotary/rs-merkle.git", rev = "85f3e82
|
||||
rstest = { version = "0.17" }
|
||||
rustls = { version = "0.21" }
|
||||
rustls-pemfile = { version = "1.0" }
|
||||
rustls-webpki = { version = "0.103" }
|
||||
rustls-pki-types = { version = "1.12" }
|
||||
sct = { version = "0.7" }
|
||||
semver = { version = "1.0" }
|
||||
serde = { version = "1.0" }
|
||||
@@ -157,7 +161,7 @@ wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
web-spawn = { version = "0.2" }
|
||||
web-time = { version = "0.2" }
|
||||
webpki = { version = "0.22" }
|
||||
webpki-roots = { version = "0.26" }
|
||||
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
|
||||
ws_stream_wasm = { git = "https://github.com/tlsnotary/ws_stream_wasm", rev = "2ed12aad9f0236e5321f577672f309920b2aef51" }
|
||||
webpki-roots = { version = "1.0" }
|
||||
webpki-root-certs = { version = "1.0" }
|
||||
ws_stream_wasm = { version = "0.7.5" }
|
||||
zeroize = { version = "1.8" }
|
||||
|
||||
@@ -21,7 +21,6 @@ rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tiny-keccak = { workspace = true, features = ["keccak"] }
|
||||
webpki-roots = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
alloy-primitives = { version = "0.8.22", default-features = false }
|
||||
|
||||
@@ -242,7 +242,7 @@ impl std::fmt::Display for AttestationBuilderError {
|
||||
mod test {
|
||||
use rstest::{fixture, rstest};
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2},
|
||||
connection::{CertBinding, CertBindingV1_2},
|
||||
fixtures::{ConnectionFixture, encoding_provider},
|
||||
hash::Blake3,
|
||||
transcript::Transcript,
|
||||
@@ -399,10 +399,10 @@ mod test {
|
||||
server_cert_data, ..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake
|
||||
}) = server_cert_data.binding
|
||||
else {
|
||||
panic!("expected v1.2 handshake data");
|
||||
};
|
||||
@@ -470,10 +470,10 @@ mod test {
|
||||
..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake
|
||||
}) = server_cert_data.binding
|
||||
else {
|
||||
panic!("expected v1.2 handshake data");
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use tlsn_core::{
|
||||
connection::{CertificateVerificationError, ServerCertData, ServerEphemKey, ServerName},
|
||||
connection::{HandshakeData, HandshakeVerificationError, ServerEphemKey, ServerName},
|
||||
hash::{Blinded, HashAlgorithm, HashProviderError, TypedHash},
|
||||
};
|
||||
|
||||
@@ -30,14 +30,14 @@ use crate::{CryptoProvider, hash::HashAlgorithmExt, serialize::impl_domain_separ
|
||||
|
||||
/// Opens a [`ServerCertCommitment`].
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ServerCertOpening(Blinded<ServerCertData>);
|
||||
pub struct ServerCertOpening(Blinded<HandshakeData>);
|
||||
|
||||
impl_domain_separator!(ServerCertOpening);
|
||||
|
||||
opaque_debug::implement!(ServerCertOpening);
|
||||
|
||||
impl ServerCertOpening {
|
||||
pub(crate) fn new(data: ServerCertData) -> Self {
|
||||
pub(crate) fn new(data: HandshakeData) -> Self {
|
||||
Self(Blinded::new(data))
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl ServerCertOpening {
|
||||
}
|
||||
|
||||
/// Returns the server identity data.
|
||||
pub fn data(&self) -> &ServerCertData {
|
||||
pub fn data(&self) -> &HandshakeData {
|
||||
self.0.data()
|
||||
}
|
||||
}
|
||||
@@ -122,8 +122,8 @@ impl From<HashProviderError> for ServerIdentityProofError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CertificateVerificationError> for ServerIdentityProofError {
|
||||
fn from(err: CertificateVerificationError) -> Self {
|
||||
impl From<HandshakeVerificationError> for ServerIdentityProofError {
|
||||
fn from(err: HandshakeVerificationError) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Certificate,
|
||||
message: err.to_string(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Attestation fixtures.
|
||||
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2},
|
||||
connection::{CertBinding, CertBindingV1_2},
|
||||
fixtures::ConnectionFixture,
|
||||
hash::HashAlgorithm,
|
||||
transcript::{
|
||||
@@ -67,7 +67,7 @@ pub fn request_fixture(
|
||||
let mut request_builder = Request::builder(&request_config);
|
||||
request_builder
|
||||
.server_name(server_name)
|
||||
.server_cert_data(server_cert_data)
|
||||
.handshake_data(server_cert_data)
|
||||
.transcript(transcript);
|
||||
|
||||
let (request, _) = request_builder.build(&provider).unwrap();
|
||||
@@ -91,12 +91,12 @@ pub fn attestation_fixture(
|
||||
..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake
|
||||
}) = server_cert_data.binding
|
||||
else {
|
||||
panic!("expected v1.2 handshake data");
|
||||
panic!("expected v1.2 binding data");
|
||||
};
|
||||
|
||||
let mut provider = CryptoProvider::default();
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use tls_core::{
|
||||
anchors::{OwnedTrustAnchor, RootCertStore},
|
||||
verify::WebPkiVerifier,
|
||||
};
|
||||
use tlsn_core::hash::HashProvider;
|
||||
use tlsn_core::{hash::HashProvider, webpki::ServerCertVerifier};
|
||||
|
||||
use crate::signing::{SignatureVerifierProvider, SignerProvider};
|
||||
|
||||
@@ -28,7 +24,7 @@ pub struct CryptoProvider {
|
||||
/// This is used to verify the server's certificate chain.
|
||||
///
|
||||
/// The default verifier uses the Mozilla root certificates.
|
||||
pub cert: WebPkiVerifier,
|
||||
pub cert: ServerCertVerifier,
|
||||
/// Signer provider.
|
||||
///
|
||||
/// This is used for signing attestations.
|
||||
@@ -45,21 +41,9 @@ impl Default for CryptoProvider {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hash: Default::default(),
|
||||
cert: default_cert_verifier(),
|
||||
cert: ServerCertVerifier::mozilla(),
|
||||
signer: Default::default(),
|
||||
signature: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_cert_verifier() -> WebPkiVerifier {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
WebPkiVerifier::new(root_store, None)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ pub struct Request {
|
||||
|
||||
impl Request {
|
||||
/// Returns a new request builder.
|
||||
pub fn builder(config: &RequestConfig) -> RequestBuilder {
|
||||
pub fn builder(config: &RequestConfig) -> RequestBuilder<'_> {
|
||||
RequestBuilder::new(config)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use tlsn_core::{
|
||||
connection::{ServerCertData, ServerName},
|
||||
connection::{HandshakeData, ServerName},
|
||||
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
pub struct RequestBuilder<'a> {
|
||||
config: &'a RequestConfig,
|
||||
server_name: Option<ServerName>,
|
||||
server_cert_data: Option<ServerCertData>,
|
||||
handshake_data: Option<HandshakeData>,
|
||||
transcript: Option<Transcript>,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
transcript_commitment_secrets: Vec<TranscriptSecret>,
|
||||
@@ -25,7 +25,7 @@ impl<'a> RequestBuilder<'a> {
|
||||
Self {
|
||||
config,
|
||||
server_name: None,
|
||||
server_cert_data: None,
|
||||
handshake_data: None,
|
||||
transcript: None,
|
||||
transcript_commitments: Vec::new(),
|
||||
transcript_commitment_secrets: Vec::new(),
|
||||
@@ -38,9 +38,9 @@ impl<'a> RequestBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the server identity data.
|
||||
pub fn server_cert_data(&mut self, data: ServerCertData) -> &mut Self {
|
||||
self.server_cert_data = Some(data);
|
||||
/// Sets the handshake data.
|
||||
pub fn handshake_data(&mut self, data: HandshakeData) -> &mut Self {
|
||||
self.handshake_data = Some(data);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ impl<'a> RequestBuilder<'a> {
|
||||
let Self {
|
||||
config,
|
||||
server_name,
|
||||
server_cert_data,
|
||||
handshake_data: server_cert_data,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
transcript_commitment_secrets,
|
||||
|
||||
@@ -46,7 +46,7 @@ pub(crate) use impl_domain_separator;
|
||||
|
||||
impl_domain_separator!(tlsn_core::connection::ServerEphemKey);
|
||||
impl_domain_separator!(tlsn_core::connection::ConnectionInfo);
|
||||
impl_domain_separator!(tlsn_core::connection::HandshakeData);
|
||||
impl_domain_separator!(tlsn_core::connection::CertBinding);
|
||||
impl_domain_separator!(tlsn_core::transcript::TranscriptCommitment);
|
||||
impl_domain_separator!(tlsn_core::transcript::TranscriptSecret);
|
||||
impl_domain_separator!(tlsn_core::transcript::encoding::EncodingCommitment);
|
||||
|
||||
@@ -5,7 +5,7 @@ use tlsn_attestation::{
|
||||
signing::SignatureAlgId,
|
||||
};
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2},
|
||||
connection::{CertBinding, CertBindingV1_2},
|
||||
fixtures::{self, ConnectionFixture, encoder_secret},
|
||||
hash::Blake3,
|
||||
transcript::{
|
||||
@@ -36,10 +36,10 @@ fn test_api() {
|
||||
server_cert_data,
|
||||
} = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake.clone()
|
||||
}) = server_cert_data.binding.clone()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -72,7 +72,7 @@ fn test_api() {
|
||||
|
||||
request_builder
|
||||
.server_name(server_name.clone())
|
||||
.server_cert_data(server_cert_data)
|
||||
.handshake_data(server_cert_data)
|
||||
.transcript(transcript)
|
||||
.transcript_commitments(
|
||||
vec![TranscriptSecret::Encoding(encoding_tree)],
|
||||
|
||||
@@ -391,7 +391,7 @@ mod tests {
|
||||
memory::{binary::U8, correlated::Delta, Array},
|
||||
prelude::*,
|
||||
};
|
||||
use mpz_zk::{Prover, Verifier};
|
||||
use mpz_zk::{Prover, ProverConfig, Verifier, VerifierConfig};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use super::*;
|
||||
@@ -408,8 +408,8 @@ mod tests {
|
||||
|
||||
let gb = Garbler::new(cot_send, [0u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
let prover = Prover::new(ProverConfig::default(), rcot_recv);
|
||||
let verifier = Verifier::new(VerifierConfig::default(), delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
@@ -488,8 +488,8 @@ mod tests {
|
||||
|
||||
let gb = Garbler::new(cot_send, [0u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
let prover = Prover::new(ProverConfig::default(), rcot_recv);
|
||||
let verifier = Verifier::new(VerifierConfig::default(), delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
@@ -574,8 +574,8 @@ mod tests {
|
||||
|
||||
let gb = Garbler::new(cot_send, [1u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
let prover = Prover::new(ProverConfig::default(), rcot_recv);
|
||||
let verifier = Verifier::new(VerifierConfig::default(), delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
|
||||
@@ -40,7 +40,6 @@ enum PrfState {
|
||||
inner_partial: [u32; 8],
|
||||
a_output: DecodeFutureTyped<BitVec, [u8; 32]>,
|
||||
},
|
||||
FinishLastP,
|
||||
Done,
|
||||
}
|
||||
|
||||
@@ -137,16 +136,18 @@ impl PrfFunction {
|
||||
assign_inner_local(vm, p.inner_local, *inner_partial, &msg)?;
|
||||
|
||||
if *iter == self.iterations {
|
||||
self.state = PrfState::FinishLastP;
|
||||
self.state = PrfState::Done;
|
||||
} else {
|
||||
self.state = PrfState::ComputeA {
|
||||
iter: *iter + 1,
|
||||
inner_partial: *inner_partial,
|
||||
msg: output.to_vec(),
|
||||
}
|
||||
};
|
||||
};
|
||||
// We recurse, so that this PHash and the next AHash could
|
||||
// be computed in a single VM execute call.
|
||||
self.flush(vm)?;
|
||||
}
|
||||
}
|
||||
PrfState::FinishLastP => self.state = PrfState::Done,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,14 @@ thiserror = { workspace = true }
|
||||
tiny-keccak = { workspace = true, features = ["keccak"] }
|
||||
web-time = { workspace = true }
|
||||
webpki-roots = { workspace = true }
|
||||
rustls-webpki = { workspace = true, features = ["ring"] }
|
||||
rustls-pki-types = { workspace = true }
|
||||
itybity = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bincode = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
rstest = { workspace = true }
|
||||
tlsn-data-fixtures = { workspace = true }
|
||||
webpki-root-certs = { workspace = true }
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use rustls_pki_types as webpki_types;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tls_core::{
|
||||
msgs::{
|
||||
codec::Codec,
|
||||
enums::NamedGroup,
|
||||
handshake::{DigitallySignedStruct, ServerECDHParams},
|
||||
},
|
||||
verify::{ServerCertVerifier as _, WebPkiVerifier},
|
||||
};
|
||||
use web_time::{Duration, UNIX_EPOCH};
|
||||
use tls_core::msgs::{codec::Codec, enums::NamedGroup, handshake::ServerECDHParams};
|
||||
|
||||
use crate::webpki::{CertificateDer, ServerCertVerifier, ServerCertVerifierError};
|
||||
|
||||
/// TLS version.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
@@ -35,40 +30,82 @@ impl TryFrom<tls_core::msgs::enums::ProtocolVersion> for TlsVersion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Server's name, a.k.a. the DNS name.
|
||||
/// Server's name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ServerName(String);
|
||||
pub enum ServerName {
|
||||
/// DNS name.
|
||||
Dns(DnsName),
|
||||
}
|
||||
|
||||
impl ServerName {
|
||||
/// Creates a new server name.
|
||||
pub fn new(name: String) -> Self {
|
||||
Self(name)
|
||||
}
|
||||
|
||||
/// Returns the name as a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ServerName {
|
||||
fn from(name: &str) -> Self {
|
||||
Self(name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for ServerName {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
pub(crate) fn to_webpki(&self) -> webpki_types::ServerName<'static> {
|
||||
match self {
|
||||
ServerName::Dns(name) => webpki_types::ServerName::DnsName(
|
||||
webpki_types::DnsName::try_from(name.0.as_str())
|
||||
.expect("name was validated")
|
||||
.to_owned(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ServerName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ServerName::Dns(name) => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// DNS name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String")]
|
||||
pub struct DnsName(String);
|
||||
|
||||
impl DnsName {
|
||||
/// Returns the DNS name as a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DnsName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for DnsName {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned when a DNS name is invalid.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("invalid DNS name")]
|
||||
pub struct InvalidDnsNameError {}
|
||||
|
||||
impl TryFrom<&str> for DnsName {
|
||||
type Error = InvalidDnsNameError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
// Borrow validation from rustls
|
||||
match webpki_types::DnsName::try_from_str(value) {
|
||||
Ok(_) => Ok(DnsName(value.to_string())),
|
||||
Err(_) => Err(InvalidDnsNameError {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for DnsName {
|
||||
type Error = InvalidDnsNameError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Self::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of a public key.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -98,6 +135,25 @@ pub enum SignatureScheme {
|
||||
ED25519 = 0x0807,
|
||||
}
|
||||
|
||||
impl fmt::Display for SignatureScheme {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SignatureScheme::RSA_PKCS1_SHA1 => write!(f, "RSA_PKCS1_SHA1"),
|
||||
SignatureScheme::ECDSA_SHA1_Legacy => write!(f, "ECDSA_SHA1_Legacy"),
|
||||
SignatureScheme::RSA_PKCS1_SHA256 => write!(f, "RSA_PKCS1_SHA256"),
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256 => write!(f, "ECDSA_NISTP256_SHA256"),
|
||||
SignatureScheme::RSA_PKCS1_SHA384 => write!(f, "RSA_PKCS1_SHA384"),
|
||||
SignatureScheme::ECDSA_NISTP384_SHA384 => write!(f, "ECDSA_NISTP384_SHA384"),
|
||||
SignatureScheme::RSA_PKCS1_SHA512 => write!(f, "RSA_PKCS1_SHA512"),
|
||||
SignatureScheme::ECDSA_NISTP521_SHA512 => write!(f, "ECDSA_NISTP521_SHA512"),
|
||||
SignatureScheme::RSA_PSS_SHA256 => write!(f, "RSA_PSS_SHA256"),
|
||||
SignatureScheme::RSA_PSS_SHA384 => write!(f, "RSA_PSS_SHA384"),
|
||||
SignatureScheme::RSA_PSS_SHA512 => write!(f, "RSA_PSS_SHA512"),
|
||||
SignatureScheme::ED25519 => write!(f, "ED25519"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<tls_core::msgs::enums::SignatureScheme> for SignatureScheme {
|
||||
type Error = &'static str;
|
||||
|
||||
@@ -142,16 +198,6 @@ impl From<SignatureScheme> for tls_core::msgs::enums::SignatureScheme {
|
||||
}
|
||||
}
|
||||
|
||||
/// X.509 certificate, DER encoded.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Certificate(pub Vec<u8>);
|
||||
|
||||
impl From<tls_core::key::Certificate> for Certificate {
|
||||
fn from(cert: tls_core::key::Certificate) -> Self {
|
||||
Self(cert.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Server's signature of the key exchange parameters.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerSignature {
|
||||
@@ -220,9 +266,9 @@ pub struct TranscriptLength {
|
||||
pub received: u32,
|
||||
}
|
||||
|
||||
/// TLS 1.2 handshake data.
|
||||
/// TLS 1.2 certificate binding.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HandshakeDataV1_2 {
|
||||
pub struct CertBindingV1_2 {
|
||||
/// Client random.
|
||||
pub client_random: [u8; 32],
|
||||
/// Server random.
|
||||
@@ -231,13 +277,18 @@ pub struct HandshakeDataV1_2 {
|
||||
pub server_ephemeral_key: ServerEphemKey,
|
||||
}
|
||||
|
||||
/// TLS handshake data.
|
||||
/// TLS certificate binding.
|
||||
///
|
||||
/// This is the data that the server signs using its public key in the
|
||||
/// certificate it presents during the TLS handshake. This provides a binding
|
||||
/// between the server's identity and the ephemeral keys used to authenticate
|
||||
/// the TLS session.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum HandshakeData {
|
||||
/// TLS 1.2 handshake data.
|
||||
V1_2(HandshakeDataV1_2),
|
||||
pub enum CertBinding {
|
||||
/// TLS 1.2 certificate binding.
|
||||
V1_2(CertBindingV1_2),
|
||||
}
|
||||
|
||||
/// Verify data from the TLS handshake finished messages.
|
||||
@@ -249,19 +300,19 @@ pub struct VerifyData {
|
||||
pub server_finished: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Server certificate and handshake data.
|
||||
/// TLS handshake data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerCertData {
|
||||
/// Certificate chain.
|
||||
pub certs: Vec<Certificate>,
|
||||
/// Server signature of the key exchange parameters.
|
||||
pub struct HandshakeData {
|
||||
/// Server certificate chain.
|
||||
pub certs: Vec<CertificateDer>,
|
||||
/// Server certificate signature over the binding message.
|
||||
pub sig: ServerSignature,
|
||||
/// TLS handshake data.
|
||||
pub handshake: HandshakeData,
|
||||
/// Certificate binding.
|
||||
pub binding: CertBinding,
|
||||
}
|
||||
|
||||
impl ServerCertData {
|
||||
/// Verifies the server certificate data.
|
||||
impl HandshakeData {
|
||||
/// Verifies the handshake data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -271,53 +322,35 @@ impl ServerCertData {
|
||||
/// * `server_name` - The server name.
|
||||
pub fn verify(
|
||||
&self,
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
time: u64,
|
||||
server_ephemeral_key: &ServerEphemKey,
|
||||
server_name: &ServerName,
|
||||
) -> Result<(), CertificateVerificationError> {
|
||||
) -> Result<(), HandshakeVerificationError> {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random,
|
||||
server_random,
|
||||
server_ephemeral_key: expected_server_ephemeral_key,
|
||||
}) = &self.handshake
|
||||
}) = &self.binding
|
||||
else {
|
||||
unreachable!("only TLS 1.2 is implemented")
|
||||
};
|
||||
|
||||
if server_ephemeral_key != expected_server_ephemeral_key {
|
||||
return Err(CertificateVerificationError::InvalidServerEphemeralKey);
|
||||
return Err(HandshakeVerificationError::InvalidServerEphemeralKey);
|
||||
}
|
||||
|
||||
// Verify server name.
|
||||
let server_name = tls_core::dns::ServerName::try_from(server_name.as_ref())
|
||||
.map_err(|_| CertificateVerificationError::InvalidIdentity(server_name.clone()))?;
|
||||
|
||||
// Verify server certificate.
|
||||
let cert_chain = self
|
||||
let (end_entity, intermediates) = self
|
||||
.certs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|cert| tls_core::key::Certificate(cert.0))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (end_entity, intermediates) = cert_chain
|
||||
.split_first()
|
||||
.ok_or(CertificateVerificationError::MissingCerts)?;
|
||||
.ok_or(HandshakeVerificationError::MissingCerts)?;
|
||||
|
||||
// Verify the end entity cert is valid for the provided server name
|
||||
// and that it chains to at least one of the roots we trust.
|
||||
verifier
|
||||
.verify_server_cert(
|
||||
end_entity,
|
||||
intermediates,
|
||||
&server_name,
|
||||
&mut [].into_iter(),
|
||||
&[],
|
||||
UNIX_EPOCH + Duration::from_secs(time),
|
||||
)
|
||||
.map_err(|_| CertificateVerificationError::InvalidCert)?;
|
||||
.verify_server_cert(end_entity, intermediates, server_name, time)
|
||||
.map_err(HandshakeVerificationError::ServerCert)?;
|
||||
|
||||
// Verify the signature matches the certificate and key exchange parameters.
|
||||
let mut message = Vec::new();
|
||||
@@ -325,11 +358,31 @@ impl ServerCertData {
|
||||
message.extend_from_slice(server_random);
|
||||
message.extend_from_slice(&server_ephemeral_key.kx_params());
|
||||
|
||||
let dss = DigitallySignedStruct::new(self.sig.scheme.into(), self.sig.sig.clone());
|
||||
use webpki::ring as alg;
|
||||
let sig_alg = match self.sig.scheme {
|
||||
SignatureScheme::RSA_PKCS1_SHA256 => alg::RSA_PKCS1_2048_8192_SHA256,
|
||||
SignatureScheme::RSA_PKCS1_SHA384 => alg::RSA_PKCS1_2048_8192_SHA384,
|
||||
SignatureScheme::RSA_PKCS1_SHA512 => alg::RSA_PKCS1_2048_8192_SHA512,
|
||||
SignatureScheme::RSA_PSS_SHA256 => alg::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
|
||||
SignatureScheme::RSA_PSS_SHA384 => alg::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
|
||||
SignatureScheme::RSA_PSS_SHA512 => alg::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256 => alg::ECDSA_P256_SHA256,
|
||||
SignatureScheme::ECDSA_NISTP384_SHA384 => alg::ECDSA_P384_SHA384,
|
||||
SignatureScheme::ED25519 => alg::ED25519,
|
||||
scheme => {
|
||||
return Err(HandshakeVerificationError::UnsupportedSignatureScheme(
|
||||
scheme,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
verifier
|
||||
.verify_tls12_signature(&message, end_entity, &dss)
|
||||
.map_err(|_| CertificateVerificationError::InvalidServerSignature)?;
|
||||
let end_entity = webpki_types::CertificateDer::from(end_entity.0.as_slice());
|
||||
let end_entity = webpki::EndEntityCert::try_from(&end_entity)
|
||||
.map_err(|_| HandshakeVerificationError::InvalidEndEntityCertificate)?;
|
||||
|
||||
end_entity
|
||||
.verify_signature(sig_alg, &message, &self.sig.sig)
|
||||
.map_err(|_| HandshakeVerificationError::InvalidServerSignature)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -338,58 +391,51 @@ impl ServerCertData {
|
||||
/// Errors that can occur when verifying a certificate chain or signature.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum CertificateVerificationError {
|
||||
#[error("invalid server identity: {0}")]
|
||||
InvalidIdentity(ServerName),
|
||||
pub enum HandshakeVerificationError {
|
||||
#[error("invalid end entity certificate")]
|
||||
InvalidEndEntityCertificate,
|
||||
#[error("missing server certificates")]
|
||||
MissingCerts,
|
||||
#[error("invalid server certificate")]
|
||||
InvalidCert,
|
||||
#[error("invalid server signature")]
|
||||
InvalidServerSignature,
|
||||
#[error("invalid server ephemeral key")]
|
||||
InvalidServerEphemeralKey,
|
||||
#[error("server certificate verification failed: {0}")]
|
||||
ServerCert(ServerCertVerifierError),
|
||||
#[error("unsupported signature scheme: {0}")]
|
||||
UnsupportedSignatureScheme(SignatureScheme),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{fixtures::ConnectionFixture, transcript::Transcript};
|
||||
use crate::{fixtures::ConnectionFixture, transcript::Transcript, webpki::RootCertStore};
|
||||
|
||||
use hex::FromHex;
|
||||
use rstest::*;
|
||||
use tls_core::{
|
||||
anchors::{OwnedTrustAnchor, RootCertStore},
|
||||
verify::WebPkiVerifier,
|
||||
};
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
|
||||
#[fixture]
|
||||
#[once]
|
||||
fn verifier() -> WebPkiVerifier {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
fn verifier() -> ServerCertVerifier {
|
||||
let mut root_store = RootCertStore {
|
||||
roots: webpki_root_certs::TLS_SERVER_ROOT_CERTS
|
||||
.iter()
|
||||
.map(|c| CertificateDer(c.to_vec()))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// Add a cert which is no longer included in the Mozilla root store.
|
||||
let cert = tls_core::key::Certificate(
|
||||
root_store.roots.push(
|
||||
appliedzkp()
|
||||
.server_cert_data
|
||||
.certs
|
||||
.last()
|
||||
.expect("chain is valid")
|
||||
.0
|
||||
.clone(),
|
||||
);
|
||||
|
||||
root_store.add(&cert).unwrap();
|
||||
|
||||
WebPkiVerifier::new(root_store, None)
|
||||
ServerCertVerifier::new(&root_store).unwrap()
|
||||
}
|
||||
|
||||
fn tlsnotary() -> ConnectionFixture {
|
||||
@@ -405,7 +451,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_sucess_ca_implicit(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Remove the CA cert
|
||||
@@ -417,7 +463,7 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
@@ -428,7 +474,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_success_ca_explicit(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] data: ConnectionFixture,
|
||||
) {
|
||||
assert!(data
|
||||
@@ -437,7 +483,7 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
@@ -447,7 +493,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_bad_time(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] data: ConnectionFixture,
|
||||
) {
|
||||
// unix time when the cert chain was NOT valid
|
||||
@@ -457,12 +503,12 @@ mod tests {
|
||||
verifier,
|
||||
bad_time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -471,7 +517,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_no_interm_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Remove the CA cert
|
||||
@@ -483,12 +529,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -498,7 +544,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_no_interm_cert_with_ca_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Remove the intermediate cert
|
||||
@@ -508,12 +554,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -522,24 +568,24 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_bad_ee_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
let ee: &[u8] = include_bytes!("./fixtures/data/unknown/ee.der");
|
||||
|
||||
// Change the end entity cert
|
||||
data.server_cert_data.certs[0] = Certificate(ee.to_vec());
|
||||
data.server_cert_data.certs[0] = CertificateDer(ee.to_vec());
|
||||
|
||||
let err = data.server_cert_data.verify(
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -548,23 +594,23 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_sig_ke_params_fail_bad_client_random(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 { client_random, .. }) =
|
||||
&mut data.server_cert_data.handshake;
|
||||
let CertBinding::V1_2(CertBindingV1_2 { client_random, .. }) =
|
||||
&mut data.server_cert_data.binding;
|
||||
client_random[31] = client_random[31].wrapping_add(1);
|
||||
|
||||
let err = data.server_cert_data.verify(
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidServerSignature
|
||||
HandshakeVerificationError::InvalidServerSignature
|
||||
));
|
||||
}
|
||||
|
||||
@@ -573,7 +619,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_sig_ke_params_fail_bad_sig(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
data.server_cert_data.sig.sig[31] = data.server_cert_data.sig.sig[31].wrapping_add(1);
|
||||
@@ -582,12 +628,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidServerSignature
|
||||
HandshakeVerificationError::InvalidServerSignature
|
||||
));
|
||||
}
|
||||
|
||||
@@ -596,10 +642,10 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_check_dns_name_present_in_cert_fail_bad_host(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] data: ConnectionFixture,
|
||||
) {
|
||||
let bad_name = ServerName::from("badhost.com");
|
||||
let bad_name = ServerName::Dns(DnsName::try_from("badhost.com").unwrap());
|
||||
|
||||
let err = data.server_cert_data.verify(
|
||||
verifier,
|
||||
@@ -610,7 +656,7 @@ mod tests {
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -618,7 +664,7 @@ mod tests {
|
||||
#[rstest]
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_invalid_ephemeral_key(verifier: &WebPkiVerifier, #[case] data: ConnectionFixture) {
|
||||
fn test_invalid_ephemeral_key(verifier: &ServerCertVerifier, #[case] data: ConnectionFixture) {
|
||||
let wrong_ephemeral_key = ServerEphemKey {
|
||||
typ: KeyType::SECP256R1,
|
||||
key: Vec::<u8>::from_hex(include_bytes!("./fixtures/data/unknown/pubkey")).unwrap(),
|
||||
@@ -628,12 +674,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
&wrong_ephemeral_key,
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidServerEphemeralKey
|
||||
HandshakeVerificationError::InvalidServerEphemeralKey
|
||||
));
|
||||
}
|
||||
|
||||
@@ -642,7 +688,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_no_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Empty certs
|
||||
@@ -652,12 +698,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::MissingCerts
|
||||
HandshakeVerificationError::MissingCerts
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ use hex::FromHex;
|
||||
|
||||
use crate::{
|
||||
connection::{
|
||||
Certificate, ConnectionInfo, HandshakeData, HandshakeDataV1_2, KeyType, ServerCertData,
|
||||
CertBinding, CertBindingV1_2, ConnectionInfo, DnsName, HandshakeData, KeyType,
|
||||
ServerEphemKey, ServerName, ServerSignature, SignatureScheme, TlsVersion, TranscriptLength,
|
||||
},
|
||||
transcript::{
|
||||
encoding::{EncoderSecret, EncodingProvider},
|
||||
Transcript,
|
||||
},
|
||||
webpki::CertificateDer,
|
||||
};
|
||||
|
||||
/// A fixture containing various TLS connection data.
|
||||
@@ -23,24 +24,26 @@ use crate::{
|
||||
pub struct ConnectionFixture {
|
||||
pub server_name: ServerName,
|
||||
pub connection_info: ConnectionInfo,
|
||||
pub server_cert_data: ServerCertData,
|
||||
pub server_cert_data: HandshakeData,
|
||||
}
|
||||
|
||||
impl ConnectionFixture {
|
||||
/// Returns a connection fixture for tlsnotary.org.
|
||||
pub fn tlsnotary(transcript_length: TranscriptLength) -> Self {
|
||||
ConnectionFixture {
|
||||
server_name: ServerName::new("tlsnotary.org".to_string()),
|
||||
server_name: ServerName::Dns(DnsName::try_from("tlsnotary.org").unwrap()),
|
||||
connection_info: ConnectionInfo {
|
||||
time: 1671637529,
|
||||
version: TlsVersion::V1_2,
|
||||
transcript_length,
|
||||
},
|
||||
server_cert_data: ServerCertData {
|
||||
server_cert_data: HandshakeData {
|
||||
certs: vec![
|
||||
Certificate(include_bytes!("fixtures/data/tlsnotary.org/ee.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/tlsnotary.org/inter.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/tlsnotary.org/ca.der").to_vec()),
|
||||
CertificateDer(include_bytes!("fixtures/data/tlsnotary.org/ee.der").to_vec()),
|
||||
CertificateDer(
|
||||
include_bytes!("fixtures/data/tlsnotary.org/inter.der").to_vec(),
|
||||
),
|
||||
CertificateDer(include_bytes!("fixtures/data/tlsnotary.org/ca.der").to_vec()),
|
||||
],
|
||||
sig: ServerSignature {
|
||||
scheme: SignatureScheme::RSA_PKCS1_SHA256,
|
||||
@@ -49,7 +52,7 @@ impl ConnectionFixture {
|
||||
))
|
||||
.unwrap(),
|
||||
},
|
||||
handshake: HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
binding: CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random: <[u8; 32]>::from_hex(include_bytes!(
|
||||
"fixtures/data/tlsnotary.org/client_random"
|
||||
))
|
||||
@@ -73,17 +76,19 @@ impl ConnectionFixture {
|
||||
/// Returns a connection fixture for appliedzkp.org.
|
||||
pub fn appliedzkp(transcript_length: TranscriptLength) -> Self {
|
||||
ConnectionFixture {
|
||||
server_name: ServerName::new("appliedzkp.org".to_string()),
|
||||
server_name: ServerName::Dns(DnsName::try_from("appliedzkp.org").unwrap()),
|
||||
connection_info: ConnectionInfo {
|
||||
time: 1671637529,
|
||||
version: TlsVersion::V1_2,
|
||||
transcript_length,
|
||||
},
|
||||
server_cert_data: ServerCertData {
|
||||
server_cert_data: HandshakeData {
|
||||
certs: vec![
|
||||
Certificate(include_bytes!("fixtures/data/appliedzkp.org/ee.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/appliedzkp.org/inter.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/appliedzkp.org/ca.der").to_vec()),
|
||||
CertificateDer(include_bytes!("fixtures/data/appliedzkp.org/ee.der").to_vec()),
|
||||
CertificateDer(
|
||||
include_bytes!("fixtures/data/appliedzkp.org/inter.der").to_vec(),
|
||||
),
|
||||
CertificateDer(include_bytes!("fixtures/data/appliedzkp.org/ca.der").to_vec()),
|
||||
],
|
||||
sig: ServerSignature {
|
||||
scheme: SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
@@ -92,7 +97,7 @@ impl ConnectionFixture {
|
||||
))
|
||||
.unwrap(),
|
||||
},
|
||||
handshake: HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
binding: CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random: <[u8; 32]>::from_hex(include_bytes!(
|
||||
"fixtures/data/appliedzkp.org/client_random"
|
||||
))
|
||||
@@ -115,10 +120,10 @@ impl ConnectionFixture {
|
||||
|
||||
/// Returns the server_ephemeral_key fixture.
|
||||
pub fn server_ephemeral_key(&self) -> &ServerEphemKey {
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = &self.server_cert_data.handshake;
|
||||
}) = &self.server_cert_data.binding;
|
||||
server_ephemeral_key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@ pub mod fixtures;
|
||||
pub mod hash;
|
||||
pub mod merkle;
|
||||
pub mod transcript;
|
||||
pub mod webpki;
|
||||
|
||||
use rangeset::ToRangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertData, ServerName},
|
||||
connection::{HandshakeData, ServerName},
|
||||
transcript::{
|
||||
Direction, Idx, PartialTranscript, Transcript, TranscriptCommitConfig,
|
||||
TranscriptCommitRequest, TranscriptCommitment, TranscriptSecret,
|
||||
@@ -23,7 +24,7 @@ use crate::{
|
||||
};
|
||||
|
||||
/// Configuration to prove information to the verifier.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProveConfig {
|
||||
server_identity: bool,
|
||||
transcript: Option<PartialTranscript>,
|
||||
@@ -32,7 +33,7 @@ pub struct ProveConfig {
|
||||
|
||||
impl ProveConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder(transcript: &Transcript) -> ProveConfigBuilder {
|
||||
pub fn builder(transcript: &Transcript) -> ProveConfigBuilder<'_> {
|
||||
ProveConfigBuilder::new(transcript)
|
||||
}
|
||||
|
||||
@@ -162,7 +163,7 @@ enum ProveConfigBuilderErrorRepr {
|
||||
}
|
||||
|
||||
/// Configuration to verify information from the prover.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct VerifyConfig {}
|
||||
|
||||
impl VerifyConfig {
|
||||
@@ -200,8 +201,8 @@ enum VerifyConfigBuilderErrorRepr {}
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ProvePayload {
|
||||
/// Server identity data.
|
||||
pub server_identity: Option<(ServerName, ServerCertData)>,
|
||||
/// Handshake data.
|
||||
pub handshake: Option<(ServerName, HandshakeData)>,
|
||||
/// Transcript data.
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
/// Transcript commitment configuration.
|
||||
@@ -209,6 +210,7 @@ pub struct ProvePayload {
|
||||
}
|
||||
|
||||
/// Prover output.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ProverOutput {
|
||||
/// Transcript commitments.
|
||||
pub transcript_commitments: Vec<TranscriptCommitment>,
|
||||
@@ -219,6 +221,7 @@ pub struct ProverOutput {
|
||||
opaque_debug::implement!(ProverOutput);
|
||||
|
||||
/// Verifier output.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifierOutput {
|
||||
/// Server identity.
|
||||
pub server_name: Option<ServerName>,
|
||||
|
||||
@@ -66,7 +66,7 @@ pub enum TranscriptSecret {
|
||||
}
|
||||
|
||||
/// Configuration for transcript commitments.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TranscriptCommitConfig {
|
||||
encoding_hash_alg: HashAlgId,
|
||||
has_encoding: bool,
|
||||
@@ -76,7 +76,7 @@ pub struct TranscriptCommitConfig {
|
||||
|
||||
impl TranscriptCommitConfig {
|
||||
/// Creates a new commit config builder.
|
||||
pub fn builder(transcript: &Transcript) -> TranscriptCommitConfigBuilder {
|
||||
pub fn builder(transcript: &Transcript) -> TranscriptCommitConfigBuilder<'_> {
|
||||
TranscriptCommitConfigBuilder::new(transcript)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
use crate::{
|
||||
connection::{
|
||||
Certificate, HandshakeData, HandshakeDataV1_2, ServerEphemKey, ServerSignature, TlsVersion,
|
||||
VerifyData,
|
||||
CertBinding, CertBindingV1_2, ServerEphemKey, ServerSignature, TlsVersion, VerifyData,
|
||||
},
|
||||
transcript::{Direction, Transcript},
|
||||
webpki::CertificateDer,
|
||||
};
|
||||
use tls_core::msgs::{
|
||||
alert::AlertMessagePayload,
|
||||
@@ -19,9 +19,9 @@ use tls_core::msgs::{
|
||||
pub struct TlsTranscript {
|
||||
time: u64,
|
||||
version: TlsVersion,
|
||||
server_cert_chain: Option<Vec<Certificate>>,
|
||||
server_cert_chain: Option<Vec<CertificateDer>>,
|
||||
server_signature: Option<ServerSignature>,
|
||||
handshake_data: HandshakeData,
|
||||
certificate_binding: CertBinding,
|
||||
sent: Vec<Record>,
|
||||
recv: Vec<Record>,
|
||||
}
|
||||
@@ -32,9 +32,9 @@ impl TlsTranscript {
|
||||
pub fn new(
|
||||
time: u64,
|
||||
version: TlsVersion,
|
||||
server_cert_chain: Option<Vec<Certificate>>,
|
||||
server_cert_chain: Option<Vec<CertificateDer>>,
|
||||
server_signature: Option<ServerSignature>,
|
||||
handshake_data: HandshakeData,
|
||||
certificate_binding: CertBinding,
|
||||
verify_data: VerifyData,
|
||||
sent: Vec<Record>,
|
||||
recv: Vec<Record>,
|
||||
@@ -198,7 +198,7 @@ impl TlsTranscript {
|
||||
version,
|
||||
server_cert_chain,
|
||||
server_signature,
|
||||
handshake_data,
|
||||
certificate_binding,
|
||||
sent,
|
||||
recv,
|
||||
})
|
||||
@@ -215,7 +215,7 @@ impl TlsTranscript {
|
||||
}
|
||||
|
||||
/// Returns the server certificate chain.
|
||||
pub fn server_cert_chain(&self) -> Option<&[Certificate]> {
|
||||
pub fn server_cert_chain(&self) -> Option<&[CertificateDer]> {
|
||||
self.server_cert_chain.as_deref()
|
||||
}
|
||||
|
||||
@@ -226,17 +226,17 @@ impl TlsTranscript {
|
||||
|
||||
/// Returns the server ephemeral key used in the TLS handshake.
|
||||
pub fn server_ephemeral_key(&self) -> &ServerEphemKey {
|
||||
match &self.handshake_data {
|
||||
HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
match &self.certificate_binding {
|
||||
CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) => server_ephemeral_key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the handshake data.
|
||||
pub fn handshake_data(&self) -> &HandshakeData {
|
||||
&self.handshake_data
|
||||
/// Returns the certificate binding data.
|
||||
pub fn certificate_binding(&self) -> &CertBinding {
|
||||
&self.certificate_binding
|
||||
}
|
||||
|
||||
/// Returns the sent records.
|
||||
|
||||
168
crates/core/src/webpki.rs
Normal file
168
crates/core/src/webpki.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
//! Web PKI types.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use rustls_pki_types::{self as webpki_types, pem::PemObject};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::connection::ServerName;
|
||||
|
||||
/// X.509 certificate, DER encoded.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CertificateDer(pub Vec<u8>);
|
||||
|
||||
impl CertificateDer {
|
||||
/// Creates a DER-encoded certificate from a PEM-encoded certificate.
|
||||
pub fn from_pem_slice(pem: &[u8]) -> Result<Self, PemError> {
|
||||
let der = webpki_types::CertificateDer::from_pem_slice(pem).map_err(|_| PemError {})?;
|
||||
|
||||
Ok(Self(der.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Private key, DER encoded.
|
||||
#[derive(Debug, Clone, zeroize::ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
pub struct PrivateKeyDer(pub Vec<u8>);
|
||||
|
||||
impl PrivateKeyDer {
|
||||
/// Creates a DER-encoded private key from a PEM-encoded private key.
|
||||
pub fn from_pem_slice(pem: &[u8]) -> Result<Self, PemError> {
|
||||
let der = webpki_types::PrivateKeyDer::from_pem_slice(pem).map_err(|_| PemError {})?;
|
||||
|
||||
Ok(Self(der.secret_der().to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
/// PEM parsing error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("failed to parse PEM object")]
|
||||
pub struct PemError {}
|
||||
|
||||
/// Root certificate store.
|
||||
///
|
||||
/// This stores root certificates which are used to verify end-entity
|
||||
/// certificates presented by a TLS server.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RootCertStore {
|
||||
/// Unvalidated DER-encoded X.509 root certificates.
|
||||
pub roots: Vec<CertificateDer>,
|
||||
}
|
||||
|
||||
impl RootCertStore {
|
||||
/// Creates an empty root certificate store.
|
||||
pub fn empty() -> Self {
|
||||
Self { roots: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Server certificate verifier.
|
||||
#[derive(Debug)]
|
||||
pub struct ServerCertVerifier {
|
||||
roots: Vec<webpki_types::TrustAnchor<'static>>,
|
||||
}
|
||||
|
||||
impl ServerCertVerifier {
|
||||
/// Creates a new server certificate verifier.
|
||||
pub fn new(roots: &RootCertStore) -> Result<Self, ServerCertVerifierError> {
|
||||
let roots = roots
|
||||
.roots
|
||||
.iter()
|
||||
.map(|cert| {
|
||||
webpki::anchor_from_trusted_cert(&webpki_types::CertificateDer::from(
|
||||
cert.0.as_slice(),
|
||||
))
|
||||
.map(|anchor| anchor.to_owned())
|
||||
.map_err(|err| ServerCertVerifierError::InvalidRootCertificate {
|
||||
cert: cert.clone(),
|
||||
reason: err.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self { roots })
|
||||
}
|
||||
|
||||
/// Creates a new server certificate verifier with Mozilla root
|
||||
/// certificates.
|
||||
pub fn mozilla() -> Self {
|
||||
Self {
|
||||
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies the server certificate was valid at the given time of
|
||||
/// presentation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `end_entity` - End-entity certificate to verify.
|
||||
/// * `intermediates` - Intermediate certificates to a trust anchor.
|
||||
/// * `server_name` - Server DNS name.
|
||||
/// * `time` - Unix time the certificate was presented.
|
||||
pub fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer,
|
||||
intermediates: &[CertificateDer],
|
||||
server_name: &ServerName,
|
||||
time: u64,
|
||||
) -> Result<(), ServerCertVerifierError> {
|
||||
let cert = webpki_types::CertificateDer::from(end_entity.0.as_slice());
|
||||
let cert = webpki::EndEntityCert::try_from(&cert).map_err(|e| {
|
||||
ServerCertVerifierError::InvalidEndEntityCertificate {
|
||||
cert: end_entity.clone(),
|
||||
reason: e.to_string(),
|
||||
}
|
||||
})?;
|
||||
let intermediates = intermediates
|
||||
.iter()
|
||||
.map(|c| webpki_types::CertificateDer::from(c.0.as_slice()))
|
||||
.collect::<Vec<_>>();
|
||||
let server_name = server_name.to_webpki();
|
||||
let time = webpki_types::UnixTime::since_unix_epoch(Duration::from_secs(time));
|
||||
|
||||
cert.verify_for_usage(
|
||||
webpki::ALL_VERIFICATION_ALGS,
|
||||
&self.roots,
|
||||
&intermediates,
|
||||
time,
|
||||
webpki::KeyUsage::server_auth(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|_| ServerCertVerifierError::InvalidPath)?;
|
||||
|
||||
cert.verify_is_valid_for_subject_name(&server_name)
|
||||
.map_err(|_| ServerCertVerifierError::InvalidServerName)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`ServerCertVerifier`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("server certificate verification failed: {0}")]
|
||||
pub enum ServerCertVerifierError {
|
||||
/// Root certificate store contains invalid certificate.
|
||||
#[error("root certificate store contains invalid certificate: {reason}")]
|
||||
InvalidRootCertificate {
|
||||
/// Invalid certificate.
|
||||
cert: CertificateDer,
|
||||
/// Reason for invalidity.
|
||||
reason: String,
|
||||
},
|
||||
/// End-entity certificate is invalid.
|
||||
#[error("end-entity certificate is invalid: {reason}")]
|
||||
InvalidEndEntityCertificate {
|
||||
/// Invalid certificate.
|
||||
cert: CertificateDer,
|
||||
/// Reason for invalidity.
|
||||
reason: String,
|
||||
},
|
||||
/// Failed to verify certificate path to provided trust anchors.
|
||||
#[error("failed to verify certificate path to provided trust anchors")]
|
||||
InvalidPath,
|
||||
/// Failed to verify certificate is valid for provided server name.
|
||||
#[error("failed to verify certificate is valid for provided server name")]
|
||||
InvalidServerName,
|
||||
}
|
||||
@@ -8,10 +8,8 @@ version = "0.0.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn = { workspace = true }
|
||||
tlsn-formats = { workspace = true }
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tls-server-fixture = { workspace = true }
|
||||
tlsn-server-fixture = { workspace = true }
|
||||
tlsn-server-fixture-certs = { workspace = true }
|
||||
@@ -21,6 +19,8 @@ bincode = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
dotenv = { version = "0.15.0" }
|
||||
ethers = "2.0.14"
|
||||
eyre = "0.6.12"
|
||||
futures = { workspace = true }
|
||||
http-body-util = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
@@ -28,6 +28,7 @@ hyper = { workspace = true, features = ["client", "http1"] }
|
||||
hyper-util = { workspace = true, features = ["full"] }
|
||||
k256 = { workspace = true, features = ["ecdsa"] }
|
||||
serde_json = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tokio = { workspace = true, features = [
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
@@ -39,7 +40,12 @@ tokio = { workspace = true, features = [
|
||||
tokio-util = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
rand.workspace = true
|
||||
|
||||
[[example]]
|
||||
name = "interactive"
|
||||
path = "interactive/interactive.rs"
|
||||
|
||||
[[example]]
|
||||
name = "eas"
|
||||
path = "eas/eas.rs"
|
||||
|
||||
36
crates/examples/eas/README.md
Normal file
36
crates/examples/eas/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## Simple Interactive Verifier + Ethereum Attestation Service
|
||||
|
||||
This example is basically the `interactive` example adding the generation of Offline [EAS](https://attest.org/) Attestation and timestamping it. Please first understand the interactive example and [EAS](https://attest.org/) before getting into this example.
|
||||
|
||||
This demo:
|
||||
|
||||
- Runs a simple interactive session between a Prover and a Verifier
|
||||
- Gets the redacted string of the response and generates an offline signed attestation of it. The attestation generated uses this [schema](https://sepolia.easscan.org/schema/view/0x938b5d03b0057688eef86d8101946311c4aaa740ffc39cef9bbfb6ce572a7198). It is stored in the `eas_attestation.json` local file.
|
||||
- Timestamps the attestation (that is mainly calling the `timestamp` method in the EAS contract), the Tx of the transaction will be shown.
|
||||
- You can verify the generated attestation with the provided `check-eas-attestation-js` example or using https://sepolia.easscan.org/tools.
|
||||
|
||||
To run this demo you need:
|
||||
|
||||
- Sepolia RPC provider URL
|
||||
- [Ethereum address secret key](https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/) with some ether.
|
||||
|
||||
This example fetches data from a local test server. To start this server, run:
|
||||
```shell
|
||||
PORT=4000 cargo run --bin tlsn-server-fixture
|
||||
```
|
||||
Next, run the interactive example with:
|
||||
```shell
|
||||
EAS_SK=<secret_key> RPC_URL=<rpc_url> SERVER_PORT=4000 cargo run --release --example eas
|
||||
```
|
||||
To view more detailed debug information, use the following command:
|
||||
```shell
|
||||
EAS_SK=<secret_key> RPC_URL=<rpc_url> RUST_LOG=debug,yamux=info,uid_mux=info SERVER_PORT=4000 cargo run --release --example eas
|
||||
```
|
||||
|
||||
This will generate a signed EAS attestation, you can verify it by using the eas-sdk with:
|
||||
```shell
|
||||
cd check-eas-attestation-js
|
||||
npm i
|
||||
RPC_URL=<rpc_url> npm run start
|
||||
```
|
||||
|
||||
1
crates/examples/eas/check-eas-attestation-js/.gitignore
vendored
Normal file
1
crates/examples/eas/check-eas-attestation-js/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
24
crates/examples/eas/check-eas-attestation-js/index.js
Normal file
24
crates/examples/eas/check-eas-attestation-js/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ethers } from "ethers";
|
||||
import { EAS, Offchain } from "@ethereum-attestation-service/eas-sdk";
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
async function main() {
|
||||
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
|
||||
const EAS_CONTRACT_ADDRESS = "0xC2679fBD37d54388Ce493F1DB75320D236e1815e";
|
||||
|
||||
const eas = new EAS(EAS_CONTRACT_ADDRESS);
|
||||
eas.connect(provider);
|
||||
|
||||
const serializedAttestation = readFileSync('../eas_attestation.json', 'utf8');
|
||||
const attestation = JSON.parse(serializedAttestation);
|
||||
|
||||
const offchain = await eas.getOffchain();
|
||||
try {
|
||||
const signatureOk = await offchain.verifyOffchainAttestationSignature(attestation.signer, attestation.sig);
|
||||
console.log("Signature verification:", signatureOk)
|
||||
} catch (err) {
|
||||
console.error("Invalid or malformed offchain attestation:", err);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
3491
crates/examples/eas/check-eas-attestation-js/package-lock.json
generated
Normal file
3491
crates/examples/eas/check-eas-attestation-js/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
crates/examples/eas/check-eas-attestation-js/package.json
Normal file
14
crates/examples/eas/check-eas-attestation-js/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "check-eas-attestation",
|
||||
"version": "1.0.0",
|
||||
"description": "Verify EAS offline attestations with Node.js",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethers": "^6.0.0",
|
||||
"@ethereum-attestation-service/eas-sdk": "^1.0.0"
|
||||
}
|
||||
}
|
||||
1
crates/examples/eas/eas.abi
Normal file
1
crates/examples/eas/eas.abi
Normal file
File diff suppressed because one or more lines are too long
1
crates/examples/eas/eas.abi.url
Normal file
1
crates/examples/eas/eas.abi.url
Normal file
@@ -0,0 +1 @@
|
||||
https://sepolia.etherscan.io/address/0xC2679fBD37d54388Ce493F1DB75320D236e1815e#code
|
||||
579
crates/examples/eas/eas.rs
Normal file
579
crates/examples/eas/eas.rs
Normal file
@@ -0,0 +1,579 @@
|
||||
use std::{
|
||||
env,
|
||||
net::{IpAddr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use ethers::{
|
||||
abi::Address,
|
||||
signers::{LocalWallet, Signer},
|
||||
types::H256,
|
||||
};
|
||||
use http_body_util::Empty;
|
||||
use hyper::{body::Bytes, Request, StatusCode, Uri};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
||||
use tracing::instrument;
|
||||
|
||||
use tls_server_fixture::CA_CERT_DER;
|
||||
use tlsn::{
|
||||
config::{CertificateDer, ProtocolConfig, ProtocolConfigValidator, RootCertStore},
|
||||
connection::ServerName,
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
transcript::PartialTranscript,
|
||||
verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig},
|
||||
};
|
||||
|
||||
use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
|
||||
use tlsn_server_fixture_certs::SERVER_DOMAIN;
|
||||
|
||||
const SECRET: &str = "random_auth_token";
|
||||
|
||||
// Maximum number of bytes that can be sent from prover to server.
|
||||
const MAX_SENT_DATA: usize = 1 << 12;
|
||||
// Maximum number of bytes that can be received by prover from server.
|
||||
const MAX_RECV_DATA: usize = 1 << 14;
|
||||
|
||||
// The contract address of the EAS contract on Sepolia.
|
||||
const EAS_ADDRESS: &str = "0xC2679fBD37d54388Ce493F1DB75320D236e1815e";
|
||||
// The chain ID of the Sepolia network.
|
||||
const EAS_CHAINID: u64 = 11155111;
|
||||
// The schema for the EAS attestation.
|
||||
const EAS_SCHEMA: &str = "0x938b5d03b0057688eef86d8101946311c4aaa740ffc39cef9bbfb6ce572a7198";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let rpc_url = env::var("RPC_URL").expect("RPC_URL environment variable must be set");
|
||||
let sk = env::var("EAS_SK").expect("EAS_SK environment variable must be set");
|
||||
let server_host: String = env::var("SERVER_HOST").unwrap_or("127.0.0.1".into());
|
||||
let server_port: u16 = env::var("SERVER_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_FIXTURE_PORT);
|
||||
|
||||
// We use SERVER_DOMAIN here to make sure it matches the domain in the test
|
||||
// server's certificate.
|
||||
let uri = format!("https://{SERVER_DOMAIN}:{server_port}/formats/html");
|
||||
let server_ip: IpAddr = server_host.parse().expect("Invalid IP address");
|
||||
let server_addr = SocketAddr::from((server_ip, server_port));
|
||||
|
||||
// Connect prover and verifier.
|
||||
let (prover_socket, verifier_socket) = tokio::io::duplex(1 << 23);
|
||||
let prover = prover(prover_socket, &server_addr, &uri);
|
||||
let verifier = verifier(verifier_socket);
|
||||
let (_, transcript) = tokio::join!(prover, verifier);
|
||||
|
||||
println!("Successfully verified {}", &uri);
|
||||
println!(
|
||||
"Verified sent data:\n{}",
|
||||
bytes_to_redacted_string(transcript.sent_unsafe())
|
||||
);
|
||||
println!(
|
||||
"Verified received data:\n{}",
|
||||
bytes_to_redacted_string(transcript.received_unsafe())
|
||||
);
|
||||
|
||||
// Generate EAS attestation
|
||||
|
||||
let signer = sk
|
||||
.parse::<LocalWallet>()
|
||||
.expect("Failed to parse LocalWallet")
|
||||
.with_chain_id(EAS_CHAINID);
|
||||
|
||||
println!("Using EAS signer: {}", signer.address());
|
||||
|
||||
let redacted_string = bytes_to_redacted_string(transcript.received_unsafe());
|
||||
let data = ethers::core::abi::encode(&[ethers::core::abi::Token::String(redacted_string)]);
|
||||
let verifying_contract = Address::from_str(EAS_ADDRESS).expect("Failed to parse EAS address");
|
||||
|
||||
let attestation = eas::OfflineAttestationBuilder {
|
||||
schema: H256::from_str(EAS_SCHEMA).unwrap(),
|
||||
recipient: Address::zero(),
|
||||
time: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
expiration_time: Some(0),
|
||||
ref_uid: None,
|
||||
revocable: true,
|
||||
nonce: Some(0),
|
||||
data,
|
||||
}
|
||||
.generate(&signer, verifying_contract)
|
||||
.await
|
||||
.expect("Failed to generate attestation");
|
||||
|
||||
std::fs::write(
|
||||
"eas_attestation.json",
|
||||
serde_json::to_string_pretty(&attestation).unwrap(),
|
||||
)
|
||||
.expect("Failed to write attestation to file");
|
||||
|
||||
println!("EAS Attestation generated and saved to eas_attestation.json");
|
||||
|
||||
// Timestamp the attestation using the EAS contract
|
||||
|
||||
_ = eas::timestamp_attestation(&signer, &rpc_url, &attestation)
|
||||
.await
|
||||
.expect("Failed to timestamp attestation");
|
||||
}
|
||||
|
||||
#[instrument(skip(verifier_socket))]
|
||||
async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
verifier_socket: T,
|
||||
server_addr: &SocketAddr,
|
||||
uri: &str,
|
||||
) {
|
||||
let uri = uri.parse::<Uri>().unwrap();
|
||||
assert_eq!(uri.scheme().unwrap().as_str(), "https");
|
||||
let server_domain = uri.authority().unwrap().host();
|
||||
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
});
|
||||
let tls_config = tls_config_builder.build().unwrap();
|
||||
|
||||
// Set up protocol configuration for prover.
|
||||
let mut prover_config_builder = ProverConfig::builder();
|
||||
prover_config_builder
|
||||
.server_name(ServerName::Dns(server_domain.try_into().unwrap()))
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let prover_config = prover_config_builder.build().unwrap();
|
||||
|
||||
// Create prover and connect to verifier.
|
||||
//
|
||||
// Perform the setup phase with the verifier.
|
||||
let prover = Prover::new(prover_config)
|
||||
.setup(verifier_socket.compat())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Connect to TLS Server.
|
||||
let tls_client_socket = tokio::net::TcpStream::connect(server_addr).await.unwrap();
|
||||
|
||||
// Pass server connection into the prover.
|
||||
let (mpc_tls_connection, prover_fut) =
|
||||
prover.connect(tls_client_socket.compat()).await.unwrap();
|
||||
|
||||
// Wrap the connection in a TokioIo compatibility layer to use it with hyper.
|
||||
let mpc_tls_connection = TokioIo::new(mpc_tls_connection.compat());
|
||||
|
||||
// Spawn the Prover to run in the background.
|
||||
let prover_task = tokio::spawn(prover_fut);
|
||||
|
||||
// MPC-TLS Handshake.
|
||||
let (mut request_sender, connection) =
|
||||
hyper::client::conn::http1::handshake(mpc_tls_connection)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Spawn the connection to run in the background.
|
||||
tokio::spawn(connection);
|
||||
|
||||
// MPC-TLS: Send Request and wait for Response.
|
||||
let request = Request::builder()
|
||||
.uri(uri.clone())
|
||||
.header("Host", server_domain)
|
||||
.header("Connection", "close")
|
||||
.header("Secret", SECRET)
|
||||
.method("GET")
|
||||
.body(Empty::<Bytes>::new())
|
||||
.unwrap();
|
||||
let response = request_sender.send_request(request).await.unwrap();
|
||||
|
||||
assert!(response.status() == StatusCode::OK);
|
||||
|
||||
// Create proof for the Verifier.
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
// Reveal the DNS name.
|
||||
builder.server_identity();
|
||||
|
||||
// Find the secret in the request.
|
||||
let pos = prover
|
||||
.transcript()
|
||||
.sent()
|
||||
.windows(SECRET.len())
|
||||
.position(|w| w == SECRET.as_bytes())
|
||||
.expect("the secret should be in the sent data");
|
||||
|
||||
// Reveal everything except for the secret.
|
||||
builder.reveal_sent(&(0..pos)).unwrap();
|
||||
builder
|
||||
.reveal_sent(&(pos + SECRET.len()..prover.transcript().sent().len()))
|
||||
.unwrap();
|
||||
|
||||
// Find the substring "Dick".
|
||||
let pos = prover
|
||||
.transcript()
|
||||
.received()
|
||||
.windows(4)
|
||||
.position(|w| w == b"Dick")
|
||||
.expect("the substring 'Dick' should be in the received data");
|
||||
|
||||
// Reveal everything except for the substring.
|
||||
builder.reveal_recv(&(0..pos)).unwrap();
|
||||
builder
|
||||
.reveal_recv(&(pos + 4..prover.transcript().received().len()))
|
||||
.unwrap();
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
prover.prove(&config).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
}
|
||||
|
||||
#[instrument(skip(socket))]
|
||||
async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
socket: T,
|
||||
) -> PartialTranscript {
|
||||
// Set up Verifier.
|
||||
let config_validator = ProtocolConfigValidator::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
let verifier_config = VerifierConfig::builder()
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.protocol_config_validator(config_validator)
|
||||
.build()
|
||||
.unwrap();
|
||||
let verifier = Verifier::new(verifier_config);
|
||||
|
||||
// Receive authenticated data.
|
||||
let VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
..
|
||||
} = verifier
|
||||
.verify(socket.compat(), &VerifyConfig::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let server_name = server_name.expect("prover should have revealed server name");
|
||||
let transcript = transcript.expect("prover should have revealed transcript data");
|
||||
|
||||
// Check sent data.
|
||||
let sent = transcript.sent_unsafe().to_vec();
|
||||
let sent_data = String::from_utf8(sent.clone()).expect("Verifier expected sent data");
|
||||
sent_data
|
||||
.find(SERVER_DOMAIN)
|
||||
.unwrap_or_else(|| panic!("Verification failed: Expected host {SERVER_DOMAIN}"));
|
||||
|
||||
// Check received data.
|
||||
let received = transcript.received_unsafe().to_vec();
|
||||
let response = String::from_utf8(received.clone()).expect("Verifier expected received data");
|
||||
response
|
||||
.find("Herman Melville")
|
||||
.unwrap_or_else(|| panic!("Expected valid data from {SERVER_DOMAIN}"));
|
||||
|
||||
// Check Session info: server name.
|
||||
let ServerName::Dns(server_name) = server_name;
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
|
||||
transcript
|
||||
}
|
||||
|
||||
/// Render redacted bytes as `🙈`.
|
||||
fn bytes_to_redacted_string(bytes: &[u8]) -> String {
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.unwrap()
|
||||
.replace('\0', "🙈")
|
||||
}
|
||||
|
||||
mod eas {
|
||||
|
||||
use std::{collections::BTreeMap, str::FromStr};
|
||||
|
||||
use super::*;
|
||||
use ethers::{
|
||||
abi::{Abi, Token},
|
||||
middleware::{
|
||||
gas_escalator::{Frequency, GeometricGasPrice},
|
||||
GasEscalatorMiddleware, MiddlewareBuilder, NonceManagerMiddleware, SignerMiddleware,
|
||||
},
|
||||
providers::{Http, Provider},
|
||||
signers::{Signer, Wallet},
|
||||
types::{
|
||||
transaction::eip712::{EIP712Domain, Eip712DomainType, TypedData},
|
||||
Address, Signature, H256, U256,
|
||||
},
|
||||
utils::{hex, keccak256, to_checksum},
|
||||
};
|
||||
use k256::ecdsa::SigningKey;
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
// A signed offline attestation, which includes the signature and the signer's
|
||||
// address.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SignedOfflineAttestation {
|
||||
/// The attestation and its signature.
|
||||
pub sig: Sig,
|
||||
/// The address of the signer.
|
||||
pub signer: Address,
|
||||
}
|
||||
|
||||
/// Represents the domain for EIP-712 typed data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Domain {
|
||||
/// The name of the domain.
|
||||
pub name: String,
|
||||
/// The version of the domain.
|
||||
pub version: String,
|
||||
/// The chain ID of the domain.
|
||||
#[serde(rename = "chainId")]
|
||||
pub chain_id: String,
|
||||
/// The address of the verifying contract.
|
||||
#[serde(rename = "verifyingContract")]
|
||||
pub verifying_contract: String,
|
||||
}
|
||||
|
||||
/// The Signed data and its signature for an offline attestation.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Sig {
|
||||
/// The version of the attestation.
|
||||
pub version: u16,
|
||||
/// The uid of the attestation, which is a hash of the attestation data.
|
||||
pub uid: String,
|
||||
|
||||
/// The domain of the attestation.
|
||||
pub domain: Domain,
|
||||
#[serde(rename = "primaryType")]
|
||||
/// The primary type of the attestation.
|
||||
pub primary_type: String,
|
||||
/// The types that the attestation manages, which is a map of type names
|
||||
/// to their definitions.
|
||||
pub types: BTreeMap<String, Vec<Eip712DomainType>>,
|
||||
/// The message of the attestation, which is a map of field names to
|
||||
/// their values.
|
||||
pub message: BTreeMap<String, Value>,
|
||||
|
||||
/// The secp256k1 signature of the attestation.
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
/// Information to build an offline attestation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OfflineAttestationBuilder {
|
||||
pub schema: H256,
|
||||
pub recipient: Address,
|
||||
pub time: u64,
|
||||
pub expiration_time: Option<u64>,
|
||||
pub ref_uid: Option<H256>,
|
||||
pub revocable: bool,
|
||||
pub nonce: Option<u64>,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl OfflineAttestationBuilder {
|
||||
/// Create and sign and offline attestation, returning a
|
||||
/// `SignedOfflineAttestation`.
|
||||
pub async fn generate<S: Signer>(
|
||||
&self,
|
||||
signer: &S,
|
||||
verifying_contract: Address,
|
||||
) -> Result<SignedOfflineAttestation, <S as Signer>::Error> {
|
||||
let expiration_time = self.expiration_time.unwrap_or(0);
|
||||
|
||||
let mut salt = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut salt);
|
||||
|
||||
// the domain for the EIP-712 typed data.
|
||||
|
||||
let domain = EIP712Domain {
|
||||
name: Some("EAS Attestation".to_string()),
|
||||
version: Some("0.26".to_string()),
|
||||
chain_id: Some(U256::from(signer.chain_id())),
|
||||
verifying_contract: Some(verifying_contract),
|
||||
salt: None,
|
||||
};
|
||||
|
||||
// the typed data for the EIP-712 signature.
|
||||
|
||||
let typed_data = TypedData {
|
||||
domain,
|
||||
primary_type: "Attest".to_string(),
|
||||
types: BTreeMap::from([(
|
||||
"Attest".to_string(),
|
||||
vec![
|
||||
domtype("version", "uint16"),
|
||||
domtype("schema", "bytes32"),
|
||||
domtype("recipient", "address"),
|
||||
domtype("time", "uint64"),
|
||||
domtype("expirationTime", "uint64"),
|
||||
domtype("revocable", "bool"),
|
||||
domtype("refUID", "bytes32"),
|
||||
domtype("data", "bytes"),
|
||||
domtype("salt", "bytes32"),
|
||||
],
|
||||
)]),
|
||||
message: BTreeMap::from([
|
||||
("version".to_string(), Value::Number(Number::from(2))),
|
||||
(
|
||||
"schema".to_string(),
|
||||
Value::String(format!("0x{}", hex::encode(self.schema))),
|
||||
),
|
||||
(
|
||||
"recipient".to_string(),
|
||||
Value::String(format!("{:#x}", self.recipient)),
|
||||
),
|
||||
("time".to_string(), Value::String(self.time.to_string())),
|
||||
(
|
||||
"expirationTime".to_string(),
|
||||
Value::String(expiration_time.to_string()),
|
||||
),
|
||||
(
|
||||
"refUID".to_string(),
|
||||
Value::String(format!(
|
||||
"0x{}",
|
||||
hex::encode(self.ref_uid.unwrap_or_default())
|
||||
)),
|
||||
),
|
||||
("revocable".to_string(), Value::Bool(self.revocable)),
|
||||
(
|
||||
"data".to_string(),
|
||||
Value::String(format!("0x{}", hex::encode(&self.data))),
|
||||
),
|
||||
(
|
||||
"nonce".to_string(),
|
||||
Value::Number(Number::from(self.nonce.unwrap_or(0))),
|
||||
),
|
||||
(
|
||||
"salt".to_string(),
|
||||
Value::String(format!("0x{}", hex::encode(salt))),
|
||||
),
|
||||
]),
|
||||
};
|
||||
|
||||
// the unique identifier (uid) for the attestation, which is a hash of the ABI
|
||||
// encoded data.
|
||||
|
||||
let uid = {
|
||||
let tokens = vec![
|
||||
Token::FixedBytes(2u16.to_be_bytes().to_vec()),
|
||||
Token::Bytes(format!("0x{}", hex::encode(self.schema)).into_bytes()),
|
||||
Token::Address(self.recipient),
|
||||
Token::Address(Address::zero()),
|
||||
Token::FixedBytes(self.time.to_be_bytes().to_vec()),
|
||||
Token::FixedBytes(expiration_time.to_be_bytes().to_vec()),
|
||||
Token::Bool(self.revocable),
|
||||
Token::FixedBytes(self.ref_uid.unwrap_or_default().0.to_vec()),
|
||||
Token::Bytes(self.data.clone()),
|
||||
Token::FixedBytes(salt.to_vec()),
|
||||
Token::FixedBytes(0u32.to_be_bytes().to_vec()),
|
||||
];
|
||||
|
||||
let encoded = ethers::core::abi::encode_packed(&tokens).unwrap();
|
||||
keccak256(encoded)
|
||||
};
|
||||
|
||||
// Sign the typed data using the signer's private key.
|
||||
let signature = signer.sign_typed_data(&typed_data).await?;
|
||||
|
||||
// generate the signed offline attestation.
|
||||
let offline_attestation = SignedOfflineAttestation {
|
||||
sig: Sig {
|
||||
version: 2,
|
||||
uid: format!("0x{}", hex::encode(uid)),
|
||||
domain: Domain {
|
||||
name: typed_data.domain.name.unwrap(),
|
||||
version: typed_data.domain.version.unwrap().to_string(),
|
||||
chain_id: typed_data.domain.chain_id.unwrap().to_string(),
|
||||
verifying_contract: to_checksum(
|
||||
&typed_data.domain.verifying_contract.unwrap(),
|
||||
None,
|
||||
),
|
||||
},
|
||||
primary_type: typed_data.primary_type,
|
||||
types: typed_data.r#types,
|
||||
message: typed_data.message,
|
||||
signature,
|
||||
},
|
||||
signer: signer.address(),
|
||||
};
|
||||
|
||||
Ok(offline_attestation)
|
||||
}
|
||||
}
|
||||
|
||||
fn domtype(name: &str, type_name: &str) -> Eip712DomainType {
|
||||
Eip712DomainType {
|
||||
name: name.to_string(),
|
||||
r#type: type_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Timestamps an offline attestation by sending a transaction to the EAS
|
||||
/// contract.
|
||||
pub async fn timestamp_attestation(
|
||||
signer: &Wallet<SigningKey>,
|
||||
rpc_url: &str,
|
||||
attestation: &SignedOfflineAttestation,
|
||||
) -> Result<H256, eyre::ErrReport> {
|
||||
// Set up signer
|
||||
|
||||
let signer_address = signer.address();
|
||||
let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
|
||||
let provider = Provider::<Http>::try_from(rpc_url)?
|
||||
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||
.wrap_into(|p| SignerMiddleware::new(p, signer.clone()))
|
||||
.wrap_into(|p| NonceManagerMiddleware::new(p, signer_address)); // Outermost layer
|
||||
|
||||
// Read ABI and create contract instance
|
||||
|
||||
let abi: Abi = serde_json::from_str(include_str!("eas.abi"))?;
|
||||
let contract = ethers::contract::Contract::new(
|
||||
EAS_ADDRESS.parse::<Address>().unwrap(),
|
||||
abi,
|
||||
provider.into(),
|
||||
);
|
||||
|
||||
// Call Timestamp(bytes32 uid) method
|
||||
|
||||
let uid =
|
||||
H256::from_str(&attestation.sig.uid).map_err(|_| eyre::eyre!("Invalid UID format"))?;
|
||||
|
||||
let method = contract.method::<_, H256>("timestamp", uid)?;
|
||||
let pending_tx = method.send().await?;
|
||||
|
||||
// Wait for transaction confirmation
|
||||
|
||||
println!("EAS Timestamping attestation: {:?}", pending_tx);
|
||||
|
||||
let receipt = pending_tx
|
||||
.confirmations(1)
|
||||
.await?
|
||||
.ok_or_else(|| eyre::eyre!("Transaction receipt not found"))?;
|
||||
|
||||
println!(
|
||||
"EAS Attestation timestamped: {:?}",
|
||||
receipt.transaction_hash
|
||||
);
|
||||
|
||||
Ok(receipt.transaction_hash)
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,8 @@ use tracing::instrument;
|
||||
|
||||
use tls_server_fixture::CA_CERT_DER;
|
||||
use tlsn::{
|
||||
config::{ProtocolConfig, ProtocolConfigValidator},
|
||||
config::{CertificateDer, ProtocolConfig, ProtocolConfigValidator, RootCertStore},
|
||||
connection::ServerName,
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
transcript::PartialTranscript,
|
||||
verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig},
|
||||
@@ -72,18 +73,16 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(root_store);
|
||||
tls_config_builder.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
});
|
||||
let tls_config = tls_config_builder.build().unwrap();
|
||||
|
||||
// Set up protocol configuration for prover.
|
||||
let mut prover_config_builder = ProverConfig::builder();
|
||||
prover_config_builder
|
||||
.server_name(server_domain)
|
||||
.server_name(ServerName::Dns(server_domain.try_into().unwrap()))
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
@@ -194,13 +193,10 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let verifier_config = VerifierConfig::builder()
|
||||
.root_store(root_store)
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.protocol_config_validator(config_validator)
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -234,6 +230,7 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
.unwrap_or_else(|| panic!("Expected valid data from {SERVER_DOMAIN}"));
|
||||
|
||||
// Check Session info: server name.
|
||||
let ServerName::Dns(server_name) = server_name;
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
|
||||
transcript
|
||||
|
||||
@@ -26,7 +26,7 @@ pub enum Id {
|
||||
One,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
pub enum IoMode {
|
||||
Client,
|
||||
Server,
|
||||
|
||||
@@ -7,12 +7,12 @@ docker build --pull -t tlsn-bench . -f ./crates/harness/harness.Dockerfile
|
||||
|
||||
Next run the benches with:
|
||||
```
|
||||
docker run -it --privileged -v ./crates/harness/:/benches tlsn-bench bash -c "runner setup; runner bench"
|
||||
docker run -it --privileged -v $(pwd)/crates/harness/:/benches tlsn-bench bash -c "runner setup; runner bench"
|
||||
```
|
||||
The `--privileged` parameter is required because this test bench needs permission to create networks with certain parameters
|
||||
|
||||
To run the benches in a browser run:
|
||||
```
|
||||
docker run -it --privileged -v ./crates/harness/:/benches tlsn-bench bash -c "cd /; runner setup; runner --target browser bench"
|
||||
docker run -it --privileged -v $(pwd)/crates/harness/:/benches tlsn-bench bash -c "runner setup; runner --target browser bench"
|
||||
```
|
||||
|
||||
|
||||
@@ -8,14 +8,9 @@ publish = false
|
||||
name = "harness_executor"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.custom]
|
||||
wasm-opt = ["-O3"]
|
||||
|
||||
[dependencies]
|
||||
tlsn-harness-core = { workspace = true }
|
||||
tlsn = { workspace = true }
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tlsn-server-fixture-certs = { workspace = true }
|
||||
|
||||
inventory = { workspace = true }
|
||||
@@ -33,6 +28,8 @@ tokio = { workspace = true, features = ["full"] }
|
||||
tokio-util = { workspace = true, features = ["compat"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# Disable tracing events as a workaround for issue 959.
|
||||
tracing = { workspace = true, features = ["release_max_level_off"] }
|
||||
wasm-bindgen = { workspace = true }
|
||||
tlsn-wasm = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
|
||||
@@ -5,7 +5,8 @@ use futures::{AsyncReadExt, AsyncWriteExt, TryFutureExt};
|
||||
|
||||
use harness_core::bench::{Bench, ProverMetrics};
|
||||
use tlsn::{
|
||||
config::ProtocolConfig,
|
||||
config::{CertificateDer, ProtocolConfig, RootCertStore},
|
||||
connection::ServerName,
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
};
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
@@ -32,20 +33,17 @@ pub async fn bench_prover(provider: &IoProvider, config: &Bench) -> Result<Prove
|
||||
|
||||
let protocol_config = builder.build()?;
|
||||
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(root_store);
|
||||
tls_config_builder.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
});
|
||||
let tls_config = tls_config_builder.build()?;
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(protocol_config)
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.server_name(ServerName::Dns(SERVER_DOMAIN.try_into().unwrap()))
|
||||
.build()?,
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
|
||||
use harness_core::bench::Bench;
|
||||
use tlsn::{
|
||||
config::ProtocolConfigValidator,
|
||||
config::{CertificateDer, ProtocolConfigValidator, RootCertStore},
|
||||
verifier::{Verifier, VerifierConfig, VerifyConfig},
|
||||
};
|
||||
use tlsn_server_fixture_certs::CA_CERT_DER;
|
||||
@@ -17,14 +17,11 @@ pub async fn bench_verifier(provider: &IoProvider, config: &Bench) -> Result<()>
|
||||
|
||||
let protocol_config = builder.build()?;
|
||||
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let verifier = Verifier::new(
|
||||
VerifierConfig::builder()
|
||||
.root_store(root_store)
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.protocol_config_validator(protocol_config)
|
||||
.build()?,
|
||||
);
|
||||
|
||||
@@ -81,7 +81,11 @@ mod native {
|
||||
mod wasm {
|
||||
use super::IoProvider;
|
||||
use crate::io::Io;
|
||||
use anyhow::Result;
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::time::Duration;
|
||||
|
||||
const CHECK_WS_OPEN_DELAY_MS: usize = 50;
|
||||
const MAX_RETRIES: usize = 50;
|
||||
|
||||
impl IoProvider {
|
||||
/// Provides a connection to the server.
|
||||
@@ -107,7 +111,27 @@ mod wasm {
|
||||
&self.config.proto_1.0,
|
||||
self.config.proto_1.1,
|
||||
);
|
||||
let (_, io) = ws_stream_wasm::WsMeta::connect(url, None).await?;
|
||||
let mut retries = 0;
|
||||
|
||||
let io = loop {
|
||||
// Connect to the websocket relay.
|
||||
let (_, io) = ws_stream_wasm::WsMeta::connect(url.clone(), None).await?;
|
||||
|
||||
// Allow some time for the relay to initiate a connection to
|
||||
// the verifier.
|
||||
std::thread::sleep(Duration::from_millis(CHECK_WS_OPEN_DELAY_MS as u64));
|
||||
|
||||
// If the relay didn't close the io, most likely the verifier
|
||||
// accepted the connection.
|
||||
if io.ready_state() == ws_stream_wasm::WsState::Open {
|
||||
break io;
|
||||
}
|
||||
|
||||
retries += 1;
|
||||
if retries > MAX_RETRIES {
|
||||
return Err(anyhow!("verifier did not accept connection"));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(io.into_io())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use tls_core::anchors::RootCertStore;
|
||||
use tlsn::{
|
||||
config::{ProtocolConfig, ProtocolConfigValidator},
|
||||
config::{CertificateDer, ProtocolConfig, ProtocolConfigValidator, RootCertStore},
|
||||
connection::ServerName,
|
||||
hash::HashAlgId,
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
transcript::{TranscriptCommitConfig, TranscriptCommitment, TranscriptCommitmentKind},
|
||||
@@ -21,19 +21,17 @@ const MAX_RECV_DATA: usize = 1 << 11;
|
||||
crate::test!("basic", prover, verifier);
|
||||
|
||||
async fn prover(provider: &IoProvider) {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(root_store);
|
||||
tls_config_builder.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
});
|
||||
|
||||
let tls_config = tls_config_builder.build().unwrap();
|
||||
|
||||
let server_name = ServerName::Dns(SERVER_DOMAIN.try_into().unwrap());
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.server_name(server_name)
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
@@ -114,11 +112,6 @@ async fn prover(provider: &IoProvider) {
|
||||
}
|
||||
|
||||
async fn verifier(provider: &IoProvider) {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let config = VerifierConfig::builder()
|
||||
.protocol_config_validator(
|
||||
ProtocolConfigValidator::builder()
|
||||
@@ -127,7 +120,9 @@ async fn verifier(provider: &IoProvider) {
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.root_store(root_store)
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -145,7 +140,9 @@ async fn verifier(provider: &IoProvider) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(server_name.unwrap().as_str(), SERVER_DOMAIN);
|
||||
let ServerName::Dns(server_name) = server_name.unwrap();
|
||||
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
assert!(
|
||||
transcript_commitments
|
||||
.iter()
|
||||
|
||||
@@ -8,6 +8,7 @@ use chromiumoxide::{
|
||||
network::{EnableParams, SetCacheDisabledParams},
|
||||
page::ReloadParams,
|
||||
},
|
||||
handler::HandlerConfig,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use harness_core::{
|
||||
@@ -126,8 +127,18 @@ impl Executor {
|
||||
const TIMEOUT: usize = 10000;
|
||||
const DELAY: usize = 100;
|
||||
let mut retries = 0;
|
||||
let config = HandlerConfig {
|
||||
// Bump the timeout for long-running benches.
|
||||
request_timeout: Duration::from_secs(120),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (browser, mut handler) = loop {
|
||||
match Browser::connect(format!("http://{}:{}", rpc_addr.0, PORT_BROWSER)).await
|
||||
match Browser::connect_with_config(
|
||||
format!("http://{}:{}", rpc_addr.0, PORT_BROWSER),
|
||||
config.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(browser) => break browser,
|
||||
Err(e) => {
|
||||
@@ -143,6 +154,14 @@ impl Executor {
|
||||
tokio::spawn(async move {
|
||||
while let Some(res) = handler.next().await {
|
||||
if let Err(e) = res {
|
||||
if e.to_string()
|
||||
== "data did not match any variant of untagged enum Message"
|
||||
{
|
||||
// Do not log this error. It appears to be
|
||||
// caused by a bug upstream.
|
||||
// https://github.com/mattsse/chromiumoxide/issues/167
|
||||
continue;
|
||||
}
|
||||
eprintln!("chromium error: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,3 +72,5 @@ tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
tokio-util = { workspace = true, features = ["compat"] }
|
||||
tracing-subscriber = { workspace = true }
|
||||
uid-mux = { workspace = true, features = ["serio", "test-utils"] }
|
||||
rustls-pki-types = { workspace = true }
|
||||
rustls-webpki = { workspace = true }
|
||||
|
||||
@@ -22,7 +22,7 @@ use serio::stream::IoStreamExt;
|
||||
use std::mem;
|
||||
use tls_core::msgs::enums::NamedGroup;
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2, TlsVersion, VerifyData},
|
||||
connection::{CertBinding, CertBindingV1_2, TlsVersion, VerifyData},
|
||||
transcript::TlsTranscript,
|
||||
};
|
||||
use tracing::{debug, instrument};
|
||||
@@ -405,7 +405,7 @@ impl MpcTlsFollower {
|
||||
let cf_vd = cf_vd.ok_or(MpcTlsError::hs("client finished VD not computed"))?;
|
||||
let sf_vd = sf_vd.ok_or(MpcTlsError::hs("server finished VD not computed"))?;
|
||||
|
||||
let handshake_data = HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let handshake_data = CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random,
|
||||
server_random,
|
||||
server_ephemeral_key: server_key
|
||||
|
||||
@@ -43,10 +43,9 @@ use tls_core::{
|
||||
suites::SupportedCipherSuite,
|
||||
};
|
||||
use tlsn_core::{
|
||||
connection::{
|
||||
Certificate, HandshakeData, HandshakeDataV1_2, ServerSignature, TlsVersion, VerifyData,
|
||||
},
|
||||
connection::{CertBinding, CertBindingV1_2, ServerSignature, TlsVersion, VerifyData},
|
||||
transcript::TlsTranscript,
|
||||
webpki::CertificateDer,
|
||||
};
|
||||
use tracing::{debug, instrument, trace, warn};
|
||||
|
||||
@@ -325,7 +324,7 @@ impl MpcTlsLeader {
|
||||
let server_cert_chain = server_cert_details
|
||||
.cert_chain()
|
||||
.iter()
|
||||
.map(|cert| Certificate(cert.0.clone()))
|
||||
.map(|cert| CertificateDer(cert.0.clone()))
|
||||
.collect();
|
||||
|
||||
let server_signature = ServerSignature {
|
||||
@@ -337,7 +336,7 @@ impl MpcTlsLeader {
|
||||
sig: server_kx_details.kx_sig().sig.0.clone(),
|
||||
};
|
||||
|
||||
let handshake_data = HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let handshake_data = CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random: client_random.0,
|
||||
server_random: server_random.0,
|
||||
server_ephemeral_key: server_key
|
||||
|
||||
@@ -12,11 +12,15 @@ use mpz_ot::{
|
||||
rcot::shared::{SharedRCOTReceiver, SharedRCOTSender},
|
||||
};
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tls_client::Certificate;
|
||||
use rustls_pki_types::CertificateDer;
|
||||
use tls_client::RootCertStore;
|
||||
use tls_client_async::bind_client;
|
||||
use tls_server_fixture::{bind_test_server_hyper, CA_CERT_DER, SERVER_DOMAIN};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
use webpki::anchor_from_trusted_cert;
|
||||
|
||||
const CA_CERT: CertificateDer = CertificateDer::from_slice(CA_CERT_DER);
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[ignore = "expensive"]
|
||||
@@ -48,11 +52,11 @@ async fn leader_task(mut leader: MpcTlsLeader) {
|
||||
let (leader_ctrl, leader_fut) = leader.run();
|
||||
tokio::spawn(async { leader_fut.await.unwrap() });
|
||||
|
||||
let mut root_store = tls_client::RootCertStore::empty();
|
||||
root_store.add(&Certificate(CA_CERT_DER.to_vec())).unwrap();
|
||||
let config = tls_client::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
.with_root_certificates(RootCertStore {
|
||||
roots: vec![anchor_from_trusted_cert(&CA_CERT).unwrap().to_owned()],
|
||||
})
|
||||
.with_no_client_auth();
|
||||
|
||||
let server_name = SERVER_DOMAIN.try_into().unwrap();
|
||||
|
||||
@@ -35,3 +35,5 @@ hyper = { workspace = true, features = ["client", "http1"] }
|
||||
hyper-util = { workspace = true, features = ["full"] }
|
||||
rstest = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
|
||||
rustls-webpki = { workspace = true }
|
||||
rustls-pki-types = { workspace = true }
|
||||
|
||||
@@ -6,7 +6,8 @@ use http_body_util::{BodyExt as _, Full};
|
||||
use hyper::{body::Bytes, Request, StatusCode};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use rstest::{fixture, rstest};
|
||||
use tls_client::{Certificate, ClientConfig, ClientConnection, RustCryptoBackend, ServerName};
|
||||
use rustls_pki_types::CertificateDer;
|
||||
use tls_client::{ClientConfig, ClientConnection, RustCryptoBackend, ServerName};
|
||||
use tls_client_async::{bind_client, ClosedConnection, ConnectionError, TlsConnection};
|
||||
use tls_server_fixture::{
|
||||
bind_test_server, bind_test_server_hyper, APP_RECORD_LENGTH, CA_CERT_DER, CLOSE_DELAY,
|
||||
@@ -14,6 +15,9 @@ use tls_server_fixture::{
|
||||
};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
||||
use webpki::anchor_from_trusted_cert;
|
||||
|
||||
const CA_CERT: CertificateDer = CertificateDer::from_slice(CA_CERT_DER);
|
||||
|
||||
// An established client TLS connection
|
||||
struct TlsFixture {
|
||||
@@ -30,7 +34,9 @@ async fn set_up_tls() -> TlsFixture {
|
||||
let _server_task = tokio::spawn(bind_test_server(server_socket.compat()));
|
||||
|
||||
let mut root_store = tls_client::RootCertStore::empty();
|
||||
root_store.add(&Certificate(CA_CERT_DER.to_vec())).unwrap();
|
||||
root_store
|
||||
.roots
|
||||
.push(anchor_from_trusted_cert(&CA_CERT).unwrap().to_owned());
|
||||
let config = ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
@@ -75,7 +81,9 @@ async fn test_hyper_ok() {
|
||||
let server_task = tokio::spawn(bind_test_server_hyper(server_socket.compat()));
|
||||
|
||||
let mut root_store = tls_client::RootCertStore::empty();
|
||||
root_store.add(&Certificate(CA_CERT_DER.to_vec())).unwrap();
|
||||
root_store
|
||||
.roots
|
||||
.push(anchor_from_trusted_cert(&CA_CERT).unwrap().to_owned());
|
||||
let config = ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
|
||||
@@ -23,7 +23,8 @@ async-trait = { workspace = true }
|
||||
log = { workspace = true, optional = true }
|
||||
ring = { workspace = true }
|
||||
sct = { workspace = true }
|
||||
webpki = { workspace = true, features = ["alloc", "std"] }
|
||||
rustls-pki-types = { workspace = true }
|
||||
rustls-webpki = { workspace = true }
|
||||
aes-gcm = { workspace = true }
|
||||
p256 = { workspace = true, features = ["ecdh"] }
|
||||
rand = { workspace = true }
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::{
|
||||
conn::{CommonState, ConnectionRandoms, State},
|
||||
error::Error,
|
||||
hash_hs::HandshakeHashBuffer,
|
||||
msgs::persist,
|
||||
ticketer::TimeBase,
|
||||
};
|
||||
use tls_core::{
|
||||
@@ -42,35 +41,6 @@ pub(super) type NextState = Box<dyn State<ClientConnectionData>>;
|
||||
pub(super) type NextStateOrError = Result<NextState, Error>;
|
||||
pub(super) type ClientContext<'a> = crate::conn::Context<'a>;
|
||||
|
||||
fn find_session(
|
||||
server_name: &ServerName,
|
||||
config: &ClientConfig,
|
||||
) -> Option<persist::Retrieved<persist::ClientSessionValue>> {
|
||||
let key = persist::ClientSessionKey::session_for_server_name(server_name);
|
||||
let key_buf = key.get_encoding();
|
||||
|
||||
let value = config.session_storage.get(&key_buf).or_else(|| {
|
||||
debug!("No cached session for {:?}", server_name);
|
||||
None
|
||||
})?;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut reader = Reader::init(&value[2..]);
|
||||
#[allow(clippy::bind_instead_of_map)] // https://github.com/rust-lang/rust-clippy/issues/8082
|
||||
CipherSuite::read_bytes(&value[..2])
|
||||
.and_then(|suite| {
|
||||
persist::ClientSessionValue::read(&mut reader, suite, &config.cipher_suites)
|
||||
})
|
||||
.and_then(|resuming| {
|
||||
let retrieved = persist::Retrieved::new(resuming, TimeBase::now().ok()?);
|
||||
match retrieved.has_expired() {
|
||||
false => Some(retrieved),
|
||||
true => None,
|
||||
}
|
||||
})
|
||||
.and_then(Some)
|
||||
}
|
||||
|
||||
pub(super) async fn start_handshake(
|
||||
server_name: ServerName,
|
||||
extra_exts: Vec<ClientExtension>,
|
||||
@@ -123,7 +93,6 @@ pub(super) async fn start_handshake(
|
||||
emit_client_hello_for_retry(
|
||||
config,
|
||||
cx,
|
||||
None,
|
||||
random,
|
||||
false,
|
||||
transcript_buffer,
|
||||
@@ -142,7 +111,6 @@ pub(super) async fn start_handshake(
|
||||
|
||||
struct ExpectServerHello {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Retrieved<persist::ClientSessionValue>>,
|
||||
server_name: ServerName,
|
||||
random: Random,
|
||||
using_ems: bool,
|
||||
@@ -162,7 +130,6 @@ struct ExpectServerHelloOrHelloRetryRequest {
|
||||
async fn emit_client_hello_for_retry(
|
||||
config: Arc<ClientConfig>,
|
||||
cx: &mut ClientContext<'_>,
|
||||
resuming_session: Option<persist::Retrieved<persist::ClientSessionValue>>,
|
||||
random: Random,
|
||||
using_ems: bool,
|
||||
mut transcript_buffer: HandshakeHashBuffer,
|
||||
@@ -176,25 +143,6 @@ async fn emit_client_hello_for_retry(
|
||||
may_send_sct_list: bool,
|
||||
suite: Option<SupportedCipherSuite>,
|
||||
) -> Result<NextState, Error> {
|
||||
// For now we do not support session resumption
|
||||
//
|
||||
// Do we have a SessionID or ticket cached for this host?
|
||||
// let (ticket, resume_version) = if let Some(resuming) = &resuming_session {
|
||||
// match &resuming.value {
|
||||
// persist::ClientSessionValue::Tls13(inner) => {
|
||||
// (inner.ticket().to_vec(), ProtocolVersion::TLSv1_3)
|
||||
// }
|
||||
// #[cfg(feature = "tls12")]
|
||||
// persist::ClientSessionValue::Tls12(inner) => {
|
||||
// (inner.ticket().to_vec(), ProtocolVersion::TLSv1_2)
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// (Vec::new(), ProtocolVersion::Unknown(0))
|
||||
// };
|
||||
|
||||
// let (ticket, resume_version) = (Vec::new(), ProtocolVersion::Unknown(0));
|
||||
|
||||
let support_tls12 = config.supports_version(ProtocolVersion::TLSv1_2);
|
||||
let support_tls13 = config.supports_version(ProtocolVersion::TLSv1_3);
|
||||
|
||||
@@ -256,48 +204,6 @@ async fn emit_client_hello_for_retry(
|
||||
// Extra extensions must be placed before the PSK extension
|
||||
exts.extend(extra_exts.iter().cloned());
|
||||
|
||||
// let fill_in_binder = if support_tls13
|
||||
// && config.enable_tickets
|
||||
// && resume_version == ProtocolVersion::TLSv1_3
|
||||
// && !ticket.is_empty()
|
||||
// {
|
||||
// let resuming =
|
||||
// resuming_session
|
||||
// .as_ref()
|
||||
// .and_then(|resuming| match (suite, resuming.tls13()) {
|
||||
// (Some(suite), Some(resuming)) => {
|
||||
// suite.tls13()?.can_resume_from(resuming.suite())?;
|
||||
// Some(resuming)
|
||||
// }
|
||||
// (None, Some(resuming)) => Some(resuming),
|
||||
// _ => None,
|
||||
// });
|
||||
// if let Some(ref resuming) = resuming {
|
||||
// tls13::prepare_resumption(
|
||||
// &config,
|
||||
// cx,
|
||||
// ticket,
|
||||
// &resuming,
|
||||
// &mut exts,
|
||||
// retryreq.is_some(),
|
||||
// )
|
||||
// .await;
|
||||
// }
|
||||
// resuming
|
||||
// } else if config.enable_tickets {
|
||||
// // If we have a ticket, include it. Otherwise, request one.
|
||||
// if ticket.is_empty() {
|
||||
// exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Request));
|
||||
// } else {
|
||||
// exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Offer(
|
||||
// Payload::new(ticket),
|
||||
// )));
|
||||
// }
|
||||
// None
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
|
||||
// Note what extensions we sent.
|
||||
hello.sent_extensions = exts.iter().map(ClientExtension::get_type).collect();
|
||||
|
||||
@@ -319,8 +225,8 @@ async fn emit_client_hello_for_retry(
|
||||
};
|
||||
|
||||
// let early_key_schedule = if let Some(resuming) = fill_in_binder {
|
||||
// let schedule = tls13::fill_in_psk_binder(&resuming, &transcript_buffer, &mut chp);
|
||||
// Some((resuming.suite(), schedule))
|
||||
// let schedule = tls13::fill_in_psk_binder(&resuming, &transcript_buffer,
|
||||
// &mut chp); Some((resuming.suite(), schedule))
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
@@ -350,7 +256,6 @@ async fn emit_client_hello_for_retry(
|
||||
|
||||
let next = ExpectServerHello {
|
||||
config,
|
||||
resuming_session,
|
||||
server_name,
|
||||
random,
|
||||
using_ems,
|
||||
@@ -551,19 +456,10 @@ impl State<ClientConnectionData> for ExpectServerHello {
|
||||
// handshake_traffic_secret.
|
||||
match suite {
|
||||
SupportedCipherSuite::Tls13(suite) => {
|
||||
let resuming_session =
|
||||
self.resuming_session
|
||||
.and_then(|resuming| match resuming.value {
|
||||
persist::ClientSessionValue::Tls13(inner) => Some(inner),
|
||||
#[cfg(feature = "tls12")]
|
||||
persist::ClientSessionValue::Tls12(_) => None,
|
||||
});
|
||||
|
||||
tls13::handle_server_hello(
|
||||
self.config,
|
||||
cx,
|
||||
server_hello,
|
||||
resuming_session,
|
||||
self.server_name,
|
||||
randoms,
|
||||
suite,
|
||||
@@ -577,16 +473,8 @@ impl State<ClientConnectionData> for ExpectServerHello {
|
||||
}
|
||||
#[cfg(feature = "tls12")]
|
||||
SupportedCipherSuite::Tls12(suite) => {
|
||||
let resuming_session =
|
||||
self.resuming_session
|
||||
.and_then(|resuming| match resuming.value {
|
||||
persist::ClientSessionValue::Tls12(inner) => Some(inner),
|
||||
persist::ClientSessionValue::Tls13(_) => None,
|
||||
});
|
||||
|
||||
tls12::CompleteServerHelloHandling {
|
||||
config: self.config,
|
||||
resuming_session,
|
||||
server_name: self.server_name,
|
||||
randoms,
|
||||
using_ems: self.using_ems,
|
||||
@@ -723,7 +611,6 @@ impl ExpectServerHelloOrHelloRetryRequest {
|
||||
emit_client_hello_for_retry(
|
||||
self.next.config,
|
||||
cx,
|
||||
self.next.resuming_session,
|
||||
self.next.random,
|
||||
self.next.using_ems,
|
||||
transcript_buffer,
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::{
|
||||
conn::{CommonState, ConnectionRandoms, State},
|
||||
error::Error,
|
||||
hash_hs::HandshakeHash,
|
||||
msgs::persist,
|
||||
sign::Signer,
|
||||
ticketer::TimeBase,
|
||||
verify,
|
||||
@@ -49,7 +48,6 @@ mod server_hello {
|
||||
|
||||
pub(in crate::client) struct CompleteServerHelloHandling {
|
||||
pub(in crate::client) config: Arc<ClientConfig>,
|
||||
pub(in crate::client) resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
pub(in crate::client) server_name: ServerName,
|
||||
pub(in crate::client) randoms: ConnectionRandoms,
|
||||
pub(in crate::client) using_ems: bool,
|
||||
@@ -113,76 +111,8 @@ mod server_hello {
|
||||
None
|
||||
};
|
||||
|
||||
// See if we're successfully resuming.
|
||||
if let Some(ref _resuming) = self.resuming_session {
|
||||
return Err(Error::General(
|
||||
"client does not support resumption".to_string(),
|
||||
));
|
||||
// if resuming.session_id == server_hello.session_id {
|
||||
// debug!("Server agreed to resume");
|
||||
|
||||
// // Is the server telling lies about the ciphersuite?
|
||||
// if resuming.suite() != suite {
|
||||
// let error_msg =
|
||||
// "abbreviated handshake offered, but with varied cs".to_string();
|
||||
// return Err(Error::PeerMisbehavedError(error_msg));
|
||||
// }
|
||||
|
||||
// // And about EMS support?
|
||||
// if resuming.extended_ms() != self.using_ems {
|
||||
// let error_msg = "server varied ems support over resume".to_string();
|
||||
// return Err(Error::PeerMisbehavedError(error_msg));
|
||||
// }
|
||||
|
||||
// let secrets =
|
||||
// ConnectionSecrets::new_resume(self.randoms, suite, resuming.secret());
|
||||
// self.config.key_log.log(
|
||||
// "CLIENT_RANDOM",
|
||||
// &secrets.randoms.client,
|
||||
// &secrets.master_secret,
|
||||
// );
|
||||
// cx.common.start_encryption_tls12(&secrets, Side::Client);
|
||||
|
||||
// // Since we're resuming, we verified the certificate and
|
||||
// // proof of possession in the prior session.
|
||||
// cx.common.peer_certificates = Some(resuming.server_cert_chain().to_vec());
|
||||
// let cert_verified = verify::ServerCertVerified::assertion();
|
||||
// let sig_verified = verify::HandshakeSignatureValid::assertion();
|
||||
|
||||
// return if must_issue_new_ticket {
|
||||
// Ok(Box::new(ExpectNewTicket {
|
||||
// config: self.config,
|
||||
// secrets,
|
||||
// resuming_session: self.resuming_session,
|
||||
// session_id: server_hello.session_id,
|
||||
// server_name: self.server_name,
|
||||
// using_ems: self.using_ems,
|
||||
// transcript: self.transcript,
|
||||
// resuming: true,
|
||||
// cert_verified,
|
||||
// sig_verified,
|
||||
// }))
|
||||
// } else {
|
||||
// Ok(Box::new(ExpectCcs {
|
||||
// config: self.config,
|
||||
// secrets,
|
||||
// resuming_session: self.resuming_session,
|
||||
// session_id: server_hello.session_id,
|
||||
// server_name: self.server_name,
|
||||
// using_ems: self.using_ems,
|
||||
// transcript: self.transcript,
|
||||
// ticket: None,
|
||||
// resuming: true,
|
||||
// cert_verified,
|
||||
// sig_verified,
|
||||
// }))
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
Ok(Box::new(ExpectCertificate {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: server_hello.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -199,7 +129,6 @@ mod server_hello {
|
||||
|
||||
struct ExpectCertificate {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
@@ -228,7 +157,6 @@ impl State<ClientConnectionData> for ExpectCertificate {
|
||||
if self.may_send_cert_status {
|
||||
Ok(Box::new(ExpectCertificateStatusOrServerKx {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -250,7 +178,6 @@ impl State<ClientConnectionData> for ExpectCertificate {
|
||||
|
||||
Ok(Box::new(ExpectServerKx {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -266,7 +193,6 @@ impl State<ClientConnectionData> for ExpectCertificate {
|
||||
|
||||
struct ExpectCertificateStatusOrServerKx {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
@@ -303,7 +229,6 @@ impl State<ClientConnectionData> for ExpectCertificateStatusOrServerKx {
|
||||
|
||||
Box::new(ExpectServerKx {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -322,7 +247,6 @@ impl State<ClientConnectionData> for ExpectCertificateStatusOrServerKx {
|
||||
}) => {
|
||||
Box::new(ExpectCertificateStatus {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -350,7 +274,6 @@ impl State<ClientConnectionData> for ExpectCertificateStatusOrServerKx {
|
||||
|
||||
struct ExpectCertificateStatus {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
@@ -395,7 +318,6 @@ impl State<ClientConnectionData> for ExpectCertificateStatus {
|
||||
|
||||
Ok(Box::new(ExpectServerKx {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -410,7 +332,6 @@ impl State<ClientConnectionData> for ExpectCertificateStatus {
|
||||
|
||||
struct ExpectServerKx {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
@@ -458,7 +379,6 @@ impl State<ClientConnectionData> for ExpectServerKx {
|
||||
|
||||
Ok(Box::new(ExpectServerDoneOrCertReq {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -570,7 +490,6 @@ async fn emit_finished(
|
||||
// client auth. Otherwise we go straight to ServerHelloDone.
|
||||
struct ExpectServerDoneOrCertReq {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
@@ -598,7 +517,6 @@ impl State<ClientConnectionData> for ExpectServerDoneOrCertReq {
|
||||
) {
|
||||
Box::new(ExpectCertificateRequest {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -616,7 +534,6 @@ impl State<ClientConnectionData> for ExpectServerDoneOrCertReq {
|
||||
|
||||
Box::new(ExpectServerDone {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -636,7 +553,6 @@ impl State<ClientConnectionData> for ExpectServerDoneOrCertReq {
|
||||
|
||||
struct ExpectCertificateRequest {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
@@ -679,7 +595,6 @@ impl State<ClientConnectionData> for ExpectCertificateRequest {
|
||||
|
||||
Ok(Box::new(ExpectServerDone {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
@@ -696,7 +611,6 @@ impl State<ClientConnectionData> for ExpectCertificateRequest {
|
||||
|
||||
struct ExpectServerDone {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
@@ -745,6 +659,7 @@ impl State<ClientConnectionData> for ExpectServerDone {
|
||||
// 3. Verify that the top certificate signed their kx.
|
||||
// 4. If doing client auth, send our Certificate.
|
||||
// 5. Complete the key exchange:
|
||||
//
|
||||
// a) generate our kx pair
|
||||
// b) emit a ClientKeyExchange containing it
|
||||
// c) if doing client auth, emit a CertificateVerify
|
||||
@@ -891,7 +806,6 @@ impl State<ClientConnectionData> for ExpectServerDone {
|
||||
if st.must_issue_new_ticket {
|
||||
Ok(Box::new(ExpectNewTicket {
|
||||
config: st.config,
|
||||
resuming_session: st.resuming_session,
|
||||
session_id: st.session_id,
|
||||
server_name: st.server_name,
|
||||
using_ems: st.using_ems,
|
||||
@@ -903,7 +817,6 @@ impl State<ClientConnectionData> for ExpectServerDone {
|
||||
} else {
|
||||
Ok(Box::new(ExpectCcs {
|
||||
config: st.config,
|
||||
resuming_session: st.resuming_session,
|
||||
session_id: st.session_id,
|
||||
server_name: st.server_name,
|
||||
using_ems: st.using_ems,
|
||||
@@ -919,7 +832,6 @@ impl State<ClientConnectionData> for ExpectServerDone {
|
||||
|
||||
struct ExpectNewTicket {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
using_ems: bool,
|
||||
@@ -946,7 +858,6 @@ impl State<ClientConnectionData> for ExpectNewTicket {
|
||||
|
||||
Ok(Box::new(ExpectCcs {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
using_ems: self.using_ems,
|
||||
@@ -962,7 +873,6 @@ impl State<ClientConnectionData> for ExpectNewTicket {
|
||||
// -- Waiting for their CCS --
|
||||
struct ExpectCcs {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
using_ems: bool,
|
||||
@@ -998,7 +908,6 @@ impl State<ClientConnectionData> for ExpectCcs {
|
||||
|
||||
Ok(Box::new(ExpectFinished {
|
||||
config: self.config,
|
||||
resuming_session: self.resuming_session,
|
||||
session_id: self.session_id,
|
||||
server_name: self.server_name,
|
||||
using_ems: self.using_ems,
|
||||
@@ -1013,7 +922,6 @@ impl State<ClientConnectionData> for ExpectCcs {
|
||||
|
||||
struct ExpectFinished {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls12ClientSessionValue>,
|
||||
session_id: SessionID,
|
||||
server_name: ServerName,
|
||||
using_ems: bool,
|
||||
@@ -1024,60 +932,6 @@ struct ExpectFinished {
|
||||
sig_verified: verify::HandshakeSignatureValid,
|
||||
}
|
||||
|
||||
// impl ExpectFinished {
|
||||
// // -- Waiting for their finished --
|
||||
// fn save_session(&mut self, cx: &mut ClientContext<'_>) {
|
||||
// // Save a ticket. If we got a new ticket, save that. Otherwise, save the
|
||||
// // original ticket again.
|
||||
// let (mut ticket, lifetime) = match self.ticket.take() {
|
||||
// Some(nst) => (nst.ticket.0, nst.lifetime_hint),
|
||||
// None => (Vec::new(), 0),
|
||||
// };
|
||||
|
||||
// if ticket.is_empty() {
|
||||
// if let Some(resuming_session) = &mut self.resuming_session {
|
||||
// ticket = resuming_session.take_ticket();
|
||||
// }
|
||||
// }
|
||||
|
||||
// if self.session_id.is_empty() && ticket.is_empty() {
|
||||
// debug!("Session not saved: server didn't allocate id or ticket");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// let time_now = match TimeBase::now() {
|
||||
// Ok(time_now) => time_now,
|
||||
// Err(e) => {
|
||||
// debug!("Session not saved: {}", e);
|
||||
// return;
|
||||
// }
|
||||
// };
|
||||
|
||||
// let key = persist::ClientSessionKey::session_for_server_name(&self.server_name);
|
||||
// let value = persist::Tls12ClientSessionValue::new(
|
||||
// self.secrets.suite(),
|
||||
// self.session_id,
|
||||
// ticket,
|
||||
// self.secrets.get_master_secret(),
|
||||
// cx.common.peer_certificates.clone().unwrap_or_default(),
|
||||
// time_now,
|
||||
// lifetime,
|
||||
// self.using_ems,
|
||||
// );
|
||||
|
||||
// let worked = self
|
||||
// .config
|
||||
// .session_storage
|
||||
// .put(key.get_encoding(), value.get_encoding());
|
||||
|
||||
// if worked {
|
||||
// debug!("Session saved");
|
||||
// } else {
|
||||
// debug!("Session not saved");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[async_trait]
|
||||
impl State<ClientConnectionData> for ExpectFinished {
|
||||
async fn handle(
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::{
|
||||
conn::{CommonState, ConnectionRandoms, State},
|
||||
error::Error,
|
||||
hash_hs::{HandshakeHash, HandshakeHashBuffer},
|
||||
msgs::persist,
|
||||
sign, verify, KeyLog,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
@@ -60,7 +59,6 @@ pub(super) async fn handle_server_hello(
|
||||
config: Arc<ClientConfig>,
|
||||
cx: &mut ClientContext<'_>,
|
||||
server_hello: &ServerHelloPayload,
|
||||
resuming_session: Option<persist::Tls13ClientSessionValue>,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
suite: &'static Tls13CipherSuite,
|
||||
@@ -102,8 +100,8 @@ pub(super) async fn handle_server_hello(
|
||||
// };
|
||||
|
||||
// if server_hello.get_psk_index() != Some(0) {
|
||||
// return Err(cx.common.illegal_param("server selected invalid psk").await);
|
||||
// }
|
||||
// return Err(cx.common.illegal_param("server selected invalid
|
||||
// psk").await); }
|
||||
|
||||
// debug!("Resuming using PSK");
|
||||
// // The key schedule has been initialized and set in fill_in_psk_binder()
|
||||
@@ -143,7 +141,6 @@ pub(super) async fn handle_server_hello(
|
||||
|
||||
Ok(Box::new(ExpectEncryptedExtensions {
|
||||
config,
|
||||
resuming_session,
|
||||
server_name,
|
||||
randoms,
|
||||
suite,
|
||||
@@ -170,69 +167,6 @@ async fn validate_server_hello(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn save_kx_hint(config: &ClientConfig, server_name: &ServerName, group: NamedGroup) {
|
||||
// let key = persist::ClientSessionKey::hint_for_server_name(server_name);
|
||||
|
||||
// config
|
||||
// .session_storage
|
||||
// .put(key.get_encoding(), group.get_encoding());
|
||||
// }
|
||||
|
||||
// /// This implements the horrifying TLS1.3 hack where PSK binders have a
|
||||
// /// data dependency on the message they are contained within.
|
||||
// pub(super) fn fill_in_psk_binder(
|
||||
// resuming: &persist::Tls13ClientSessionValue,
|
||||
// transcript: &HandshakeHashBuffer,
|
||||
// hmp: &mut HandshakeMessagePayload,
|
||||
// ) -> KeyScheduleEarly {
|
||||
// // We need to know the hash function of the suite we're trying to resume into.
|
||||
// let hkdf_alg = &resuming.suite().hkdf_algorithm;
|
||||
// let suite_hash = resuming.suite().hash_algorithm();
|
||||
|
||||
// // The binder is calculated over the clienthello, but doesn't include itself or its
|
||||
// // length, or the length of its container.
|
||||
// let binder_plaintext = hmp.get_encoding_for_binder_signing();
|
||||
// let handshake_hash = transcript.get_hash_given(suite_hash, &binder_plaintext);
|
||||
|
||||
// // Run a fake key_schedule to simulate what the server will do if it chooses
|
||||
// // to resume.
|
||||
// let key_schedule = KeyScheduleEarly::new(hkdf_alg, resuming.secret());
|
||||
// let real_binder = key_schedule.resumption_psk_binder_key_and_sign_verify_data(&handshake_hash);
|
||||
|
||||
// if let HandshakePayload::ClientHello(ref mut ch) = hmp.payload {
|
||||
// ch.set_psk_binder(real_binder.as_ref());
|
||||
// };
|
||||
|
||||
// key_schedule
|
||||
// }
|
||||
|
||||
// pub(super) async fn prepare_resumption(
|
||||
// config: &ClientConfig,
|
||||
// cx: &mut ClientContext<'_>,
|
||||
// ticket: Vec<u8>,
|
||||
// resuming_session: &persist::Retrieved<&persist::Tls13ClientSessionValue>,
|
||||
// exts: &mut Vec<ClientExtension>,
|
||||
// doing_retry: bool,
|
||||
// ) {
|
||||
// let resuming_suite = resuming_session.suite();
|
||||
// cx.common.suite = Some(resuming_suite.into());
|
||||
// cx.data.resumption_ciphersuite = Some(resuming_suite.into());
|
||||
|
||||
// // Finally, and only for TLS1.3 with a ticket resumption, include a binder
|
||||
// // for our ticket. This must go last.
|
||||
// //
|
||||
// // Include an empty binder. It gets filled in below because it depends on
|
||||
// // the message it's contained in (!!!).
|
||||
// let obfuscated_ticket_age = resuming_session.obfuscated_ticket_age();
|
||||
|
||||
// let binder_len = resuming_suite.hash_algorithm().output_len();
|
||||
// let binder = vec![0u8; binder_len];
|
||||
|
||||
// let psk_identity = PresharedKeyIdentity::new(ticket, obfuscated_ticket_age);
|
||||
// let psk_ext = PresharedKeyOffer::new(psk_identity, binder);
|
||||
// exts.push(ClientExtension::PresharedKey(psk_ext));
|
||||
// }
|
||||
|
||||
pub(super) async fn emit_fake_ccs(
|
||||
sent_tls13_fake_ccs: &mut bool,
|
||||
common: &mut CommonState,
|
||||
@@ -287,7 +221,6 @@ async fn validate_encrypted_extensions(
|
||||
|
||||
struct ExpectEncryptedExtensions {
|
||||
config: Arc<ClientConfig>,
|
||||
resuming_session: Option<persist::Tls13ClientSessionValue>,
|
||||
server_name: ServerName,
|
||||
randoms: ConnectionRandoms,
|
||||
suite: &'static Tls13CipherSuite,
|
||||
@@ -313,52 +246,19 @@ impl State<ClientConnectionData> for ExpectEncryptedExtensions {
|
||||
validate_encrypted_extensions(cx.common, &self.hello, exts).await?;
|
||||
hs::process_alpn_protocol(cx.common, &self.config, exts.get_alpn_protocol()).await?;
|
||||
|
||||
if let Some(resuming_session) = self.resuming_session {
|
||||
let was_early_traffic = cx.common.early_traffic;
|
||||
if was_early_traffic {
|
||||
if exts.early_data_extension_offered() {
|
||||
cx.data.early_data.accepted();
|
||||
} else {
|
||||
cx.data.early_data.rejected();
|
||||
cx.common.early_traffic = false;
|
||||
}
|
||||
}
|
||||
|
||||
if was_early_traffic && !cx.common.early_traffic {
|
||||
// If no early traffic, set the encryption key for handshakes
|
||||
cx.common.record_layer.set_message_encrypter();
|
||||
}
|
||||
|
||||
cx.common.peer_certificates = Some(resuming_session.server_cert_chain().to_vec());
|
||||
|
||||
// We *don't* reverify the certificate chain here: resumption is a
|
||||
// continuation of the previous session in terms of security policy.
|
||||
let cert_verified = verify::ServerCertVerified::assertion();
|
||||
let sig_verified = verify::HandshakeSignatureValid::assertion();
|
||||
Ok(Box::new(ExpectFinished {
|
||||
config: self.config,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
suite: self.suite,
|
||||
transcript: self.transcript,
|
||||
client_auth: None,
|
||||
cert_verified,
|
||||
sig_verified,
|
||||
}))
|
||||
} else {
|
||||
if exts.early_data_extension_offered() {
|
||||
let msg = "server sent early data extension without resumption".to_string();
|
||||
return Err(Error::PeerMisbehavedError(msg));
|
||||
}
|
||||
Ok(Box::new(ExpectCertificateOrCertReq {
|
||||
config: self.config,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
suite: self.suite,
|
||||
transcript: self.transcript,
|
||||
may_send_sct_list: self.hello.server_may_send_sct_list(),
|
||||
}))
|
||||
if exts.early_data_extension_offered() {
|
||||
let msg = "server sent early data extension without resumption".to_string();
|
||||
return Err(Error::PeerMisbehavedError(msg));
|
||||
}
|
||||
|
||||
Ok(Box::new(ExpectCertificateOrCertReq {
|
||||
config: self.config,
|
||||
server_name: self.server_name,
|
||||
randoms: self.randoms,
|
||||
suite: self.suite,
|
||||
transcript: self.transcript,
|
||||
may_send_sct_list: self.hello.server_may_send_sct_list(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,9 +322,9 @@ impl State<ClientConnectionData> for ExpectCertificateOrCertReq {
|
||||
}
|
||||
}
|
||||
|
||||
// TLS1.3 version of CertificateRequest handling. We then move to expecting the server
|
||||
// Certificate. Unfortunately the CertificateRequest type changed in an annoying way
|
||||
// in TLS1.3.
|
||||
// TLS1.3 version of CertificateRequest handling. We then move to expecting the
|
||||
// server Certificate. Unfortunately the CertificateRequest type changed in an
|
||||
// annoying way in TLS1.3.
|
||||
struct ExpectCertificateRequest {
|
||||
config: Arc<ClientConfig>,
|
||||
server_name: ServerName,
|
||||
@@ -787,8 +687,8 @@ impl State<ClientConnectionData> for ExpectFinished {
|
||||
|
||||
st.transcript.add_message(&m);
|
||||
|
||||
/* The EndOfEarlyData message to server is still encrypted with early data keys,
|
||||
* but appears in the transcript after the server Finished. */
|
||||
/* The EndOfEarlyData message to server is still encrypted with early data
|
||||
* keys, but appears in the transcript after the server Finished. */
|
||||
if cx.common.early_traffic {
|
||||
emit_end_of_early_data_tls13(&mut st.transcript, cx.common).await?;
|
||||
cx.common.early_traffic = false;
|
||||
@@ -893,42 +793,6 @@ impl ExpectTraffic {
|
||||
));
|
||||
}
|
||||
|
||||
// let handshake_hash = self.transcript.get_current_hash();
|
||||
// let secret = self
|
||||
// .key_schedule
|
||||
// .resumption_master_secret_and_derive_ticket_psk(&handshake_hash, &nst.nonce.0);
|
||||
|
||||
// let time_now = match TimeBase::now() {
|
||||
// Ok(t) => t,
|
||||
// #[allow(unused_variables)]
|
||||
// Err(e) => {
|
||||
// debug!("Session not saved: {}", e);
|
||||
// return Ok(());
|
||||
// }
|
||||
// };
|
||||
|
||||
// let value = persist::Tls13ClientSessionValue::new(
|
||||
// self.suite,
|
||||
// nst.ticket.0.clone(),
|
||||
// secret,
|
||||
// cx.common.peer_certificates.clone().unwrap_or_default(),
|
||||
// time_now,
|
||||
// nst.lifetime,
|
||||
// nst.age_add,
|
||||
// nst.get_max_early_data_size().unwrap_or_default(),
|
||||
// );
|
||||
|
||||
// let key = persist::ClientSessionKey::session_for_server_name(&self.server_name);
|
||||
// #[allow(unused_mut)]
|
||||
// let mut ticket = value.get_encoding();
|
||||
|
||||
// let worked = self.session_storage.put(key.get_encoding(), ticket);
|
||||
|
||||
// if worked {
|
||||
// debug!("Ticket saved");
|
||||
// } else {
|
||||
// debug!("Ticket not saved");
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -948,27 +812,6 @@ impl ExpectTraffic {
|
||||
Err(Error::General(
|
||||
"received unsupported key update request from peer".to_string(),
|
||||
))
|
||||
|
||||
// match kur {
|
||||
// KeyUpdateRequest::UpdateNotRequested => {}
|
||||
// KeyUpdateRequest::UpdateRequested => {
|
||||
// self.want_write_key_update = true;
|
||||
// }
|
||||
// _ => {
|
||||
// common
|
||||
// .send_fatal_alert(AlertDescription::IllegalParameter)
|
||||
// .await;
|
||||
// return Err(Error::CorruptMessagePayload(ContentType::Handshake));
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Update our read-side keys.
|
||||
// let new_read_key = self.key_schedule.next_server_application_traffic_secret();
|
||||
// common
|
||||
// .record_layer
|
||||
// .set_message_decrypter(self.suite.derive_decrypter(&new_read_key));
|
||||
|
||||
// Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,10 +865,11 @@ impl State<ClientConnectionData> for ExpectTraffic {
|
||||
// .send_msg_encrypt(Message::build_key_update_notify().into())
|
||||
// .await;
|
||||
|
||||
// let write_key = self.key_schedule.next_client_application_traffic_secret();
|
||||
// let write_key =
|
||||
// self.key_schedule.next_client_application_traffic_secret();
|
||||
// common
|
||||
// .record_layer
|
||||
// .set_message_encrypter(self.suite.derive_encrypter(&write_key));
|
||||
// }
|
||||
// .set_message_encrypter(self.suite.derive_encrypter(&
|
||||
// write_key)); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ impl ConnectionCommon {
|
||||
}
|
||||
|
||||
/// Returns an object that allows reading plaintext.
|
||||
pub fn reader(&mut self) -> Reader {
|
||||
pub fn reader(&mut self) -> Reader<'_> {
|
||||
Reader {
|
||||
received_plaintext: &mut self.common_state.received_plaintext,
|
||||
// Are we done? i.e., have we processed all received messages, and received a
|
||||
|
||||
@@ -301,15 +301,11 @@ mod conn;
|
||||
mod error;
|
||||
mod hash_hs;
|
||||
mod limited_cache;
|
||||
mod msgs;
|
||||
mod rand;
|
||||
mod record_layer;
|
||||
//mod stream;
|
||||
mod vecbuf;
|
||||
pub(crate) use tls_core::verify;
|
||||
#[cfg(test)]
|
||||
mod verifybench;
|
||||
pub(crate) use tls_core::x509;
|
||||
pub(crate) use tls_core::{verify, x509};
|
||||
#[macro_use]
|
||||
mod check;
|
||||
mod bs_debug;
|
||||
@@ -330,7 +326,7 @@ pub mod internal {
|
||||
|
||||
// The public interface is:
|
||||
pub use crate::{
|
||||
anchors::{OwnedTrustAnchor, RootCertStore},
|
||||
anchors::RootCertStore,
|
||||
builder::{ConfigBuilder, WantsCipherSuites, WantsKxGroups, WantsVerifier, WantsVersions},
|
||||
conn::{CommonState, ConnectionCommon, IoState, Reader, SideData},
|
||||
error::Error,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
pub(crate) mod persist;
|
||||
|
||||
#[cfg(test)]
|
||||
mod persist_test;
|
||||
@@ -1,526 +0,0 @@
|
||||
use crate::{client::ServerName, ticketer::TimeBase};
|
||||
use std::cmp;
|
||||
#[cfg(feature = "tls12")]
|
||||
use std::mem;
|
||||
#[cfg(feature = "tls12")]
|
||||
use tls_core::suites::Tls12CipherSuite;
|
||||
use tls_core::{
|
||||
msgs::{
|
||||
base::{PayloadU16, PayloadU8},
|
||||
codec::{Codec, Reader},
|
||||
enums::{CipherSuite, ProtocolVersion},
|
||||
handshake::{CertificatePayload, SessionID},
|
||||
},
|
||||
suites::{SupportedCipherSuite, Tls13CipherSuite},
|
||||
};
|
||||
|
||||
// These are the keys and values we store in session storage.
|
||||
|
||||
// --- Client types ---
|
||||
/// Keys for session resumption and tickets.
|
||||
/// Matching value is a `ClientSessionValue`.
|
||||
#[derive(Debug)]
|
||||
pub struct ClientSessionKey {
|
||||
kind: &'static [u8],
|
||||
name: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Codec for ClientSessionKey {
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
bytes.extend_from_slice(self.kind);
|
||||
bytes.extend_from_slice(&self.name);
|
||||
}
|
||||
|
||||
// Don't need to read these.
|
||||
fn read(_r: &mut Reader) -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientSessionKey {
|
||||
pub fn session_for_server_name(server_name: &ServerName) -> Self {
|
||||
Self {
|
||||
kind: b"session",
|
||||
name: server_name.encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hint_for_server_name(server_name: &ServerName) -> Self {
|
||||
Self {
|
||||
kind: b"kx-hint",
|
||||
name: server_name.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientSessionValue {
|
||||
Tls13(Tls13ClientSessionValue),
|
||||
#[cfg(feature = "tls12")]
|
||||
Tls12(Tls12ClientSessionValue),
|
||||
}
|
||||
|
||||
impl ClientSessionValue {
|
||||
pub fn read(
|
||||
reader: &mut Reader<'_>,
|
||||
suite: CipherSuite,
|
||||
supported: &[SupportedCipherSuite],
|
||||
) -> Option<Self> {
|
||||
match supported.iter().find(|s| s.suite() == suite)? {
|
||||
SupportedCipherSuite::Tls13(inner) => {
|
||||
Tls13ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls13)
|
||||
}
|
||||
#[cfg(feature = "tls12")]
|
||||
SupportedCipherSuite::Tls12(inner) => {
|
||||
Tls12ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn common(&self) -> &ClientSessionCommon {
|
||||
match self {
|
||||
Self::Tls13(inner) => &inner.common,
|
||||
#[cfg(feature = "tls12")]
|
||||
Self::Tls12(inner) => &inner.common,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tls13ClientSessionValue> for ClientSessionValue {
|
||||
fn from(v: Tls13ClientSessionValue) -> Self {
|
||||
Self::Tls13(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl From<Tls12ClientSessionValue> for ClientSessionValue {
|
||||
fn from(v: Tls12ClientSessionValue) -> Self {
|
||||
Self::Tls12(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Retrieved<T> {
|
||||
pub value: T,
|
||||
retrieved_at: TimeBase,
|
||||
}
|
||||
|
||||
impl<T> Retrieved<T> {
|
||||
pub fn new(value: T, retrieved_at: TimeBase) -> Self {
|
||||
Self {
|
||||
value,
|
||||
retrieved_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Retrieved<&Tls13ClientSessionValue> {
|
||||
pub fn obfuscated_ticket_age(&self) -> u32 {
|
||||
let age_secs = self
|
||||
.retrieved_at
|
||||
.as_secs()
|
||||
.saturating_sub(self.value.common.epoch);
|
||||
let age_millis = age_secs as u32 * 1000;
|
||||
age_millis.wrapping_add(self.value.age_add)
|
||||
}
|
||||
}
|
||||
|
||||
impl Retrieved<ClientSessionValue> {
|
||||
pub fn tls13(&self) -> Option<Retrieved<&Tls13ClientSessionValue>> {
|
||||
match &self.value {
|
||||
ClientSessionValue::Tls13(value) => Some(Retrieved::new(value, self.retrieved_at)),
|
||||
#[cfg(feature = "tls12")]
|
||||
ClientSessionValue::Tls12(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_expired(&self) -> bool {
|
||||
let common = self.value.common();
|
||||
common.lifetime_secs != 0
|
||||
&& common.epoch + u64::from(common.lifetime_secs) < self.retrieved_at.as_secs()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Retrieved<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tls13ClientSessionValue {
|
||||
suite: &'static Tls13CipherSuite,
|
||||
age_add: u32,
|
||||
max_early_data_size: u32,
|
||||
pub common: ClientSessionCommon,
|
||||
}
|
||||
|
||||
impl Tls13ClientSessionValue {
|
||||
pub fn new(
|
||||
suite: &'static Tls13CipherSuite,
|
||||
ticket: Vec<u8>,
|
||||
secret: Vec<u8>,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
age_add: u32,
|
||||
max_early_data_size: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
suite,
|
||||
age_add,
|
||||
max_early_data_size,
|
||||
common: ClientSessionCommon::new(
|
||||
ticket,
|
||||
secret,
|
||||
time_now,
|
||||
lifetime_secs,
|
||||
server_cert_chain,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] with an extra `suite` argument.
|
||||
///
|
||||
/// We decode the `suite` argument separately because it allows us to
|
||||
/// decide whether we're decoding an 1.2 or 1.3 session value.
|
||||
pub fn read(suite: &'static Tls13CipherSuite, r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
suite,
|
||||
age_add: u32::read(r)?,
|
||||
max_early_data_size: u32::read(r)?,
|
||||
common: ClientSessionCommon::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inherent implementation of the [`Codec::get_encoding()`] method.
|
||||
///
|
||||
/// (See `read()` for why this is inherent here.)
|
||||
pub fn get_encoding(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
self.suite.common.suite.encode(&mut bytes);
|
||||
self.age_add.encode(&mut bytes);
|
||||
self.max_early_data_size.encode(&mut bytes);
|
||||
self.common.encode(&mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn max_early_data_size(&self) -> u32 {
|
||||
self.max_early_data_size
|
||||
}
|
||||
|
||||
pub fn suite(&self) -> &'static Tls13CipherSuite {
|
||||
self.suite
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Tls13ClientSessionValue {
|
||||
type Target = ClientSessionCommon;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.common
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
#[derive(Debug)]
|
||||
pub struct Tls12ClientSessionValue {
|
||||
suite: &'static Tls12CipherSuite,
|
||||
pub session_id: SessionID,
|
||||
extended_ms: bool,
|
||||
pub common: ClientSessionCommon,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl Tls12ClientSessionValue {
|
||||
pub fn new(
|
||||
suite: &'static Tls12CipherSuite,
|
||||
session_id: SessionID,
|
||||
ticket: Vec<u8>,
|
||||
master_secret: Vec<u8>,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
extended_ms: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
suite,
|
||||
session_id,
|
||||
extended_ms,
|
||||
common: ClientSessionCommon::new(
|
||||
ticket,
|
||||
master_secret,
|
||||
time_now,
|
||||
lifetime_secs,
|
||||
server_cert_chain,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] with an extra `suite` argument.
|
||||
///
|
||||
/// We decode the `suite` argument separately because it allows us to
|
||||
/// decide whether we're decoding an 1.2 or 1.3 session value.
|
||||
fn read(suite: &'static Tls12CipherSuite, r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
suite,
|
||||
session_id: SessionID::read(r)?,
|
||||
extended_ms: u8::read(r)? == 1,
|
||||
common: ClientSessionCommon::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inherent implementation of the [`Codec::get_encoding()`] method.
|
||||
///
|
||||
/// (See `read()` for why this is inherent here.)
|
||||
pub fn get_encoding(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
self.suite.common.suite.encode(&mut bytes);
|
||||
self.session_id.encode(&mut bytes);
|
||||
(if self.extended_ms { 1u8 } else { 0u8 }).encode(&mut bytes);
|
||||
self.common.encode(&mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn take_ticket(&mut self) -> Vec<u8> {
|
||||
mem::take(&mut self.common.ticket.0)
|
||||
}
|
||||
|
||||
pub fn extended_ms(&self) -> bool {
|
||||
self.extended_ms
|
||||
}
|
||||
|
||||
pub fn suite(&self) -> &'static Tls12CipherSuite {
|
||||
self.suite
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl std::ops::Deref for Tls12ClientSessionValue {
|
||||
type Target = ClientSessionCommon;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.common
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientSessionCommon {
|
||||
ticket: PayloadU16,
|
||||
secret: PayloadU8,
|
||||
epoch: u64,
|
||||
lifetime_secs: u32,
|
||||
server_cert_chain: CertificatePayload,
|
||||
}
|
||||
|
||||
impl ClientSessionCommon {
|
||||
fn new(
|
||||
ticket: Vec<u8>,
|
||||
secret: Vec<u8>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ticket: PayloadU16(ticket),
|
||||
secret: PayloadU8(secret),
|
||||
epoch: time_now.as_secs(),
|
||||
lifetime_secs: cmp::min(lifetime_secs, MAX_TICKET_LIFETIME),
|
||||
server_cert_chain,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] is inherent here to avoid leaking the [`Codec`]
|
||||
/// implementation through [`Deref`] implementations on
|
||||
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
|
||||
fn read(r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
ticket: PayloadU16::read(r)?,
|
||||
secret: PayloadU8::read(r)?,
|
||||
epoch: u64::read(r)?,
|
||||
lifetime_secs: u32::read(r)?,
|
||||
server_cert_chain: CertificatePayload::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// [`Codec::encode()`] is inherent here to avoid leaking the [`Codec`]
|
||||
/// implementation through [`Deref`] implementations on
|
||||
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
self.ticket.encode(bytes);
|
||||
self.secret.encode(bytes);
|
||||
self.epoch.encode(bytes);
|
||||
self.lifetime_secs.encode(bytes);
|
||||
self.server_cert_chain.encode(bytes);
|
||||
}
|
||||
|
||||
pub fn server_cert_chain(&self) -> &[tls_core::key::Certificate] {
|
||||
self.server_cert_chain.as_ref()
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &[u8] {
|
||||
self.secret.0.as_ref()
|
||||
}
|
||||
|
||||
pub fn ticket(&self) -> &[u8] {
|
||||
self.ticket.0.as_ref()
|
||||
}
|
||||
|
||||
/// Test only: wind back epoch by delta seconds.
|
||||
pub fn rewind_epoch(&mut self, delta: u32) {
|
||||
self.epoch -= delta as u64;
|
||||
}
|
||||
}
|
||||
|
||||
static MAX_TICKET_LIFETIME: u32 = 7 * 24 * 60 * 60;
|
||||
|
||||
/// This is the maximum allowed skew between server and client clocks, over
|
||||
/// the maximum ticket lifetime period. This encompasses TCP retransmission
|
||||
/// times in case packet loss occurs when the client sends the ClientHello
|
||||
/// or receives the NewSessionTicket, _and_ actual clock skew over this period.
|
||||
static MAX_FRESHNESS_SKEW_MS: u32 = 60 * 1000;
|
||||
|
||||
// --- Server types ---
|
||||
pub type ServerSessionKey = SessionID;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ServerSessionValue {
|
||||
pub sni: Option<webpki::DnsName>,
|
||||
pub version: ProtocolVersion,
|
||||
pub cipher_suite: CipherSuite,
|
||||
pub master_secret: PayloadU8,
|
||||
pub extended_ms: bool,
|
||||
pub client_cert_chain: Option<CertificatePayload>,
|
||||
pub alpn: Option<PayloadU8>,
|
||||
pub application_data: PayloadU16,
|
||||
pub creation_time_sec: u64,
|
||||
pub age_obfuscation_offset: u32,
|
||||
freshness: Option<bool>,
|
||||
}
|
||||
|
||||
impl Codec for ServerSessionValue {
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
if let Some(ref sni) = self.sni {
|
||||
1u8.encode(bytes);
|
||||
let sni_bytes: &str = sni.as_ref().into();
|
||||
PayloadU8::new(Vec::from(sni_bytes)).encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
self.version.encode(bytes);
|
||||
self.cipher_suite.encode(bytes);
|
||||
self.master_secret.encode(bytes);
|
||||
(if self.extended_ms { 1u8 } else { 0u8 }).encode(bytes);
|
||||
if let Some(ref chain) = self.client_cert_chain {
|
||||
1u8.encode(bytes);
|
||||
chain.encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
if let Some(ref alpn) = self.alpn {
|
||||
1u8.encode(bytes);
|
||||
alpn.encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
self.application_data.encode(bytes);
|
||||
self.creation_time_sec.encode(bytes);
|
||||
self.age_obfuscation_offset.encode(bytes);
|
||||
}
|
||||
|
||||
fn read(r: &mut Reader) -> Option<Self> {
|
||||
let has_sni = u8::read(r)?;
|
||||
let sni = if has_sni == 1 {
|
||||
let dns_name = PayloadU8::read(r)?;
|
||||
let dns_name = webpki::DnsNameRef::try_from_ascii(&dns_name.0).ok()?;
|
||||
Some(dns_name.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let v = ProtocolVersion::read(r)?;
|
||||
let cs = CipherSuite::read(r)?;
|
||||
let ms = PayloadU8::read(r)?;
|
||||
let ems = u8::read(r)?;
|
||||
let has_ccert = u8::read(r)? == 1;
|
||||
let ccert = if has_ccert {
|
||||
Some(CertificatePayload::read(r)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let has_alpn = u8::read(r)? == 1;
|
||||
let alpn = if has_alpn {
|
||||
Some(PayloadU8::read(r)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let application_data = PayloadU16::read(r)?;
|
||||
let creation_time_sec = u64::read(r)?;
|
||||
let age_obfuscation_offset = u32::read(r)?;
|
||||
|
||||
Some(Self {
|
||||
sni,
|
||||
version: v,
|
||||
cipher_suite: cs,
|
||||
master_secret: ms,
|
||||
extended_ms: ems == 1u8,
|
||||
client_cert_chain: ccert,
|
||||
alpn,
|
||||
application_data,
|
||||
creation_time_sec,
|
||||
age_obfuscation_offset,
|
||||
freshness: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerSessionValue {
|
||||
pub fn new(
|
||||
sni: Option<&webpki::DnsName>,
|
||||
v: ProtocolVersion,
|
||||
cs: CipherSuite,
|
||||
ms: Vec<u8>,
|
||||
client_cert_chain: Option<CertificatePayload>,
|
||||
alpn: Option<Vec<u8>>,
|
||||
application_data: Vec<u8>,
|
||||
creation_time: TimeBase,
|
||||
age_obfuscation_offset: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
sni: sni.cloned(),
|
||||
version: v,
|
||||
cipher_suite: cs,
|
||||
master_secret: PayloadU8::new(ms),
|
||||
extended_ms: false,
|
||||
client_cert_chain,
|
||||
alpn: alpn.map(PayloadU8::new),
|
||||
application_data: PayloadU16::new(application_data),
|
||||
creation_time_sec: creation_time.as_secs(),
|
||||
age_obfuscation_offset,
|
||||
freshness: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_extended_ms_used(&mut self) {
|
||||
self.extended_ms = true;
|
||||
}
|
||||
|
||||
pub fn set_freshness(mut self, obfuscated_client_age_ms: u32, time_now: TimeBase) -> Self {
|
||||
let client_age_ms = obfuscated_client_age_ms.wrapping_sub(self.age_obfuscation_offset);
|
||||
let server_age_ms =
|
||||
(time_now.as_secs().saturating_sub(self.creation_time_sec) as u32).saturating_mul(1000);
|
||||
|
||||
let age_difference = if client_age_ms < server_age_ms {
|
||||
server_age_ms - client_age_ms
|
||||
} else {
|
||||
client_age_ms - server_age_ms
|
||||
};
|
||||
|
||||
self.freshness = Some(age_difference <= MAX_FRESHNESS_SKEW_MS);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_fresh(&self) -> bool {
|
||||
self.freshness.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
use super::persist::*;
|
||||
use crate::ticketer::TimeBase;
|
||||
use std::convert::TryInto;
|
||||
use tls_core::{
|
||||
key::Certificate,
|
||||
msgs::{
|
||||
codec::{Codec, Reader},
|
||||
enums::*,
|
||||
},
|
||||
suites::TLS13_AES_128_GCM_SHA256,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn clientsessionkey_is_debug() {
|
||||
let name = "hello".try_into().unwrap();
|
||||
let csk = ClientSessionKey::session_for_server_name(&name);
|
||||
println!("{:?}", csk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clientsessionkey_cannot_be_read() {
|
||||
let bytes = [0; 1];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
assert!(ClientSessionKey::read(&mut rd).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clientsessionvalue_is_debug() {
|
||||
let csv = ClientSessionValue::from(Tls13ClientSessionValue::new(
|
||||
TLS13_AES_128_GCM_SHA256.tls13().unwrap(),
|
||||
vec![],
|
||||
vec![1, 2, 3],
|
||||
vec![Certificate(b"abc".to_vec()), Certificate(b"def".to_vec())],
|
||||
TimeBase::now().unwrap(),
|
||||
15,
|
||||
10,
|
||||
128,
|
||||
));
|
||||
println!("{:?}", csv);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_is_debug() {
|
||||
let ssv = ServerSessionValue::new(
|
||||
None,
|
||||
ProtocolVersion::TLSv1_3,
|
||||
CipherSuite::TLS13_AES_128_GCM_SHA256,
|
||||
vec![1, 2, 3],
|
||||
None,
|
||||
None,
|
||||
vec![4, 5, 6],
|
||||
TimeBase::now().unwrap(),
|
||||
0x12345678,
|
||||
);
|
||||
println!("{:?}", ssv);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_no_sni() {
|
||||
let bytes = [
|
||||
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
|
||||
];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
let ssv = ServerSessionValue::read(&mut rd).unwrap();
|
||||
assert_eq!(ssv.get_encoding(), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_with_cert() {
|
||||
let bytes = [
|
||||
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
|
||||
];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
let ssv = ServerSessionValue::read(&mut rd).unwrap();
|
||||
assert_eq!(ssv.get_encoding(), bytes);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use ring::{
|
||||
io::der,
|
||||
signature::{self, EcdsaKeyPair, Ed25519KeyPair, RsaKeyPair},
|
||||
};
|
||||
use rustls_pki_types as pki_types;
|
||||
use std::{convert::TryFrom, error::Error as StdError, fmt, sync::Arc};
|
||||
use tls_core::{
|
||||
key,
|
||||
@@ -71,51 +72,6 @@ impl CertifiedKey {
|
||||
pub fn end_entity_cert(&self) -> Result<&key::Certificate, SignError> {
|
||||
self.cert.first().ok_or(SignError(()))
|
||||
}
|
||||
|
||||
/// Check the certificate chain for validity:
|
||||
/// - it should be non-empty list
|
||||
/// - the first certificate should be parsable as a x509v3,
|
||||
/// - the first certificate should quote the given server name
|
||||
/// (if provided)
|
||||
///
|
||||
/// These checks are not security-sensitive. They are the
|
||||
/// *server* attempting to detect accidental misconfiguration.
|
||||
pub(crate) fn cross_check_end_entity_cert(
|
||||
&self,
|
||||
name: Option<webpki::DnsNameRef>,
|
||||
) -> Result<(), Error> {
|
||||
// Always reject an empty certificate chain.
|
||||
let end_entity_cert = self.end_entity_cert().map_err(|SignError(())| {
|
||||
Error::General("No end-entity certificate in certificate chain".to_string())
|
||||
})?;
|
||||
|
||||
// Reject syntactically-invalid end-entity certificates.
|
||||
let end_entity_cert =
|
||||
webpki::EndEntityCert::try_from(end_entity_cert.as_ref()).map_err(|_| {
|
||||
Error::General(
|
||||
"End-entity certificate in certificate \
|
||||
chain is syntactically invalid"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Some(name) = name {
|
||||
// If SNI was offered then the certificate must be valid for
|
||||
// that hostname. Note that this doesn't fully validate that the
|
||||
// certificate is valid; it only validates that the name is one
|
||||
// that the certificate is valid for, if the certificate is
|
||||
// valid.
|
||||
if end_entity_cert.verify_is_valid_for_dns_name(name).is_err() {
|
||||
return Err(Error::General(
|
||||
"The server certificate is not \
|
||||
valid for the given name"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse `der` as any supported key encoding/type, returning
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
// This program does benchmarking of the functions in verify.rs,
|
||||
// that do certificate chain validation and signature verification.
|
||||
//
|
||||
// Note: we don't use any of the standard 'cargo bench', 'test::Bencher',
|
||||
// etc. because it's unstable at the time of writing.
|
||||
|
||||
use crate::{anchors, verify, verify::ServerCertVerifier, OwnedTrustAnchor};
|
||||
use std::convert::TryInto;
|
||||
use web_time::{Duration, Instant, SystemTime};
|
||||
|
||||
use webpki_roots;
|
||||
|
||||
fn duration_nanos(d: Duration) -> u64 {
|
||||
((d.as_secs() as f64) * 1e9 + (d.subsec_nanos() as f64)) as u64
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reddit_cert() {
|
||||
Context::new(
|
||||
"reddit",
|
||||
"reddit.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-reddit.0.der"),
|
||||
include_bytes!("testdata/cert-reddit.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_cert() {
|
||||
Context::new(
|
||||
"github",
|
||||
"github.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-github.0.der"),
|
||||
include_bytes!("testdata/cert-github.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arstechnica_cert() {
|
||||
Context::new(
|
||||
"arstechnica",
|
||||
"arstechnica.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-arstechnica.0.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.1.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.2.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.3.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_twitter_cert() {
|
||||
Context::new(
|
||||
"twitter",
|
||||
"twitter.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-twitter.0.der"),
|
||||
include_bytes!("testdata/cert-twitter.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wikipedia_cert() {
|
||||
Context::new(
|
||||
"wikipedia",
|
||||
"wikipedia.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-wikipedia.0.der"),
|
||||
include_bytes!("testdata/cert-wikipedia.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_google_cert() {
|
||||
Context::new(
|
||||
"google",
|
||||
"www.google.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-google.0.der"),
|
||||
include_bytes!("testdata/cert-google.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hn_cert() {
|
||||
Context::new(
|
||||
"hn",
|
||||
"news.ycombinator.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-hn.0.der"),
|
||||
include_bytes!("testdata/cert-hn.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stackoverflow_cert() {
|
||||
Context::new(
|
||||
"stackoverflow",
|
||||
"stackoverflow.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-stackoverflow.0.der"),
|
||||
include_bytes!("testdata/cert-stackoverflow.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duckduckgo_cert() {
|
||||
Context::new(
|
||||
"duckduckgo",
|
||||
"duckduckgo.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-duckduckgo.0.der"),
|
||||
include_bytes!("testdata/cert-duckduckgo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rustlang_cert() {
|
||||
Context::new(
|
||||
"rustlang",
|
||||
"www.rust-lang.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-rustlang.0.der"),
|
||||
include_bytes!("testdata/cert-rustlang.1.der"),
|
||||
include_bytes!("testdata/cert-rustlang.2.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wapo_cert() {
|
||||
Context::new(
|
||||
"wapo",
|
||||
"www.washingtonpost.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-wapo.0.der"),
|
||||
include_bytes!("testdata/cert-wapo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
struct Context {
|
||||
name: &'static str,
|
||||
domain: &'static str,
|
||||
roots: anchors::RootCertStore,
|
||||
chain: Vec<tls_core::key::Certificate>,
|
||||
now: SystemTime,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new(name: &'static str, domain: &'static str, certs: &[&'static [u8]]) -> Self {
|
||||
let mut roots = anchors::RootCertStore::empty();
|
||||
roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
Self {
|
||||
name,
|
||||
domain,
|
||||
roots,
|
||||
chain: certs
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|bytes| tls_core::key::Certificate(bytes.to_vec()))
|
||||
.collect(),
|
||||
now: SystemTime::UNIX_EPOCH + Duration::from_secs(1640870720),
|
||||
}
|
||||
}
|
||||
|
||||
fn bench(&self, count: usize) {
|
||||
let verifier = verify::WebPkiVerifier::new(self.roots.clone(), None);
|
||||
const SCTS: &[&[u8]] = &[];
|
||||
const OCSP_RESPONSE: &[u8] = &[];
|
||||
let mut times = Vec::new();
|
||||
|
||||
let (end_entity, intermediates) = self.chain.split_first().unwrap();
|
||||
for _ in 0..count {
|
||||
let start = Instant::now();
|
||||
let server_name = self.domain.try_into().unwrap();
|
||||
verifier
|
||||
.verify_server_cert(
|
||||
end_entity,
|
||||
intermediates,
|
||||
&server_name,
|
||||
&mut SCTS.iter().copied(),
|
||||
OCSP_RESPONSE,
|
||||
self.now,
|
||||
)
|
||||
.unwrap();
|
||||
times.push(duration_nanos(Instant::now().duration_since(start)));
|
||||
}
|
||||
|
||||
println!(
|
||||
"verify_server_cert({}): min {:?}us",
|
||||
self.name,
|
||||
times.iter().min().unwrap() / 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -780,14 +780,12 @@ async fn client_checks_server_certificate_with_given_name() {
|
||||
let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
|
||||
|
||||
let err = do_handshake_until_error(&mut client, &mut server).await;
|
||||
assert_eq!(
|
||||
assert!(matches!(
|
||||
err,
|
||||
Err(ErrorFromPeer::Client(Error::CoreError(
|
||||
tls_core::Error::InvalidCertificateData(
|
||||
"invalid peer certificate: CertNotValidForName".into(),
|
||||
)
|
||||
tls_core::Error::InvalidCertificateData(_)
|
||||
)))
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -888,7 +886,7 @@ async fn client_error_is_sticky() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[allow(clippy::no_effect)]
|
||||
#[allow(clippy::unnecessary_operation)]
|
||||
async fn client_is_send() {
|
||||
let (client, _) = make_pair(KeyType::Rsa).await;
|
||||
&client as &dyn Send;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use futures::{AsyncRead, AsyncWrite};
|
||||
use rustls::{server::AllowAnyAuthenticatedClient, ServerConfig, ServerConnection};
|
||||
use rustls_pki_types::CertificateDer;
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
io,
|
||||
@@ -15,6 +16,7 @@ use tls_client::{
|
||||
Certificate, ClientConfig, ClientConnection, Error, PrivateKey, RootCertStore,
|
||||
RustCryptoBackend,
|
||||
};
|
||||
use webpki::anchor_from_trusted_cert;
|
||||
|
||||
macro_rules! embed_files {
|
||||
(
|
||||
@@ -409,9 +411,17 @@ pub fn finish_client_config(
|
||||
kt: KeyType,
|
||||
config: tls_client::ConfigBuilder<tls_client::WantsVerifier>,
|
||||
) -> ClientConfig {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
let mut rootbuf = io::BufReader::new(kt.bytes_for("ca.cert"));
|
||||
root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut rootbuf).unwrap());
|
||||
let roots = rustls_pemfile::certs(&mut rootbuf)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|cert| {
|
||||
let der = CertificateDer::from_slice(&cert);
|
||||
anchor_from_trusted_cert(&der).unwrap().to_owned()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let root_store = RootCertStore { roots };
|
||||
|
||||
config
|
||||
.with_root_certificates(root_store)
|
||||
@@ -422,9 +432,17 @@ pub fn finish_client_config_with_creds(
|
||||
kt: KeyType,
|
||||
config: tls_client::ConfigBuilder<tls_client::WantsVerifier>,
|
||||
) -> ClientConfig {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
let mut rootbuf = io::BufReader::new(kt.bytes_for("ca.cert"));
|
||||
root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut rootbuf).unwrap());
|
||||
let roots = rustls_pemfile::certs(&mut rootbuf)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|cert| {
|
||||
let der = CertificateDer::from_slice(&cert);
|
||||
anchor_from_trusted_cert(&der).unwrap().to_owned()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let root_store = RootCertStore { roots };
|
||||
|
||||
config
|
||||
.with_root_certificates(root_store)
|
||||
|
||||
@@ -35,4 +35,5 @@ sha2 = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
web-time = { workspace = true }
|
||||
webpki = { workspace = true, features = ["alloc", "std"] }
|
||||
rustls-webpki = { workspace = true, features = ["ring"] }
|
||||
rustls-pki-types = { workspace = true }
|
||||
|
||||
@@ -1,65 +1,16 @@
|
||||
use rustls_pki_types::TrustAnchor;
|
||||
|
||||
use crate::{
|
||||
msgs::handshake::{DistinguishedName, DistinguishedNames},
|
||||
x509,
|
||||
};
|
||||
|
||||
/// A trust anchor, commonly known as a "Root Certificate."
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OwnedTrustAnchor {
|
||||
subject: Vec<u8>,
|
||||
spki: Vec<u8>,
|
||||
name_constraints: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl OwnedTrustAnchor {
|
||||
/// Get a `webpki::TrustAnchor` by borrowing the owned elements.
|
||||
pub(crate) fn to_trust_anchor(&self) -> webpki::TrustAnchor {
|
||||
webpki::TrustAnchor {
|
||||
subject: &self.subject,
|
||||
spki: &self.spki,
|
||||
name_constraints: self.name_constraints.as_deref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs an `OwnedTrustAnchor` from its components.
|
||||
///
|
||||
/// `subject` is the subject field of the trust anchor.
|
||||
///
|
||||
/// `spki` is the `subjectPublicKeyInfo` field of the trust anchor.
|
||||
///
|
||||
/// `name_constraints` is the value of a DER-encoded name constraints to
|
||||
/// apply for this trust anchor, if any.
|
||||
pub fn from_subject_spki_name_constraints(
|
||||
subject: impl Into<Vec<u8>>,
|
||||
spki: impl Into<Vec<u8>>,
|
||||
name_constraints: Option<impl Into<Vec<u8>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
subject: subject.into(),
|
||||
spki: spki.into(),
|
||||
name_constraints: name_constraints.map(|x| x.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur during operations with RootCertStore
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum RootCertStoreError {
|
||||
#[error(transparent)]
|
||||
WebpkiError(#[from] webpki::Error),
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error("Unexpected PEM certificate count. Expected 1 certificate, got {0}")]
|
||||
PemCertUnexpectedCount(usize),
|
||||
}
|
||||
|
||||
/// A container for root certificates able to provide a root-of-trust
|
||||
/// for connection authentication.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RootCertStore {
|
||||
/// The list of roots.
|
||||
pub roots: Vec<OwnedTrustAnchor>,
|
||||
pub roots: Vec<TrustAnchor<'static>>,
|
||||
}
|
||||
|
||||
impl RootCertStore {
|
||||
@@ -91,100 +42,4 @@ impl RootCertStore {
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
/// Add a single DER-encoded certificate to the store.
|
||||
pub fn add(&mut self, der: &crate::key::Certificate) -> Result<(), RootCertStoreError> {
|
||||
let ta = webpki::TrustAnchor::try_from_cert_der(&der.0)?;
|
||||
let ota = OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject,
|
||||
ta.spki,
|
||||
ta.name_constraints,
|
||||
);
|
||||
self.roots.push(ota);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a single PEM-encoded certificate to the store.
|
||||
pub fn add_pem(&mut self, pem: &str) -> Result<(), RootCertStoreError> {
|
||||
let mut certificates = rustls_pemfile::certs(&mut pem.as_bytes())?;
|
||||
|
||||
if certificates.len() != 1 {
|
||||
return Err(RootCertStoreError::PemCertUnexpectedCount(
|
||||
certificates.len(),
|
||||
));
|
||||
}
|
||||
|
||||
self.add(&crate::key::Certificate(certificates.remove(0)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds all the given TrustAnchors `anchors`. This does not
|
||||
/// fail.
|
||||
pub fn add_server_trust_anchors(
|
||||
&mut self,
|
||||
trust_anchors: impl Iterator<Item = OwnedTrustAnchor>,
|
||||
) {
|
||||
self.roots.extend(trust_anchors)
|
||||
}
|
||||
|
||||
/// Parse the given DER-encoded certificates and add all that can be parsed
|
||||
/// in a best-effort fashion.
|
||||
///
|
||||
/// This is because large collections of root certificates often
|
||||
/// include ancient or syntactically invalid certificates.
|
||||
///
|
||||
/// Returns the number of certificates added, and the number that were ignored.
|
||||
pub fn add_parsable_certificates(&mut self, der_certs: &[Vec<u8>]) -> (usize, usize) {
|
||||
let mut valid_count = 0;
|
||||
let mut invalid_count = 0;
|
||||
|
||||
for der_cert in der_certs {
|
||||
match self.add(&crate::key::Certificate(der_cert.clone())) {
|
||||
Ok(_) => valid_count += 1,
|
||||
Err(_err) => invalid_count += 1,
|
||||
}
|
||||
}
|
||||
|
||||
(valid_count, invalid_count)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const CA_PEM_CERT: &[u8] = include_bytes!("../testdata/cert-digicert.pem");
|
||||
|
||||
#[test]
|
||||
fn test_add_pem_ok() {
|
||||
let pem = std::str::from_utf8(CA_PEM_CERT).unwrap();
|
||||
assert!(RootCertStore::empty().add_pem(pem).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_pem_err_bad_cert() {
|
||||
assert_eq!(
|
||||
RootCertStore::empty()
|
||||
.add_pem("bad pem")
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"Unexpected PEM certificate count. Expected 1 certificate, got 0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_pem_err_more_than_one_cert() {
|
||||
let pem1 = std::str::from_utf8(CA_PEM_CERT).unwrap();
|
||||
let pem2 = pem1;
|
||||
|
||||
assert_eq!(
|
||||
RootCertStore::empty()
|
||||
.add_pem((pem1.to_owned() + pem2).as_str())
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"Unexpected PEM certificate count. Expected 1 certificate, got 2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{error::Error as StdError, fmt};
|
||||
|
||||
use crate::verify;
|
||||
use rustls_pki_types as pki_types;
|
||||
|
||||
/// Encodes ways a client can know the expected name of the server.
|
||||
///
|
||||
@@ -26,20 +26,16 @@ use crate::verify;
|
||||
/// ```
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ServerName {
|
||||
/// The server is identified by a DNS name. The name
|
||||
/// is sent in the TLS Server Name Indication (SNI)
|
||||
/// extension.
|
||||
DnsName(verify::DnsName),
|
||||
}
|
||||
pub struct ServerName(pub(crate) pki_types::ServerName<'static>);
|
||||
|
||||
impl ServerName {
|
||||
/// Return the name that should go in the SNI extension.
|
||||
/// If [`None`] is returned, the SNI extension is not included
|
||||
/// in the handshake.
|
||||
pub fn for_sni(&self) -> Option<webpki::DnsNameRef> {
|
||||
match self {
|
||||
Self::DnsName(dns_name) => Some(dns_name.0.as_ref()),
|
||||
pub fn for_sni(&self) -> Option<pki_types::DnsName<'static>> {
|
||||
match &self.0 {
|
||||
pki_types::ServerName::DnsName(dns_name) => Some(dns_name.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,13 +45,17 @@ impl ServerName {
|
||||
DnsName = 0x01,
|
||||
}
|
||||
|
||||
let Self::DnsName(dns_name) = self;
|
||||
let bytes = dns_name.0.as_ref();
|
||||
let bytes: &[u8] = match &self.0 {
|
||||
pki_types::ServerName::DnsName(dns_name) => dns_name.as_ref().as_ref(),
|
||||
pki_types::ServerName::IpAddress(pki_types::IpAddr::V4(ip)) => ip.as_ref(),
|
||||
pki_types::ServerName::IpAddress(pki_types::IpAddr::V6(ip)) => ip.as_ref(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut r = Vec::with_capacity(2 + bytes.as_ref().len());
|
||||
let mut r = Vec::with_capacity(2 + bytes.len());
|
||||
r.push(UniqueTypeCode::DnsName as u8);
|
||||
r.push(bytes.as_ref().len() as u8);
|
||||
r.extend_from_slice(bytes.as_ref());
|
||||
r.push(bytes.len() as u8);
|
||||
r.extend_from_slice(bytes);
|
||||
|
||||
r
|
||||
}
|
||||
@@ -66,9 +66,9 @@ impl ServerName {
|
||||
impl TryFrom<&str> for ServerName {
|
||||
type Error = InvalidDnsNameError;
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
match webpki::DnsNameRef::try_from_ascii_str(s) {
|
||||
Ok(dns) => Ok(Self::DnsName(verify::DnsName(dns.into()))),
|
||||
Err(webpki::InvalidDnsNameError) => Err(InvalidDnsNameError),
|
||||
match pki_types::DnsName::try_from(s) {
|
||||
Ok(dns) => Ok(Self(pki_types::ServerName::DnsName(dns.to_owned()))),
|
||||
Err(_) => Err(InvalidDnsNameError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::msgs::enums::{AlertDescription, ContentType, HandshakeType};
|
||||
use std::{error::Error as StdError, fmt, time::SystemTimeError};
|
||||
use std::{error::Error as StdError, fmt};
|
||||
use web_time::SystemTimeError;
|
||||
|
||||
/// rustls reports protocol errors using this type.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -41,8 +42,9 @@ pub enum Error {
|
||||
/// We couldn't decrypt a message. This is invariably fatal.
|
||||
DecryptError,
|
||||
|
||||
/// We couldn't encrypt a message because it was larger than the allowed message size.
|
||||
/// This should never happen if the application is using valid record sizes.
|
||||
/// We couldn't encrypt a message because it was larger than the allowed
|
||||
/// message size. This should never happen if the application is using
|
||||
/// valid record sizes.
|
||||
EncryptError,
|
||||
|
||||
/// The peer doesn't support a protocol version/feature we require.
|
||||
|
||||
@@ -7,7 +7,7 @@ pub struct Reader<'a> {
|
||||
}
|
||||
|
||||
impl Reader<'_> {
|
||||
pub fn init(bytes: &[u8]) -> Reader {
|
||||
pub fn init(bytes: &[u8]) -> Reader<'_> {
|
||||
Reader {
|
||||
buf: bytes,
|
||||
offs: 0,
|
||||
@@ -42,7 +42,7 @@ impl Reader<'_> {
|
||||
self.offs
|
||||
}
|
||||
|
||||
pub fn sub(&mut self, len: usize) -> Option<Reader> {
|
||||
pub fn sub(&mut self, len: usize) -> Option<Reader<'_>> {
|
||||
self.take(len).map(Reader::init)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use rustls_pki_types as pki_types;
|
||||
|
||||
use crate::{
|
||||
key,
|
||||
msgs::{
|
||||
@@ -249,14 +251,14 @@ impl DecomposedSignatureScheme for SignatureScheme {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ServerNamePayload {
|
||||
// Stored twice, bytes so we can round-trip, and DnsName for use
|
||||
HostName((PayloadU16, webpki::DnsName)),
|
||||
HostName((PayloadU16, pki_types::DnsName<'static>)),
|
||||
Unknown(Payload),
|
||||
}
|
||||
|
||||
impl ServerNamePayload {
|
||||
pub fn new_hostname(hostname: webpki::DnsName) -> Self {
|
||||
pub fn new_hostname(hostname: pki_types::DnsName<'static>) -> Self {
|
||||
let raw = {
|
||||
let s: &str = hostname.as_ref().into();
|
||||
let s: &str = hostname.as_ref();
|
||||
PayloadU16::new(s.as_bytes().into())
|
||||
};
|
||||
Self::HostName((raw, hostname))
|
||||
@@ -266,8 +268,8 @@ impl ServerNamePayload {
|
||||
let raw = PayloadU16::read(r)?;
|
||||
|
||||
let dns_name = {
|
||||
match webpki::DnsNameRef::try_from_ascii(&raw.0) {
|
||||
Ok(dns_name) => dns_name.into(),
|
||||
match pki_types::DnsName::try_from(raw.0.as_slice()) {
|
||||
Ok(dns_name) => dns_name.to_owned(),
|
||||
Err(_) => {
|
||||
warn!("Illegal SNI hostname received {:?}", raw.0);
|
||||
return None;
|
||||
@@ -313,11 +315,12 @@ declare_u16_vec!(ServerNameRequest, ServerName);
|
||||
|
||||
pub trait ConvertServerNameList {
|
||||
fn has_duplicate_names_for_type(&self) -> bool;
|
||||
fn get_single_hostname(&self) -> Option<webpki::DnsNameRef>;
|
||||
fn get_single_hostname(&self) -> Option<pki_types::DnsName<'static>>;
|
||||
}
|
||||
|
||||
impl ConvertServerNameList for ServerNameRequest {
|
||||
/// RFC6066: "The ServerNameList MUST NOT contain more than one name of the same name_type."
|
||||
/// RFC6066: "The ServerNameList MUST NOT contain more than one name of the
|
||||
/// same name_type."
|
||||
fn has_duplicate_names_for_type(&self) -> bool {
|
||||
let mut seen = collections::HashSet::new();
|
||||
|
||||
@@ -330,10 +333,10 @@ impl ConvertServerNameList for ServerNameRequest {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_single_hostname(&self) -> Option<webpki::DnsNameRef> {
|
||||
fn only_dns_hostnames(name: &ServerName) -> Option<webpki::DnsNameRef> {
|
||||
fn get_single_hostname(&self) -> Option<pki_types::DnsName<'static>> {
|
||||
fn only_dns_hostnames(name: &ServerName) -> Option<pki_types::DnsName<'static>> {
|
||||
if let ServerNamePayload::HostName((_, ref dns)) = name.payload {
|
||||
Some(dns.as_ref())
|
||||
Some(dns.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -694,16 +697,16 @@ impl Codec for ClientExtension {
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_hostname_trailing_dot_for_sni(dns_name: webpki::DnsNameRef) -> webpki::DnsName {
|
||||
let dns_name_str: &str = dns_name.into();
|
||||
fn trim_hostname_trailing_dot_for_sni(
|
||||
dns_name: pki_types::DnsName<'static>,
|
||||
) -> pki_types::DnsName<'static> {
|
||||
let dns_name_str: &str = dns_name.as_ref();
|
||||
|
||||
// RFC6066: "The hostname is represented as a byte string using
|
||||
// ASCII encoding without a trailing dot"
|
||||
if dns_name_str.ends_with('.') {
|
||||
let trimmed = &dns_name_str[0..dns_name_str.len() - 1];
|
||||
webpki::DnsNameRef::try_from_ascii_str(trimmed)
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
pki_types::DnsName::try_from(trimmed).unwrap().to_owned()
|
||||
} else {
|
||||
dns_name.to_owned()
|
||||
}
|
||||
@@ -711,7 +714,7 @@ fn trim_hostname_trailing_dot_for_sni(dns_name: webpki::DnsNameRef) -> webpki::D
|
||||
|
||||
impl ClientExtension {
|
||||
/// Make a basic SNI ServerNameRequest quoting `hostname`.
|
||||
pub fn make_sni(dns_name: webpki::DnsNameRef) -> Self {
|
||||
pub fn make_sni(dns_name: pki_types::DnsName<'static>) -> Self {
|
||||
let name = ServerName {
|
||||
typ: ServerNameType::HostName,
|
||||
payload: ServerNamePayload::new_hostname(trim_hostname_trailing_dot_for_sni(dns_name)),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use rustls_pki_types as pki_types;
|
||||
|
||||
use super::{
|
||||
base::{Payload, PayloadU16, PayloadU24, PayloadU8},
|
||||
codec::{put_u16, Codec, Reader},
|
||||
@@ -5,7 +7,6 @@ use super::{
|
||||
handshake::*,
|
||||
};
|
||||
use crate::key::Certificate;
|
||||
use webpki::DnsNameRef;
|
||||
|
||||
#[test]
|
||||
fn rejects_short_random() {
|
||||
@@ -186,7 +187,8 @@ fn can_roundtrip_multiname_sni() {
|
||||
|
||||
assert!(req.has_duplicate_names_for_type());
|
||||
|
||||
let dns_name_str: &str = req.get_single_hostname().unwrap().into();
|
||||
let dns_name = req.get_single_hostname().unwrap();
|
||||
let dns_name_str: &str = dns_name.as_ref();
|
||||
assert_eq!(dns_name_str, "hi");
|
||||
|
||||
assert_eq!(req[0].typ, ServerNameType::HostName);
|
||||
@@ -363,7 +365,7 @@ fn get_sample_clienthellopayload() -> ClientHelloPayload {
|
||||
ClientExtension::ECPointFormats(ECPointFormatList::supported()),
|
||||
ClientExtension::NamedGroups(vec![NamedGroup::X25519]),
|
||||
ClientExtension::SignatureAlgorithms(vec![SignatureScheme::ECDSA_NISTP256_SHA256]),
|
||||
ClientExtension::make_sni(DnsNameRef::try_from_ascii_str("hello").unwrap()),
|
||||
ClientExtension::make_sni(pki_types::DnsName::try_from("hello").unwrap().to_owned()),
|
||||
ClientExtension::SessionTicket(ClientSessionTicket::Request),
|
||||
ClientExtension::SessionTicket(ClientSessionTicket::Offer(Payload(vec![]))),
|
||||
ClientExtension::Protocols(vec![PayloadU8(vec![0])]),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
anchors::{OwnedTrustAnchor, RootCertStore},
|
||||
anchors::RootCertStore,
|
||||
dns::ServerName,
|
||||
error::Error,
|
||||
key::Certificate,
|
||||
@@ -9,27 +9,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use ring::digest::Digest;
|
||||
use std::convert::TryFrom;
|
||||
use web_time::SystemTime;
|
||||
use rustls_pki_types as pki_types;
|
||||
use web_time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm];
|
||||
|
||||
/// Which signature verification mechanisms we support. No particular
|
||||
/// order.
|
||||
static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[
|
||||
&webpki::ECDSA_P256_SHA256,
|
||||
&webpki::ECDSA_P256_SHA384,
|
||||
&webpki::ECDSA_P384_SHA256,
|
||||
&webpki::ECDSA_P384_SHA384,
|
||||
&webpki::ED25519,
|
||||
&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
|
||||
&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
|
||||
&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
|
||||
&webpki::RSA_PKCS1_2048_8192_SHA256,
|
||||
&webpki::RSA_PKCS1_2048_8192_SHA384,
|
||||
&webpki::RSA_PKCS1_2048_8192_SHA512,
|
||||
&webpki::RSA_PKCS1_3072_8192_SHA384,
|
||||
];
|
||||
type SignatureAlgorithms = &'static [&'static dyn pki_types::SignatureVerificationAlgorithm];
|
||||
|
||||
// Marker types. These are used to bind the fact some verification
|
||||
// (certificate chain or handshake signature) has taken place into
|
||||
@@ -170,7 +153,7 @@ pub trait ServerCertVerifier: Send + Sync {
|
||||
|
||||
/// A type which encapsuates a string that is a syntactically valid DNS name.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DnsName(pub(crate) webpki::DnsName);
|
||||
pub struct DnsName(pub(crate) pki_types::DnsName<'static>);
|
||||
|
||||
impl AsRef<str> for DnsName {
|
||||
fn as_ref(&self) -> &str {
|
||||
@@ -289,35 +272,33 @@ impl ServerCertVerifier for WebPkiVerifier {
|
||||
_ocsp_response: &[u8],
|
||||
now: SystemTime,
|
||||
) -> Result<ServerCertVerified, Error> {
|
||||
let (cert, chain, trustroots) = prepare(end_entity, intermediates, &self.roots)?;
|
||||
// `webpki::Time::try_from` does not work with `web_time::SystemTime`.
|
||||
// To workaround this we convert `SystemTime` to seconds and use
|
||||
// `webpki::Time::from_seconds_since_unix_epoch` instead.
|
||||
let duration_since_epoch = now
|
||||
.duration_since(web_time::UNIX_EPOCH)
|
||||
.map_err(|_| Error::FailedToGetCurrentTime)?;
|
||||
let seconds_since_unix_epoch = duration_since_epoch.as_secs();
|
||||
let webpki_now = webpki::Time::from_seconds_since_unix_epoch(seconds_since_unix_epoch);
|
||||
let cert = pki_types::CertificateDer::from(end_entity.0.as_slice());
|
||||
let cert = webpki::EndEntityCert::try_from(&cert).map_err(pki_error)?;
|
||||
let intermediates = intermediates
|
||||
.iter()
|
||||
.map(|c| pki_types::CertificateDer::from(c.0.as_slice()))
|
||||
.collect::<Vec<_>>();
|
||||
let time = pki_types::UnixTime::since_unix_epoch(now.duration_since(UNIX_EPOCH)?);
|
||||
|
||||
let ServerName::DnsName(dns_name) = server_name;
|
||||
|
||||
let cert = cert
|
||||
.verify_is_valid_tls_server_cert(
|
||||
SUPPORTED_SIG_ALGS,
|
||||
&webpki::TlsServerTrustAnchors(&trustroots),
|
||||
&chain,
|
||||
webpki_now,
|
||||
)
|
||||
.map_err(pki_error)
|
||||
.map(|_| cert)?;
|
||||
cert.verify_for_usage(
|
||||
webpki::ALL_VERIFICATION_ALGS,
|
||||
&self.roots.roots,
|
||||
&intermediates,
|
||||
time,
|
||||
webpki::KeyUsage::server_auth(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(pki_error)?;
|
||||
|
||||
if let Some(policy) = &self.ct_policy {
|
||||
policy.verify(end_entity, now, scts)?;
|
||||
}
|
||||
|
||||
cert.verify_is_valid_for_dns_name(dns_name.0.as_ref())
|
||||
.map_err(pki_error)
|
||||
cert.verify_is_valid_for_subject_name(&server_name.0)
|
||||
.map(|_| ServerCertVerified::assertion())
|
||||
.map_err(pki_error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,31 +410,6 @@ impl CertificateTransparencyPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
type CertChainAndRoots<'a, 'b> = (
|
||||
webpki::EndEntityCert<'a>,
|
||||
Vec<&'a [u8]>,
|
||||
Vec<webpki::TrustAnchor<'b>>,
|
||||
);
|
||||
|
||||
fn prepare<'a, 'b>(
|
||||
end_entity: &'a Certificate,
|
||||
intermediates: &'a [Certificate],
|
||||
roots: &'b RootCertStore,
|
||||
) -> Result<CertChainAndRoots<'a, 'b>, Error> {
|
||||
// EE cert must appear first.
|
||||
let cert = webpki::EndEntityCert::try_from(end_entity.0.as_ref()).map_err(pki_error)?;
|
||||
|
||||
let intermediates: Vec<&'a [u8]> = intermediates.iter().map(|cert| cert.0.as_ref()).collect();
|
||||
|
||||
let trustroots: Vec<webpki::TrustAnchor> = roots
|
||||
.roots
|
||||
.iter()
|
||||
.map(OwnedTrustAnchor::to_trust_anchor)
|
||||
.collect();
|
||||
|
||||
Ok((cert, intermediates, trustroots))
|
||||
}
|
||||
|
||||
pub(crate) fn pki_error(error: webpki::Error) -> Error {
|
||||
use webpki::Error::*;
|
||||
match error {
|
||||
@@ -466,20 +422,24 @@ pub(crate) fn pki_error(error: webpki::Error) -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
static ECDSA_SHA256: SignatureAlgorithms =
|
||||
&[&webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P384_SHA256];
|
||||
static ECDSA_SHA256: SignatureAlgorithms = &[
|
||||
webpki::ring::ECDSA_P256_SHA256,
|
||||
webpki::ring::ECDSA_P384_SHA256,
|
||||
];
|
||||
|
||||
static ECDSA_SHA384: SignatureAlgorithms =
|
||||
&[&webpki::ECDSA_P256_SHA384, &webpki::ECDSA_P384_SHA384];
|
||||
static ECDSA_SHA384: SignatureAlgorithms = &[
|
||||
webpki::ring::ECDSA_P256_SHA384,
|
||||
webpki::ring::ECDSA_P384_SHA384,
|
||||
];
|
||||
|
||||
static ED25519: SignatureAlgorithms = &[&webpki::ED25519];
|
||||
static ED25519: SignatureAlgorithms = &[webpki::ring::ED25519];
|
||||
|
||||
static RSA_SHA256: SignatureAlgorithms = &[&webpki::RSA_PKCS1_2048_8192_SHA256];
|
||||
static RSA_SHA384: SignatureAlgorithms = &[&webpki::RSA_PKCS1_2048_8192_SHA384];
|
||||
static RSA_SHA512: SignatureAlgorithms = &[&webpki::RSA_PKCS1_2048_8192_SHA512];
|
||||
static RSA_PSS_SHA256: SignatureAlgorithms = &[&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY];
|
||||
static RSA_PSS_SHA384: SignatureAlgorithms = &[&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY];
|
||||
static RSA_PSS_SHA512: SignatureAlgorithms = &[&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY];
|
||||
static RSA_SHA256: SignatureAlgorithms = &[webpki::ring::RSA_PKCS1_2048_8192_SHA256];
|
||||
static RSA_SHA384: SignatureAlgorithms = &[webpki::ring::RSA_PKCS1_2048_8192_SHA384];
|
||||
static RSA_SHA512: SignatureAlgorithms = &[webpki::ring::RSA_PKCS1_2048_8192_SHA512];
|
||||
static RSA_PSS_SHA256: SignatureAlgorithms = &[webpki::ring::RSA_PSS_2048_8192_SHA256_LEGACY_KEY];
|
||||
static RSA_PSS_SHA384: SignatureAlgorithms = &[webpki::ring::RSA_PSS_2048_8192_SHA384_LEGACY_KEY];
|
||||
static RSA_PSS_SHA512: SignatureAlgorithms = &[webpki::ring::RSA_PSS_2048_8192_SHA512_LEGACY_KEY];
|
||||
|
||||
fn convert_scheme(scheme: SignatureScheme) -> Result<SignatureAlgorithms, Error> {
|
||||
match scheme {
|
||||
@@ -514,7 +474,7 @@ fn verify_sig_using_any_alg(
|
||||
// webpki::SignatureAlgorithm. Therefore, convert_algs maps to several and
|
||||
// we try them all.
|
||||
for alg in algs {
|
||||
match cert.verify_signature(alg, message, sig) {
|
||||
match cert.verify_signature(*alg, message, sig) {
|
||||
Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey) => continue,
|
||||
res => return res,
|
||||
}
|
||||
@@ -529,7 +489,9 @@ fn verify_signed_struct(
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, Error> {
|
||||
let possible_algs = convert_scheme(dss.scheme)?;
|
||||
let cert = webpki::EndEntityCert::try_from(cert.0.as_ref()).map_err(pki_error)?;
|
||||
|
||||
let cert = pki_types::CertificateDer::from(cert.0.as_slice());
|
||||
let cert = webpki::EndEntityCert::try_from(&cert).map_err(pki_error)?;
|
||||
|
||||
verify_sig_using_any_alg(&cert, possible_algs, message, &dss.sig.0)
|
||||
.map_err(pki_error)
|
||||
@@ -538,16 +500,16 @@ fn verify_signed_struct(
|
||||
|
||||
fn convert_alg_tls13(
|
||||
scheme: SignatureScheme,
|
||||
) -> Result<&'static webpki::SignatureAlgorithm, Error> {
|
||||
) -> Result<&'static dyn pki_types::SignatureVerificationAlgorithm, Error> {
|
||||
use crate::msgs::enums::SignatureScheme::*;
|
||||
|
||||
match scheme {
|
||||
ECDSA_NISTP256_SHA256 => Ok(&webpki::ECDSA_P256_SHA256),
|
||||
ECDSA_NISTP384_SHA384 => Ok(&webpki::ECDSA_P384_SHA384),
|
||||
ED25519 => Ok(&webpki::ED25519),
|
||||
RSA_PSS_SHA256 => Ok(&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY),
|
||||
RSA_PSS_SHA384 => Ok(&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY),
|
||||
RSA_PSS_SHA512 => Ok(&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY),
|
||||
ECDSA_NISTP256_SHA256 => Ok(webpki::ring::ECDSA_P256_SHA256),
|
||||
ECDSA_NISTP384_SHA384 => Ok(webpki::ring::ECDSA_P384_SHA384),
|
||||
ED25519 => Ok(webpki::ring::ED25519),
|
||||
RSA_PSS_SHA256 => Ok(webpki::ring::RSA_PSS_2048_8192_SHA256_LEGACY_KEY),
|
||||
RSA_PSS_SHA384 => Ok(webpki::ring::RSA_PSS_2048_8192_SHA384_LEGACY_KEY),
|
||||
RSA_PSS_SHA512 => Ok(webpki::ring::RSA_PSS_2048_8192_SHA512_LEGACY_KEY),
|
||||
_ => {
|
||||
let error_msg = format!("received unsupported sig scheme {scheme:?}");
|
||||
Err(Error::PeerMisbehavedError(error_msg))
|
||||
@@ -583,7 +545,8 @@ fn verify_tls13(
|
||||
) -> Result<HandshakeSignatureValid, Error> {
|
||||
let alg = convert_alg_tls13(dss.scheme)?;
|
||||
|
||||
let cert = webpki::EndEntityCert::try_from(cert.0.as_ref()).map_err(pki_error)?;
|
||||
let cert = pki_types::CertificateDer::from(cert.0.as_slice());
|
||||
let cert = webpki::EndEntityCert::try_from(&cert).map_err(pki_error)?;
|
||||
|
||||
cert.verify_signature(alg, msg, &dss.sig.0)
|
||||
.map_err(pki_error)
|
||||
|
||||
@@ -12,8 +12,7 @@ workspace = true
|
||||
|
||||
[features]
|
||||
default = ["rayon"]
|
||||
rayon = ["mpz-common/rayon"]
|
||||
force-st = ["mpz-common/force-st"]
|
||||
rayon = ["mpz-zk/rayon", "mpz-garble/rayon"]
|
||||
web = ["dep:web-spawn"]
|
||||
|
||||
[dependencies]
|
||||
@@ -45,7 +44,8 @@ derive_builder = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
opaque-debug = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rustls-pki-types = "1.12.0"
|
||||
rustls-pki-types = { workspace = true }
|
||||
rustls-webpki = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
@@ -28,6 +28,14 @@ impl TranscriptRefs {
|
||||
&self.recv
|
||||
}
|
||||
|
||||
/// Returns the transcript lengths.
|
||||
pub(crate) fn len(&self) -> (usize, usize) {
|
||||
let sent = self.sent.iter().map(|v| v.len()).sum();
|
||||
let recv = self.recv.iter().map(|v| v.len()).sum();
|
||||
|
||||
(sent, recv)
|
||||
}
|
||||
|
||||
/// Returns VM references for the given direction and index, otherwise
|
||||
/// `None` if the index is out of bounds.
|
||||
pub(crate) fn get(&self, direction: Direction, idx: &Idx) -> Option<Vec<Vector<U8>>> {
|
||||
|
||||
@@ -5,6 +5,8 @@ use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
pub use tlsn_core::webpki::{CertificateDer, PrivateKeyDer, RootCertStore};
|
||||
|
||||
// Default is 32 bytes to decrypt the TLS protocol messages.
|
||||
const DEFAULT_MAX_RECV_ONLINE: usize = 32;
|
||||
// Default maximum number of TLS records to allow.
|
||||
@@ -108,7 +110,7 @@ impl ProtocolConfig {
|
||||
|
||||
/// Protocol configuration validator used by checker (i.e. verifier) to perform
|
||||
/// compatibility check with the peer's (i.e. the prover's) configuration.
|
||||
#[derive(derive_builder::Builder, Clone, Debug)]
|
||||
#[derive(derive_builder::Builder, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ProtocolConfigValidator {
|
||||
/// Maximum number of bytes that can be sent.
|
||||
max_sent_data: usize,
|
||||
@@ -190,22 +192,22 @@ impl ProtocolConfigValidator {
|
||||
max_sent_records: Option<usize>,
|
||||
max_recv_records_online: Option<usize>,
|
||||
) -> Result<(), ProtocolConfigError> {
|
||||
if let Some(max_sent_records) = max_sent_records {
|
||||
if max_sent_records > self.max_sent_records {
|
||||
return Err(ProtocolConfigError::max_record_count(format!(
|
||||
"max_sent_records {} is greater than the configured limit {}",
|
||||
max_sent_records, self.max_sent_records,
|
||||
)));
|
||||
}
|
||||
if let Some(max_sent_records) = max_sent_records
|
||||
&& max_sent_records > self.max_sent_records
|
||||
{
|
||||
return Err(ProtocolConfigError::max_record_count(format!(
|
||||
"max_sent_records {} is greater than the configured limit {}",
|
||||
max_sent_records, self.max_sent_records,
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(max_recv_records_online) = max_recv_records_online {
|
||||
if max_recv_records_online > self.max_recv_records_online {
|
||||
return Err(ProtocolConfigError::max_record_count(format!(
|
||||
"max_recv_records_online {} is greater than the configured limit {}",
|
||||
max_recv_records_online, self.max_recv_records_online,
|
||||
)));
|
||||
}
|
||||
if let Some(max_recv_records_online) = max_recv_records_online
|
||||
&& max_recv_records_online > self.max_recv_records_online
|
||||
{
|
||||
return Err(ProtocolConfigError::max_record_count(format!(
|
||||
"max_recv_records_online {} is greater than the configured limit {}",
|
||||
max_recv_records_online, self.max_recv_records_online,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -231,15 +233,17 @@ impl ProtocolConfigValidator {
|
||||
/// situations.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum NetworkSetting {
|
||||
/// Prefers a bandwidth-heavy protocol.
|
||||
/// Reduces network round-trips at the expense of consuming more network
|
||||
/// bandwidth.
|
||||
Bandwidth,
|
||||
/// Prefers a latency-heavy protocol.
|
||||
/// Reduces network bandwidth utilization at the expense of more network
|
||||
/// round-trips.
|
||||
Latency,
|
||||
}
|
||||
|
||||
impl Default for NetworkSetting {
|
||||
fn default() -> Self {
|
||||
Self::Bandwidth
|
||||
Self::Latency
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,12 @@ pub(crate) async fn transfer<'a>(
|
||||
.zip(recv_keys)
|
||||
.for_each(|(enc, key)| *enc ^= key);
|
||||
|
||||
// Set frame limit and add some extra bytes cushion room.
|
||||
let (sent, recv) = refs.len();
|
||||
let frame_limit = ENCODING_SIZE * (sent + recv) + ctx.io().limit();
|
||||
|
||||
ctx.io_mut()
|
||||
.with_limit(frame_limit)
|
||||
.send(Encodings {
|
||||
sent: sent_encoding,
|
||||
recv: recv_encoding,
|
||||
@@ -114,7 +119,12 @@ pub(crate) async fn receive<'a>(
|
||||
f: impl Fn(Vector<U8>) -> &'a [Mac],
|
||||
idxs: impl IntoIterator<Item = &(Direction, Idx)>,
|
||||
) -> Result<(EncodingCommitment, EncodingTree), EncodingError> {
|
||||
let Encodings { mut sent, mut recv } = ctx.io_mut().expect_next().await?;
|
||||
// Set frame limit and add some extra bytes cushion room.
|
||||
let (sent, recv) = refs.len();
|
||||
let frame_limit = ENCODING_SIZE * (sent + recv) + ctx.io().limit();
|
||||
|
||||
let Encodings { mut sent, mut recv } =
|
||||
ctx.io_mut().with_limit(frame_limit).expect_next().await?;
|
||||
|
||||
let sent_macs: Vec<u8> = refs
|
||||
.sent()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use tlsn_core::connection::{ServerCertData, ServerName};
|
||||
use tlsn_core::connection::{HandshakeData, ServerName};
|
||||
|
||||
/// Message sent from Prover to Verifier to prove the server identity.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -10,5 +10,5 @@ pub(crate) struct ServerIdentityProof {
|
||||
/// Server name.
|
||||
pub name: ServerName,
|
||||
/// Server identity data.
|
||||
pub data: ServerCertData,
|
||||
pub data: HandshakeData,
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ pub mod state;
|
||||
pub use config::{ProverConfig, ProverConfigBuilder, TlsConfig, TlsConfigBuilder};
|
||||
pub use error::ProverError;
|
||||
pub use future::ProverFuture;
|
||||
use rustls_pki_types::CertificateDer;
|
||||
pub use tlsn_core::{ProveConfig, ProveConfigBuilder, ProveConfigBuilderError, ProverOutput};
|
||||
|
||||
use mpz_common::Context;
|
||||
use mpz_core::Block;
|
||||
use mpz_garble_core::Delta;
|
||||
use mpz_vm_core::prelude::*;
|
||||
use mpz_zk::ProverConfig as ZkProverConfig;
|
||||
use webpki::anchor_from_trusted_cert;
|
||||
|
||||
use crate::{
|
||||
Role,
|
||||
@@ -32,20 +35,16 @@ use crate::{
|
||||
use futures::{AsyncRead, AsyncWrite, TryFutureExt};
|
||||
use mpc_tls::{LeaderCtrl, MpcTlsLeader, SessionKeys};
|
||||
use rand::Rng;
|
||||
use serio::{SinkExt, stream::IoStreamExt};
|
||||
use serio::SinkExt;
|
||||
use std::sync::Arc;
|
||||
use tls_client::{ClientConnection, ServerName as TlsServerName};
|
||||
use tls_client_async::{TlsConnection, bind_client};
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_attestation::{
|
||||
Attestation, CryptoProvider, Secrets,
|
||||
request::{Request, RequestConfig},
|
||||
};
|
||||
use tlsn_core::{
|
||||
ProvePayload,
|
||||
connection::ServerCertData,
|
||||
connection::{HandshakeData, ServerName},
|
||||
hash::{Blake3, HashAlgId, HashAlgorithm, Keccak256, Sha256},
|
||||
transcript::{Direction, TlsTranscript, Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
transcript::{TlsTranscript, Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
};
|
||||
use tlsn_deap::Deap;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -183,21 +182,40 @@ impl Prover<state::Setup> {
|
||||
|
||||
let (mpc_ctrl, mpc_fut) = mpc_tls.run();
|
||||
|
||||
let ServerName::Dns(server_name) = self.config.server_name();
|
||||
let server_name =
|
||||
TlsServerName::try_from(self.config.server_name().as_str()).map_err(|_| {
|
||||
ProverError::config(format!(
|
||||
"invalid server name: {}",
|
||||
self.config.server_name()
|
||||
))
|
||||
})?;
|
||||
TlsServerName::try_from(server_name.as_ref()).expect("name was validated");
|
||||
|
||||
let root_store = if let Some(root_store) = self.config.tls_config().root_store() {
|
||||
let roots = root_store
|
||||
.roots
|
||||
.iter()
|
||||
.map(|cert| {
|
||||
let der = CertificateDer::from_slice(&cert.0);
|
||||
anchor_from_trusted_cert(&der)
|
||||
.map(|anchor| anchor.to_owned())
|
||||
.map_err(ProverError::config)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
tls_client::RootCertStore { roots }
|
||||
} else {
|
||||
tls_client::RootCertStore {
|
||||
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
|
||||
}
|
||||
};
|
||||
|
||||
let config = tls_client::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(self.config.tls_config().root_store().clone());
|
||||
.with_root_certificates(root_store);
|
||||
|
||||
let config = if let Some((cert, key)) = self.config.tls_config().client_auth() {
|
||||
config
|
||||
.with_single_cert(cert.clone(), key.clone())
|
||||
.with_single_cert(
|
||||
cert.iter()
|
||||
.map(|cert| tls_client::Certificate(cert.0.clone()))
|
||||
.collect(),
|
||||
tls_client::PrivateKey(key.0.clone()),
|
||||
)
|
||||
.map_err(ProverError::config)?
|
||||
} else {
|
||||
config.with_no_client_auth()
|
||||
@@ -354,10 +372,10 @@ impl Prover<state::Committed> {
|
||||
};
|
||||
|
||||
let payload = ProvePayload {
|
||||
server_identity: config.server_identity().then(|| {
|
||||
handshake: config.server_identity().then(|| {
|
||||
(
|
||||
self.config.server_name().clone(),
|
||||
ServerCertData {
|
||||
HandshakeData {
|
||||
certs: tls_transcript
|
||||
.server_cert_chain()
|
||||
.expect("server cert chain is present")
|
||||
@@ -366,7 +384,7 @@ impl Prover<state::Committed> {
|
||||
.server_signature()
|
||||
.expect("server signature is present")
|
||||
.clone(),
|
||||
handshake: tls_transcript.handshake_data().clone(),
|
||||
binding: tls_transcript.certificate_binding().clone(),
|
||||
},
|
||||
)
|
||||
}),
|
||||
@@ -458,118 +476,6 @@ impl Prover<state::Committed> {
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Requests an attestation from the verifier.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - The attestation request configuration.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
#[deprecated(
|
||||
note = "attestation functionality will be removed from this API in future releases."
|
||||
)]
|
||||
pub async fn notarize(
|
||||
&mut self,
|
||||
config: &RequestConfig,
|
||||
) -> Result<(Attestation, Secrets), ProverError> {
|
||||
#[allow(deprecated)]
|
||||
self.notarize_with_provider(config, &CryptoProvider::default())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Requests an attestation from the verifier.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - The attestation request configuration.
|
||||
/// * `provider` - Cryptography provider.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
#[deprecated(
|
||||
note = "attestation functionality will be removed from this API in future releases."
|
||||
)]
|
||||
pub async fn notarize_with_provider(
|
||||
&mut self,
|
||||
config: &RequestConfig,
|
||||
provider: &CryptoProvider,
|
||||
) -> Result<(Attestation, Secrets), ProverError> {
|
||||
let mut builder = ProveConfig::builder(self.transcript());
|
||||
|
||||
if let Some(config) = config.transcript_commit() {
|
||||
// Temporarily, we reject attestation requests which contain hash commitments to
|
||||
// subsets of the transcript. We do this because we want to preserve the
|
||||
// obliviousness of the reference notary, and hash commitments currently leak
|
||||
// the ranges which are being committed.
|
||||
for ((direction, idx), _) in config.iter_hash() {
|
||||
let len = match direction {
|
||||
Direction::Sent => self.transcript().sent().len(),
|
||||
Direction::Received => self.transcript().received().len(),
|
||||
};
|
||||
|
||||
if idx.start() > 0 || idx.end() < len || idx.count() != 1 {
|
||||
return Err(ProverError::attestation(
|
||||
"hash commitments to subsets of the transcript are currently not supported in attestation requests",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
builder.transcript_commit(config.clone());
|
||||
}
|
||||
|
||||
let disclosure_config = builder.build().map_err(ProverError::attestation)?;
|
||||
|
||||
let ProverOutput {
|
||||
transcript_commitments,
|
||||
transcript_secrets,
|
||||
..
|
||||
} = self.prove(&disclosure_config).await?;
|
||||
|
||||
let state::Committed {
|
||||
mux_fut,
|
||||
ctx,
|
||||
tls_transcript,
|
||||
transcript,
|
||||
..
|
||||
} = &mut self.state;
|
||||
|
||||
let mut builder = Request::builder(config);
|
||||
|
||||
builder
|
||||
.server_name(self.config.server_name().clone())
|
||||
.server_cert_data(ServerCertData {
|
||||
certs: tls_transcript
|
||||
.server_cert_chain()
|
||||
.expect("server cert chain is present")
|
||||
.to_vec(),
|
||||
sig: tls_transcript
|
||||
.server_signature()
|
||||
.expect("server signature is present")
|
||||
.clone(),
|
||||
handshake: tls_transcript.handshake_data().clone(),
|
||||
})
|
||||
.transcript(transcript.clone())
|
||||
.transcript_commitments(transcript_secrets, transcript_commitments);
|
||||
|
||||
let (request, secrets) = builder.build(provider).map_err(ProverError::attestation)?;
|
||||
|
||||
let attestation = mux_fut
|
||||
.poll_with(async {
|
||||
debug!("sending attestation request");
|
||||
|
||||
ctx.io_mut().send(request.clone()).await?;
|
||||
|
||||
let attestation: Attestation = ctx.io_mut().expect_next().await?;
|
||||
|
||||
Ok::<_, ProverError>(attestation)
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Check the attestation is consistent with the Prover's view.
|
||||
request
|
||||
.validate(&attestation)
|
||||
.map_err(ProverError::attestation)?;
|
||||
|
||||
Ok((attestation, secrets))
|
||||
}
|
||||
|
||||
/// Closes the connection with the verifier.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn close(self) -> Result<(), ProverError> {
|
||||
@@ -618,7 +524,7 @@ fn build_mpc_tls(config: &ProverConfig, ctx: Context) -> (Arc<Mutex<Deap<Mpc, Zk
|
||||
delta,
|
||||
);
|
||||
|
||||
let zk = Zk::new(rcot_recv.clone());
|
||||
let zk = Zk::new(ZkProverConfig::default(), rcot_recv.clone());
|
||||
|
||||
let vm = Arc::new(Mutex::new(Deap::new(tlsn_deap::Role::Leader, mpc, zk)));
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use crate::config::{NetworkSetting, ProtocolConfig};
|
||||
use mpc_tls::Config;
|
||||
use rustls_pki_types::{CertificateDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, pem::PemObject};
|
||||
use tls_core::{
|
||||
anchors::{OwnedTrustAnchor, RootCertStore},
|
||||
key,
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tlsn_core::{
|
||||
connection::ServerName,
|
||||
webpki::{CertificateDer, PrivateKeyDer, RootCertStore},
|
||||
};
|
||||
use tlsn_core::connection::ServerName;
|
||||
|
||||
use crate::config::{NetworkSetting, ProtocolConfig};
|
||||
|
||||
/// Configuration for the prover.
|
||||
#[derive(Debug, Clone, derive_builder::Builder)]
|
||||
#[derive(Debug, Clone, derive_builder::Builder, Serialize, Deserialize)]
|
||||
pub struct ProverConfig {
|
||||
/// The server DNS name.
|
||||
#[builder(setter(into))]
|
||||
@@ -67,31 +67,13 @@ impl ProverConfig {
|
||||
}
|
||||
|
||||
/// Configuration for the prover's TLS connection.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TlsConfig {
|
||||
/// Root certificates.
|
||||
root_store: RootCertStore,
|
||||
root_store: Option<RootCertStore>,
|
||||
/// Certificate chain and a matching private key for client
|
||||
/// authentication.
|
||||
client_auth: Option<(Vec<key::Certificate>, key::PrivateKey)>,
|
||||
}
|
||||
|
||||
impl Default for TlsConfig {
|
||||
fn default() -> Self {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
|
||||
Self {
|
||||
root_store,
|
||||
client_auth: None,
|
||||
}
|
||||
}
|
||||
client_auth: Option<(Vec<CertificateDer>, PrivateKeyDer)>,
|
||||
}
|
||||
|
||||
impl TlsConfig {
|
||||
@@ -100,13 +82,13 @@ impl TlsConfig {
|
||||
TlsConfigBuilder::default()
|
||||
}
|
||||
|
||||
pub(crate) fn root_store(&self) -> &RootCertStore {
|
||||
&self.root_store
|
||||
pub(crate) fn root_store(&self) -> Option<&RootCertStore> {
|
||||
self.root_store.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a certificate chain and a matching private key for client
|
||||
/// authentication.
|
||||
pub fn client_auth(&self) -> &Option<(Vec<key::Certificate>, key::PrivateKey)> {
|
||||
pub fn client_auth(&self) -> &Option<(Vec<CertificateDer>, PrivateKeyDer)> {
|
||||
&self.client_auth
|
||||
}
|
||||
}
|
||||
@@ -115,7 +97,7 @@ impl TlsConfig {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TlsConfigBuilder {
|
||||
root_store: Option<RootCertStore>,
|
||||
client_auth: Option<(Vec<key::Certificate>, key::PrivateKey)>,
|
||||
client_auth: Option<(Vec<CertificateDer>, PrivateKeyDer)>,
|
||||
}
|
||||
|
||||
impl TlsConfigBuilder {
|
||||
@@ -138,74 +120,16 @@ impl TlsConfigBuilder {
|
||||
///
|
||||
/// - Each certificate in the chain must be in the X.509 format.
|
||||
/// - The key must be in the ASN.1 format (either PKCS#8 or PKCS#1).
|
||||
pub fn client_auth(&mut self, cert_key: (Vec<Vec<u8>>, Vec<u8>)) -> &mut Self {
|
||||
let certs = cert_key
|
||||
.0
|
||||
.into_iter()
|
||||
.map(key::Certificate)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.client_auth = Some((certs, key::PrivateKey(cert_key.1)));
|
||||
pub fn client_auth(&mut self, cert_key: (Vec<CertificateDer>, PrivateKeyDer)) -> &mut Self {
|
||||
self.client_auth = Some(cert_key);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a PEM-encoded certificate chain and a matching private key for
|
||||
/// client authentication.
|
||||
///
|
||||
/// Often the chain will consist of a single end-entity certificate.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cert_key` - A tuple containing the certificate chain and the private
|
||||
/// key.
|
||||
///
|
||||
/// - Each certificate in the chain must be in the X.509 format.
|
||||
/// - The key must be in the ASN.1 format (either PKCS#8 or PKCS#1).
|
||||
pub fn client_auth_pem(
|
||||
&mut self,
|
||||
cert_key: (Vec<Vec<u8>>, Vec<u8>),
|
||||
) -> Result<&mut Self, TlsConfigError> {
|
||||
let key = match PrivatePkcs8KeyDer::from_pem_slice(&cert_key.1) {
|
||||
// Try to parse as PEM PKCS#8.
|
||||
Ok(key) => (*key.secret_pkcs8_der()).to_vec(),
|
||||
// Otherwise, try to parse as PEM PKCS#1.
|
||||
Err(_) => match PrivatePkcs1KeyDer::from_pem_slice(&cert_key.1) {
|
||||
Ok(key) => (*key.secret_pkcs1_der()).to_vec(),
|
||||
Err(_) => return Err(ErrorRepr::InvalidKey.into()),
|
||||
},
|
||||
};
|
||||
|
||||
let certs = cert_key
|
||||
.0
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let c =
|
||||
CertificateDer::from_pem_slice(c).map_err(|_| ErrorRepr::InvalidCertificate)?;
|
||||
Ok::<key::Certificate, TlsConfigError>(key::Certificate(c.as_ref().to_vec()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
self.client_auth = Some((certs, key::PrivateKey(key)));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Builds the TLS configuration.
|
||||
pub fn build(&self) -> Result<TlsConfig, TlsConfigError> {
|
||||
pub fn build(self) -> Result<TlsConfig, TlsConfigError> {
|
||||
Ok(TlsConfig {
|
||||
root_store: self.root_store.clone().unwrap_or_else(|| {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(
|
||||
|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
},
|
||||
));
|
||||
root_store
|
||||
}),
|
||||
client_auth: self.client_auth.clone(),
|
||||
root_store: self.root_store,
|
||||
client_auth: self.client_auth,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -216,10 +140,5 @@ impl TlsConfigBuilder {
|
||||
pub struct TlsConfigError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("tls config error: {0}")]
|
||||
enum ErrorRepr {
|
||||
#[error("the certificate for client authentication is invalid")]
|
||||
InvalidCertificate,
|
||||
#[error("the private key for client authentication is invalid")]
|
||||
InvalidKey,
|
||||
}
|
||||
#[error("tls config error")]
|
||||
enum ErrorRepr {}
|
||||
|
||||
@@ -49,13 +49,6 @@ impl ProverError {
|
||||
{
|
||||
Self::new(ErrorKind::Commit, source)
|
||||
}
|
||||
|
||||
pub(crate) fn attestation<E>(source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync + 'static>>,
|
||||
{
|
||||
Self::new(ErrorKind::Attestation, source)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -65,7 +58,6 @@ enum ErrorKind {
|
||||
Zk,
|
||||
Config,
|
||||
Commit,
|
||||
Attestation,
|
||||
}
|
||||
|
||||
impl fmt::Display for ProverError {
|
||||
@@ -78,7 +70,6 @@ impl fmt::Display for ProverError {
|
||||
ErrorKind::Zk => f.write_str("zk error")?,
|
||||
ErrorKind::Config => f.write_str("config error")?,
|
||||
ErrorKind::Commit => f.write_str("commit error")?,
|
||||
ErrorKind::Attestation => f.write_str("attestation error")?,
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
|
||||
@@ -8,7 +8,10 @@ use std::sync::Arc;
|
||||
|
||||
pub use config::{VerifierConfig, VerifierConfigBuilder, VerifierConfigBuilderError};
|
||||
pub use error::VerifierError;
|
||||
pub use tlsn_core::{VerifierOutput, VerifyConfig, VerifyConfigBuilder, VerifyConfigBuilderError};
|
||||
pub use tlsn_core::{
|
||||
VerifierOutput, VerifyConfig, VerifyConfigBuilder, VerifyConfigBuilderError,
|
||||
webpki::ServerCertVerifier,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Role,
|
||||
@@ -30,12 +33,12 @@ use mpz_common::Context;
|
||||
use mpz_core::Block;
|
||||
use mpz_garble_core::Delta;
|
||||
use mpz_vm_core::prelude::*;
|
||||
use serio::{SinkExt, stream::IoStreamExt};
|
||||
use tls_core::{msgs::enums::ContentType, verify::WebPkiVerifier};
|
||||
use tlsn_attestation::{Attestation, AttestationConfig, CryptoProvider, request::Request};
|
||||
use mpz_zk::VerifierConfig as ZkVerifierConfig;
|
||||
use serio::stream::IoStreamExt;
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_core::{
|
||||
ProvePayload,
|
||||
connection::{ConnectionInfo, ServerName, TranscriptLength},
|
||||
connection::{ConnectionInfo, ServerName},
|
||||
transcript::{TlsTranscript, TranscriptCommitment},
|
||||
};
|
||||
use tlsn_deap::Deap;
|
||||
@@ -152,59 +155,6 @@ impl Verifier<state::Initialized> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the verifier to completion and attests to the TLS session.
|
||||
///
|
||||
/// This is a convenience method which runs all the steps needed for
|
||||
/// notarization.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `socket` - The socket to the prover.
|
||||
/// * `config` - The attestation configuration.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
#[deprecated(
|
||||
note = "attestation functionality will be removed from this API in future releases."
|
||||
)]
|
||||
pub async fn notarize<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
config: &AttestationConfig,
|
||||
) -> Result<Attestation, VerifierError> {
|
||||
#[allow(deprecated)]
|
||||
self.notarize_with_provider(socket, config, &CryptoProvider::default())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Runs the verifier to completion and attests to the TLS session.
|
||||
///
|
||||
/// This is a convenience method which runs all the steps needed for
|
||||
/// notarization.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `socket` - The socket to the prover.
|
||||
/// * `config` - The attestation configuration.
|
||||
/// * `provider` - Cryptography provider.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
#[deprecated(
|
||||
note = "attestation functionality will be removed from this API in future releases."
|
||||
)]
|
||||
pub async fn notarize_with_provider<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
config: &AttestationConfig,
|
||||
provider: &CryptoProvider,
|
||||
) -> Result<Attestation, VerifierError> {
|
||||
let mut verifier = self.setup(socket).await?.run().await?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let attestation = verifier.notarize_with_provider(config, provider).await?;
|
||||
|
||||
verifier.close().await?;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
/// Runs the TLS verifier to completion, verifying the TLS session.
|
||||
///
|
||||
/// This is a convenience method which runs all the steps needed for
|
||||
@@ -359,15 +309,20 @@ impl Verifier<state::Committed> {
|
||||
} = &mut self.state;
|
||||
|
||||
let ProvePayload {
|
||||
server_identity,
|
||||
handshake,
|
||||
transcript,
|
||||
transcript_commit,
|
||||
} = mux_fut
|
||||
.poll_with(ctx.io_mut().expect_next().map_err(VerifierError::from))
|
||||
.await?;
|
||||
|
||||
let verifier = WebPkiVerifier::new(self.config.root_store().clone(), None);
|
||||
let server_name = if let Some((name, cert_data)) = server_identity {
|
||||
let verifier = if let Some(root_store) = self.config.root_store() {
|
||||
ServerCertVerifier::new(root_store).map_err(VerifierError::config)?
|
||||
} else {
|
||||
ServerCertVerifier::mozilla()
|
||||
};
|
||||
|
||||
let server_name = if let Some((name, cert_data)) = handshake {
|
||||
cert_data
|
||||
.verify(
|
||||
&verifier,
|
||||
@@ -472,123 +427,6 @@ impl Verifier<state::Committed> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Attests to the TLS session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - Attestation configuration.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
#[deprecated(
|
||||
note = "attestation functionality will be removed from this API in future releases."
|
||||
)]
|
||||
pub async fn notarize(
|
||||
&mut self,
|
||||
config: &AttestationConfig,
|
||||
) -> Result<Attestation, VerifierError> {
|
||||
#[allow(deprecated)]
|
||||
self.notarize_with_provider(config, &CryptoProvider::default())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Attests to the TLS session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - Attestation configuration.
|
||||
/// * `provider` - Cryptography provider.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
#[deprecated(
|
||||
note = "attestation functionality will be removed from this API in future releases."
|
||||
)]
|
||||
pub async fn notarize_with_provider(
|
||||
&mut self,
|
||||
config: &AttestationConfig,
|
||||
provider: &CryptoProvider,
|
||||
) -> Result<Attestation, VerifierError> {
|
||||
let VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
} = self.verify(&VerifyConfig::default()).await?;
|
||||
|
||||
if server_name.is_some() {
|
||||
return Err(VerifierError::attestation(
|
||||
"server name can not be revealed to a verifier",
|
||||
));
|
||||
} else if transcript.is_some() {
|
||||
return Err(VerifierError::attestation(
|
||||
"transcript data can not be revealed to a verifier",
|
||||
));
|
||||
}
|
||||
|
||||
let state::Committed {
|
||||
mux_fut,
|
||||
ctx,
|
||||
tls_transcript,
|
||||
..
|
||||
} = &mut self.state;
|
||||
|
||||
let sent_len = tls_transcript
|
||||
.sent()
|
||||
.iter()
|
||||
.filter_map(|record| {
|
||||
if let ContentType::ApplicationData = record.typ {
|
||||
Some(record.ciphertext.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum::<usize>();
|
||||
|
||||
let recv_len = tls_transcript
|
||||
.recv()
|
||||
.iter()
|
||||
.filter_map(|record| {
|
||||
if let ContentType::ApplicationData = record.typ {
|
||||
Some(record.ciphertext.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum::<usize>();
|
||||
|
||||
let request: Request = mux_fut
|
||||
.poll_with(ctx.io_mut().expect_next().map_err(VerifierError::from))
|
||||
.await?;
|
||||
|
||||
let mut builder = Attestation::builder(config)
|
||||
.accept_request(request)
|
||||
.map_err(VerifierError::attestation)?;
|
||||
|
||||
builder
|
||||
.connection_info(ConnectionInfo {
|
||||
time: tls_transcript.time(),
|
||||
version: (*tls_transcript.version()),
|
||||
transcript_length: TranscriptLength {
|
||||
sent: sent_len as u32,
|
||||
received: recv_len as u32,
|
||||
},
|
||||
})
|
||||
.server_ephemeral_key(tls_transcript.server_ephemeral_key().clone())
|
||||
.transcript_commitments(transcript_commitments);
|
||||
|
||||
let attestation = builder
|
||||
.build(provider)
|
||||
.map_err(VerifierError::attestation)?;
|
||||
|
||||
mux_fut
|
||||
.poll_with(
|
||||
ctx.io_mut()
|
||||
.send(attestation.clone())
|
||||
.map_err(VerifierError::from),
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Sent attestation");
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
/// Closes the connection with the prover.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn close(self) -> Result<(), VerifierError> {
|
||||
@@ -637,7 +475,7 @@ fn build_mpc_tls(
|
||||
|
||||
let mpc = Mpc::new(mpz_ot::cot::DerandCOTReceiver::new(rcot_recv.clone()));
|
||||
|
||||
let zk = Zk::new(delta, rcot_send.clone());
|
||||
let zk = Zk::new(ZkVerifierConfig::default(), delta, rcot_send.clone());
|
||||
|
||||
let vm = Arc::new(Mutex::new(Deap::new(tlsn_deap::Role::Follower, mpc, zk)));
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
use std::fmt::{Debug, Formatter, Result};
|
||||
|
||||
use crate::config::{NetworkSetting, ProtocolConfig, ProtocolConfigValidator};
|
||||
use mpc_tls::Config;
|
||||
use tls_core::anchors::{OwnedTrustAnchor, RootCertStore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tlsn_core::webpki::RootCertStore;
|
||||
|
||||
use crate::config::{NetworkSetting, ProtocolConfig, ProtocolConfigValidator};
|
||||
|
||||
/// Configuration for the [`Verifier`](crate::tls::Verifier).
|
||||
#[allow(missing_docs)]
|
||||
#[derive(derive_builder::Builder)]
|
||||
#[derive(derive_builder::Builder, Serialize, Deserialize)]
|
||||
#[builder(pattern = "owned")]
|
||||
pub struct VerifierConfig {
|
||||
protocol_config_validator: ProtocolConfigValidator,
|
||||
#[builder(default = "default_root_store()")]
|
||||
root_store: RootCertStore,
|
||||
#[builder(setter(strip_option))]
|
||||
root_store: Option<RootCertStore>,
|
||||
}
|
||||
|
||||
impl Debug for VerifierConfig {
|
||||
@@ -34,8 +36,8 @@ impl VerifierConfig {
|
||||
}
|
||||
|
||||
/// Returns the root certificate store.
|
||||
pub fn root_store(&self) -> &RootCertStore {
|
||||
&self.root_store
|
||||
pub fn root_store(&self) -> Option<&RootCertStore> {
|
||||
self.root_store.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn build_mpc_tls_config(&self, protocol_config: &ProtocolConfig) -> Config {
|
||||
@@ -61,16 +63,3 @@ impl VerifierConfig {
|
||||
builder.build().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn default_root_store() -> RootCertStore {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
|
||||
root_store
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ impl VerifierError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn config<E>(source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync + 'static>>,
|
||||
{
|
||||
Self::new(ErrorKind::Config, source)
|
||||
}
|
||||
|
||||
pub(crate) fn mpc<E>(source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync + 'static>>,
|
||||
@@ -34,13 +41,6 @@ impl VerifierError {
|
||||
Self::new(ErrorKind::Zk, source)
|
||||
}
|
||||
|
||||
pub(crate) fn attestation<E>(source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync + 'static>>,
|
||||
{
|
||||
Self::new(ErrorKind::Attestation, source)
|
||||
}
|
||||
|
||||
pub(crate) fn verify<E>(source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync + 'static>>,
|
||||
@@ -56,7 +56,6 @@ enum ErrorKind {
|
||||
Mpc,
|
||||
Zk,
|
||||
Commit,
|
||||
Attestation,
|
||||
Verify,
|
||||
}
|
||||
|
||||
@@ -70,7 +69,6 @@ impl fmt::Display for VerifierError {
|
||||
ErrorKind::Mpc => f.write_str("mpc error")?,
|
||||
ErrorKind::Zk => f.write_str("zk error")?,
|
||||
ErrorKind::Commit => f.write_str("commit error")?,
|
||||
ErrorKind::Attestation => f.write_str("attestation error")?,
|
||||
ErrorKind::Verify => f.write_str("verification error")?,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use futures::{AsyncReadExt, AsyncWriteExt};
|
||||
use tlsn::{
|
||||
config::{ProtocolConfig, ProtocolConfigValidator},
|
||||
config::{CertificateDer, ProtocolConfig, ProtocolConfigValidator, RootCertStore},
|
||||
connection::ServerName,
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
transcript::{TranscriptCommitConfig, TranscriptCommitment},
|
||||
verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig},
|
||||
@@ -37,19 +38,17 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(verifier_soc
|
||||
|
||||
let server_task = tokio::spawn(bind(server_socket.compat()));
|
||||
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(root_store);
|
||||
tls_config_builder.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
});
|
||||
|
||||
let tls_config = tls_config_builder.build().unwrap();
|
||||
|
||||
let server_name = ServerName::Dns(SERVER_DOMAIN.try_into().unwrap());
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.server_name(server_name)
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
@@ -110,11 +109,6 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(verifier_soc
|
||||
|
||||
#[instrument(skip(socket))]
|
||||
async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(socket: T) {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let config_validator = ProtocolConfigValidator::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
@@ -123,7 +117,9 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(soc
|
||||
|
||||
let verifier = Verifier::new(
|
||||
VerifierConfig::builder()
|
||||
.root_store(root_store)
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.protocol_config_validator(config_validator)
|
||||
.build()
|
||||
.unwrap(),
|
||||
@@ -140,7 +136,9 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(soc
|
||||
|
||||
let transcript = transcript.unwrap();
|
||||
|
||||
assert_eq!(server_name.unwrap().as_str(), SERVER_DOMAIN);
|
||||
let ServerName::Dns(server_name) = server_name.unwrap();
|
||||
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
assert!(!transcript.is_complete());
|
||||
assert_eq!(
|
||||
transcript.sent_authed().iter_ranges().next().unwrap(),
|
||||
|
||||
@@ -14,9 +14,6 @@ workspace = true
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.custom]
|
||||
wasm-opt = ["-O3"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
test = []
|
||||
@@ -49,8 +46,7 @@ tsify-next = { version = "0.5", default-features = false, features = ["js"] }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
web-spawn = { workspace = true }
|
||||
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
|
||||
ws_stream_wasm = { git = "https://github.com/tlsnotary/ws_stream_wasm", rev = "2ed12aad9f0236e5321f577672f309920b2aef51" }
|
||||
ws_stream_wasm = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
@@ -14,12 +14,9 @@ pub mod verifier;
|
||||
|
||||
pub use log::{LoggingConfig, LoggingLevel};
|
||||
|
||||
use tlsn::{attestation::CryptoProvider, transcript::Direction};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
use crate::types::{Attestation, Presentation, Reveal, Secrets};
|
||||
|
||||
#[cfg(feature = "test")]
|
||||
pub use tests::*;
|
||||
|
||||
@@ -46,34 +43,3 @@ pub async fn initialize(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a presentation.
|
||||
#[wasm_bindgen]
|
||||
pub fn build_presentation(
|
||||
attestation: &Attestation,
|
||||
secrets: &Secrets,
|
||||
reveal: Reveal,
|
||||
) -> Result<Presentation, JsError> {
|
||||
let provider = CryptoProvider::default();
|
||||
|
||||
let mut builder = attestation.0.presentation_builder(&provider);
|
||||
|
||||
builder.identity_proof(secrets.0.identity_proof());
|
||||
|
||||
let mut proof_builder = secrets.0.transcript_proof_builder();
|
||||
|
||||
for range in reveal.sent.iter() {
|
||||
proof_builder.reveal(range, Direction::Sent)?;
|
||||
}
|
||||
|
||||
for range in reveal.recv.iter() {
|
||||
proof_builder.reveal(range, Direction::Received)?;
|
||||
}
|
||||
|
||||
builder.transcript_proof(proof_builder.build()?);
|
||||
|
||||
builder
|
||||
.build()
|
||||
.map(Presentation::from)
|
||||
.map_err(JsError::from)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use crate::types::NetworkSetting;
|
||||
use serde::Deserialize;
|
||||
use tlsn::config::ProtocolConfig;
|
||||
use tlsn::{
|
||||
config::{CertificateDer, PrivateKeyDer, ProtocolConfig},
|
||||
connection::ServerName,
|
||||
};
|
||||
use tsify_next::Tsify;
|
||||
use wasm_bindgen::JsError;
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
@@ -17,8 +21,10 @@ pub struct ProverConfig {
|
||||
pub client_auth: Option<(Vec<Vec<u8>>, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl From<ProverConfig> for tlsn::prover::ProverConfig {
|
||||
fn from(value: ProverConfig) -> Self {
|
||||
impl TryFrom<ProverConfig> for tlsn::prover::ProverConfig {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_from(value: ProverConfig) -> Result<Self, Self::Error> {
|
||||
let mut builder = ProtocolConfig::builder();
|
||||
|
||||
builder.max_sent_data(value.max_sent_data);
|
||||
@@ -44,21 +50,36 @@ impl From<ProverConfig> for tlsn::prover::ProverConfig {
|
||||
let protocol_config = builder.build().unwrap();
|
||||
|
||||
let mut builder = tlsn::prover::TlsConfig::builder();
|
||||
if let Some(cert_key) = value.client_auth {
|
||||
// Try to parse as PEM-encoded.
|
||||
if builder.client_auth_pem(cert_key.clone()).is_err() {
|
||||
// Otherwise assume DER encoding.
|
||||
builder.client_auth(cert_key);
|
||||
}
|
||||
if let Some((certs, key)) = value.client_auth {
|
||||
let certs = certs
|
||||
.into_iter()
|
||||
.map(|cert| {
|
||||
// Try to parse as PEM-encoded, otherwise assume DER.
|
||||
if let Ok(cert) = CertificateDer::from_pem_slice(&cert) {
|
||||
cert
|
||||
} else {
|
||||
CertificateDer(cert)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let key = PrivateKeyDer(key);
|
||||
builder.client_auth((certs, key));
|
||||
}
|
||||
let tls_config = builder.build().unwrap();
|
||||
|
||||
let server_name = ServerName::Dns(
|
||||
value
|
||||
.server_name
|
||||
.try_into()
|
||||
.map_err(|_| JsError::new("invalid server name"))?,
|
||||
);
|
||||
|
||||
let mut builder = tlsn::prover::ProverConfig::builder();
|
||||
builder
|
||||
.server_name(value.server_name.as_ref())
|
||||
.server_name(server_name)
|
||||
.protocol_config(protocol_config)
|
||||
.tls_config(tls_config);
|
||||
|
||||
builder.build().unwrap()
|
||||
Ok(builder.build().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,7 @@ use futures::TryFutureExt;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::body::Bytes;
|
||||
use tls_client_async::TlsConnection;
|
||||
use tlsn::{
|
||||
attestation::request::RequestConfig,
|
||||
prover::{state, ProveConfig, Prover},
|
||||
transcript::TranscriptCommitConfigBuilder,
|
||||
};
|
||||
use tlsn::prover::{state, ProveConfig, Prover};
|
||||
use tracing::info;
|
||||
use wasm_bindgen::{prelude::*, JsError};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
@@ -45,10 +41,10 @@ impl State {
|
||||
#[wasm_bindgen(js_class = Prover)]
|
||||
impl JsProver {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(config: ProverConfig) -> JsProver {
|
||||
JsProver {
|
||||
state: State::Initialized(Prover::new(config.into())),
|
||||
}
|
||||
pub fn new(config: ProverConfig) -> Result<JsProver> {
|
||||
Ok(JsProver {
|
||||
state: State::Initialized(Prover::new(config.try_into()?)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Set up the prover.
|
||||
@@ -108,44 +104,6 @@ impl JsProver {
|
||||
Ok(Transcript::from(prover.transcript()))
|
||||
}
|
||||
|
||||
/// Runs the notarization protocol.
|
||||
pub async fn notarize(&mut self, commit: Commit) -> Result<NotarizationOutput> {
|
||||
let mut prover = self.state.take().try_into_committed()?;
|
||||
|
||||
info!("starting notarization");
|
||||
|
||||
let mut builder = TranscriptCommitConfigBuilder::new(prover.transcript());
|
||||
|
||||
for range in commit.sent {
|
||||
builder.commit_sent(&range)?;
|
||||
}
|
||||
|
||||
for range in commit.recv {
|
||||
builder.commit_recv(&range)?;
|
||||
}
|
||||
|
||||
let transcript_commit = builder.build()?;
|
||||
|
||||
let mut builder = RequestConfig::builder();
|
||||
|
||||
builder.transcript_commit(transcript_commit);
|
||||
|
||||
let request_config = builder.build()?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let (attestation, secrets) = prover.notarize(&request_config).await?;
|
||||
prover.close().await?;
|
||||
|
||||
info!("notarization complete");
|
||||
|
||||
self.state = State::Complete;
|
||||
|
||||
Ok(NotarizationOutput {
|
||||
attestation: attestation.into(),
|
||||
secrets: secrets.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Reveals data to the verifier and finalizes the protocol.
|
||||
pub async fn reveal(&mut self, reveal: Reveal) -> Result<()> {
|
||||
let mut prover = self.state.take().try_into_committed()?;
|
||||
|
||||
@@ -78,83 +78,6 @@ pub async fn test_prove() -> Result<(), JsValue> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn test_notarize() -> Result<(), JsValue> {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let provider = CryptoProvider {
|
||||
cert: WebPkiVerifier::new(root_store, None),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
.max_sent_data(1024)
|
||||
.max_recv_data(1024)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.crypto_provider(provider)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut prover = JsProver::from(prover);
|
||||
|
||||
let uri = format!("https://{SERVER_DOMAIN}/bytes?size=512");
|
||||
|
||||
prover
|
||||
.setup("ws://localhost:8080/tcp?addr=localhost%3A8011")
|
||||
.await?;
|
||||
|
||||
prover
|
||||
.send_request(
|
||||
"ws://localhost:8080/tcp?addr=localhost%3A8083",
|
||||
HttpRequest {
|
||||
method: Method::GET,
|
||||
uri,
|
||||
headers: HashMap::from([("Accept".to_string(), b"*".to_vec())]),
|
||||
body: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _ = prover.transcript()?;
|
||||
|
||||
let NotarizationOutput {
|
||||
attestation,
|
||||
secrets,
|
||||
} = prover
|
||||
.notarize(Commit {
|
||||
sent: vec![0..10],
|
||||
recv: vec![0..10],
|
||||
})
|
||||
.await?;
|
||||
|
||||
let attestation = Attestation::deserialize(attestation.serialize())?;
|
||||
let secrets = Secrets::deserialize(secrets.serialize())?;
|
||||
|
||||
let presentation = build_presentation(
|
||||
&attestation,
|
||||
&secrets,
|
||||
Reveal {
|
||||
sent: vec![(0..10)],
|
||||
recv: vec![(0..10)],
|
||||
server_identity: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
let _ = Presentation::deserialize(presentation.serialize())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn test_verifier() -> Result<(), JsValue> {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
|
||||
@@ -4,7 +4,6 @@ use http_body_util::Full;
|
||||
use hyper::body::Bytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use tlsn::attestation::CryptoProvider;
|
||||
use tsify_next::Tsify;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@@ -174,134 +173,6 @@ pub struct Reveal {
|
||||
pub server_identity: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub enum KeyType {
|
||||
P256,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
#[serde(transparent)]
|
||||
pub struct Attestation(pub(crate) tlsn::attestation::Attestation);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Attestation {
|
||||
pub fn verifying_key(&self) -> VerifyingKey {
|
||||
self.0.body.verifying_key().into()
|
||||
}
|
||||
|
||||
/// Serializes to a byte array.
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
bincode::serialize(self).expect("Attestation should be serializable")
|
||||
}
|
||||
|
||||
/// Deserializes from a byte array.
|
||||
pub fn deserialize(bytes: Vec<u8>) -> Result<Attestation, JsError> {
|
||||
Ok(bincode::deserialize(&bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn::attestation::Attestation> for Attestation {
|
||||
fn from(value: tlsn::attestation::Attestation) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
#[serde(transparent)]
|
||||
pub struct Secrets(pub(crate) tlsn::attestation::Secrets);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Secrets {
|
||||
/// Returns the transcript.
|
||||
pub fn transcript(&self) -> Transcript {
|
||||
self.0.transcript().into()
|
||||
}
|
||||
|
||||
/// Serializes to a byte array.
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
bincode::serialize(self).expect("Secrets should be serializable")
|
||||
}
|
||||
|
||||
/// Deserializes from a byte array.
|
||||
pub fn deserialize(bytes: Vec<u8>) -> Result<Secrets, JsError> {
|
||||
Ok(bincode::deserialize(&bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn::attestation::Secrets> for Secrets {
|
||||
fn from(value: tlsn::attestation::Secrets) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
#[serde(transparent)]
|
||||
pub struct Presentation(tlsn::attestation::presentation::Presentation);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Presentation {
|
||||
/// Returns the verifying key.
|
||||
pub fn verifying_key(&self) -> VerifyingKey {
|
||||
self.0.verifying_key().into()
|
||||
}
|
||||
|
||||
/// Verifies the presentation.
|
||||
pub fn verify(&self) -> Result<PresentationOutput, JsError> {
|
||||
let provider = CryptoProvider::default();
|
||||
|
||||
self.0
|
||||
.clone()
|
||||
.verify(&provider)
|
||||
.map(PresentationOutput::from)
|
||||
.map_err(JsError::from)
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
bincode::serialize(self).expect("Presentation should be serializable")
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: Vec<u8>) -> Result<Presentation, JsError> {
|
||||
Ok(bincode::deserialize(&bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn::attestation::presentation::Presentation> for Presentation {
|
||||
fn from(value: tlsn::attestation::presentation::Presentation) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
#[tsify(into_wasm_abi)]
|
||||
pub struct PresentationOutput {
|
||||
pub attestation: Attestation,
|
||||
pub server_name: Option<String>,
|
||||
pub connection_info: ConnectionInfo,
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
}
|
||||
|
||||
impl From<tlsn::attestation::presentation::PresentationOutput> for PresentationOutput {
|
||||
fn from(value: tlsn::attestation::presentation::PresentationOutput) -> Self {
|
||||
Self {
|
||||
attestation: value.attestation.into(),
|
||||
server_name: value.server_name.map(|name| name.as_str().to_string()),
|
||||
connection_info: value.connection_info.into(),
|
||||
transcript: value.transcript.map(PartialTranscript::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct NotarizationOutput {
|
||||
pub attestation: Attestation,
|
||||
pub secrets: Secrets,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
#[tsify(into_wasm_abi)]
|
||||
pub struct VerifierOutput {
|
||||
@@ -310,22 +181,6 @@ pub struct VerifierOutput {
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
#[tsify(into_wasm_abi)]
|
||||
pub struct VerifyingKey {
|
||||
pub alg: u8,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<&tlsn::attestation::signing::VerifyingKey> for VerifyingKey {
|
||||
fn from(value: &tlsn::attestation::signing::VerifyingKey) -> Self {
|
||||
Self {
|
||||
alg: value.alg.as_u8(),
|
||||
data: value.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub enum NetworkSetting {
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use config::VerifierConfig;
|
||||
use enum_try_as_inner::EnumTryAsInner;
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn::{
|
||||
connection::{ConnectionInfo, TranscriptLength},
|
||||
connection::{ConnectionInfo, ServerName, TranscriptLength},
|
||||
verifier::{
|
||||
state::{self, Initialized},
|
||||
Verifier, VerifyConfig,
|
||||
@@ -106,7 +106,10 @@ impl JsVerifier {
|
||||
self.state = State::Complete;
|
||||
|
||||
Ok(VerifierOutput {
|
||||
server_name: output.server_name.map(|s| s.as_str().to_string()),
|
||||
server_name: output.server_name.map(|name| {
|
||||
let ServerName::Dns(name) = name;
|
||||
name.to_string()
|
||||
}),
|
||||
connection_info: connection_info.into(),
|
||||
transcript: output.transcript.map(|t| t.into()),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user