From c708817a8b82dbb2e2eb8c78491eb96b55d51dc0 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 17 Mar 2026 09:48:13 +0100 Subject: [PATCH 01/44] WIP use nested-attested-tls for proxy --- Cargo.lock | 765 ++++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 11 +- src/lib.rs | 296 ++++++++++----------- 3 files changed, 863 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0459228..e01c501 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +17,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "alloy-json-rpc" version = "1.6.3" @@ -242,9 +263,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ark-ff" @@ -435,19 +456,41 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive 0.5.1", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "asn1-rs" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" dependencies = [ - "asn1-rs-derive", + "asn1-rs-derive 0.6.0", "asn1-rs-impl", "displaydoc", "nom", @@ -457,6 +500,18 @@ dependencies = [ "time", ] +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "synstructure", +] + [[package]] name = "asn1-rs-derive" version = "0.6.0" @@ -512,7 +567,39 @@ dependencies = [ "az-tdx-vtpm", "base64 0.22.1", "configfs-tsm", - "dcap-qvl", + "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override)", + "hex", + "http", + "num-bigint", + "once_cell", + "openssl", + "parity-scale-codec", + "pem-rfc7468", + "rand_core 0.6.4", + "reqwest", + "rustls-webpki", + "serde", + "serde_json", + "tdx-quote", + "thiserror 2.0.17", + "time", + "tokio", + "tokio-rustls", + "tracing", + "tss-esapi", + "x509-parser 0.18.1", +] + +[[package]] +name = "attestation" +version = "0.0.1" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +dependencies = [ + "anyhow", + "az-tdx-vtpm", + "base64 0.22.1", + "configfs-tsm", + "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override)", "hex", "http", "num-bigint", @@ -532,7 +619,7 @@ dependencies = [ "tokio-rustls", "tracing", "tss-esapi", - "x509-parser", + "x509-parser 0.18.1", ] [[package]] @@ -557,7 +644,7 @@ version = "0.0.1" dependencies = [ "alloy-rpc-client", "alloy-transport-http", - "attestation", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fadd-attestation-crate)", "bytes", "futures-util", "http", @@ -565,7 +652,7 @@ dependencies = [ "hyper", "hyper-util", "parity-scale-codec", - "rcgen", + "rcgen 0.14.7", "serde_json", "sha2", "tempfile", @@ -577,7 +664,26 @@ dependencies = [ "tracing", "url", "webpki-roots", - "x509-parser", + "x509-parser 0.18.1", +] + +[[package]] +name = "attested-tls" +version = "0.0.1" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +dependencies = [ + "anyhow", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", + "ra-tls", + "rcgen 0.14.7", + "rustls", + "serde_json", + "sha2", + "thiserror 2.0.17", + "tokio", + "tracing", + "x509-parser 0.18.1", + "yasna 0.5.2", ] [[package]] @@ -585,7 +691,8 @@ name = "attested-tls-proxy" version = "1.1.1" dependencies = [ "anyhow", - "attested-tls", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", + "attested-tls 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", "axum", "bytes", "clap", @@ -594,12 +701,13 @@ dependencies = [ "hyper", "hyper-util", "jsonrpsee", + "nested-tls", "p256", "pem-rfc7468", "pin-project-lite", "pkcs1", "pkcs8", - "rcgen", + "rcgen 0.14.7", "reqwest", "rsa", "rustls-pemfile", @@ -614,7 +722,7 @@ dependencies = [ "tracing", "tracing-subscriber", "webpki-roots", - "x509-parser", + "x509-parser 0.18.1", ] [[package]] @@ -855,6 +963,29 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -864,6 +995,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bon" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.108", +] + [[package]] name = "borsh" version = "1.6.0" @@ -887,6 +1043,27 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -924,6 +1101,23 @@ dependencies = [ "shlex", ] +[[package]] +name = "cc-eventlog" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "anyhow", + "digest 0.10.7", + "ez-hash", + "fs-err", + "hex", + "parity-scale-codec", + "serde", + "serde-human-bytes", + "serde_json", + "sha2", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -1004,6 +1198,18 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187437900921c8172f33316ad51a3267df588e99a2aebfa5ca1a2ed44df9e703" +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "const-hex" version = "1.17.0" @@ -1042,6 +1248,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -1086,6 +1298,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -1170,6 +1391,40 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.108", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.108", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -1179,7 +1434,44 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "dcap-qvl" version = "0.3.12" -source = "git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override#b61d8f3ffb59f225d7b98220e2185a66f1c7f8c7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e7842b81018f3b991dc65ec0a95ff347332de58478c4ac43459095af00cc89" +dependencies = [ + "anyhow", + "asn1_der", + "base64 0.22.1", + "borsh", + "byteorder", + "chrono", + "const-oid", + "dcap-qvl-webpki", + "der", + "derive_more 2.1.1", + "futures", + "hex", + "log", + "p256", + "parity-scale-codec", + "pem", + "reqwest", + "ring", + "rustls-pki-types", + "scale-info", + "serde", + "serde-human-bytes", + "serde_json", + "sha2", + "signature", + "tracing", + "urlencoding", + "wasm-bindgen-futures", + "x509-cert", +] + +[[package]] +name = "dcap-qvl" +version = "0.3.12" +source = "git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override#e38818f0b7b600ceadad1ec3efd9e681bcbdc1e5" dependencies = [ "anyhow", "asn1_der", @@ -1243,13 +1535,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "der-parser" version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", "displaydoc", "nom", "num-bigint", @@ -1370,7 +1676,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1384,6 +1690,44 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "dstack-attest" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "anyhow", + "cc-eventlog", + "dcap-qvl 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "dstack-types", + "errify", + "ez-hash", + "fs-err", + "hex", + "hex_fmt", + "insta", + "or-panic", + "parity-scale-codec", + "serde", + "serde-human-bytes", + "serde_json", + "sha2", + "sha3", + "tdx-attest", + "tracing", +] + +[[package]] +name = "dstack-types" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "parity-scale-codec", + "serde", + "serde-human-bytes", + "sha3", + "size-parser", +] + [[package]] name = "dunce" version = "1.0.5" @@ -1463,6 +1807,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1521,6 +1871,28 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errify" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb818c3c01af9cdeb367f7e92e290b9a080935cdc5fb6cc0c1193ae17032849" +dependencies = [ + "anyhow", + "errify-macros", +] + +[[package]] +name = "errify-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e87afa19e6030c2cf5514b00d5a242a3ea9492a2aa618635076914f5d15e7af" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.108", +] + [[package]] name = "errno" version = "0.3.14" @@ -1528,7 +1900,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "ez-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b3b3adc5fbbc9e21416d5b721b1bccb501a87d7b32ac89f2c7cea229d40772" +dependencies = [ + "blake2", + "blake3", + "digest 0.10.7", + "md-5", + "sha1", + "sha2", + "sha3", ] [[package]] @@ -1599,6 +1986,16 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1635,6 +2032,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" +dependencies = [ + "autocfg", +] + [[package]] name = "funty" version = "2.0.0" @@ -1826,6 +2232,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + [[package]] name = "hickory-proto" version = "0.25.2" @@ -1872,6 +2284,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2102,6 +2523,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -2155,6 +2582,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "insta" +version = "1.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + [[package]] name = "iocuddle" version = "0.1.1" @@ -2374,9 +2813,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" @@ -2463,6 +2902,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.7.6" @@ -2500,6 +2949,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.0" @@ -2546,6 +3005,30 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nested-tls" +version = "0.0.1" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +dependencies = [ + "rustls", + "tokio", + "tokio-rustls", + "tracing", +] + +[[package]] +name = "nix" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.3" @@ -2562,7 +3045,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2647,13 +3130,22 @@ dependencies = [ "serde", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs 0.6.2", +] + [[package]] name = "oid-registry" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", ] [[package]] @@ -2773,6 +3265,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "or-panic" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "596a79faf55e869e7bc0c2162cf2f18a54d4d1112876bceae587ad954fcbd574" + [[package]] name = "p256" version = "0.13.2" @@ -3026,6 +3524,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.108", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -3086,6 +3594,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.10.0" @@ -3163,7 +3684,7 @@ dependencies = [ "once_cell", "socket2 0.6.1", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -3181,6 +3702,43 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "ra-tls" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "anyhow", + "bon", + "cc-eventlog", + "dcap-qvl 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "dstack-attest", + "dstack-types", + "elliptic-curve", + "errify", + "ez-hash", + "flate2", + "fs-err", + "hex", + "hex_fmt", + "hkdf", + "or-panic", + "p256", + "parity-scale-codec", + "rand 0.8.5", + "rcgen 0.13.2", + "ring", + "rmp-serde", + "rustls-pki-types", + "serde", + "serde-human-bytes", + "serde_json", + "sha2", + "sha3", + "tracing", + "x509-parser 0.16.0", + "yasna 0.5.2", +] + [[package]] name = "radium" version = "0.7.0" @@ -3268,14 +3826,29 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.14.5" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fae430c6b28f1ad601274e78b7dffa0546de0b73b4cd32f46723c0c2a16f7a5" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ "pem", "ring", "rustls-pki-types", "time", + "x509-parser 0.16.0", + "yasna 0.5.2", +] + +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", "yasna 0.5.2", ] @@ -3422,6 +3995,25 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -3532,15 +4124,17 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.34" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "brotli", + "brotli-decompressor", "once_cell", "ring", "rustls-pki-types", @@ -3560,9 +4154,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -3725,10 +4319,11 @@ dependencies = [ [[package]] name = "serde-human-bytes" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ef65cb41f3f9cef63c431193229067e8b98b53c4d4c4ed38a8ca87c4d07676" +checksum = "6a091af6294712930d01e375cce513e4ac416f823e033e8991ec4e5d6e6ef4c0" dependencies = [ + "base64 0.13.1", "hex", "serde", ] @@ -3902,6 +4497,28 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "size-parser" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "anyhow", + "serde", + "thiserror 2.0.17", +] + [[package]] name = "slab" version = "0.4.11" @@ -4083,6 +4700,25 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tdx-attest" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "anyhow", + "cc-eventlog", + "fs-err", + "hex", + "libc", + "parity-scale-codec", + "serde", + "serde-human-bytes", + "serde_json", + "sha2", + "thiserror 2.0.17", + "vsock", +] + [[package]] name = "tdx-quote" version = "0.0.5" @@ -4107,7 +4743,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4217,9 +4853,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -4389,9 +5025,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -4401,9 +5037,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -4412,9 +5048,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -4689,6 +5325,16 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" +[[package]] +name = "vsock" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b82aeb12ad864eb8cd26a6c21175d0bdc66d398584ee6c93c76964c3bcfc78ff" +dependencies = [ + "libc", + "nix", +] + [[package]] name = "wait-timeout" version = "0.2.1" @@ -4889,6 +5535,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -5158,16 +5813,34 @@ dependencies = [ [[package]] name = "x509-parser" -version = "0.18.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", "data-encoding", - "der-parser", + "der-parser 9.0.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.7.1", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs 0.7.1", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", "ring", "rusticata-macros", "thiserror 2.0.17", @@ -5195,6 +5868,12 @@ dependencies = [ "x509-ocsp", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yasna" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 40a3ab3..c285277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,10 @@ repository = "https://github.com/flashbots/attested-tls-proxy" keywords = ["attested-TLS", "CVM", "TDX"] [dependencies] -attested-tls = { path = "attested-tls", default-features = false } -tokio = { version = "1.48.0", features = ["full"] } +attested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } +nested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } +tokio = { version = "1.50.0", features = ["full"] } tokio-rustls = { version = "0.26.4", default-features = false } x509-parser = { version = "0.18.0", features = ["verify"] } thiserror = "2.0.17" @@ -45,14 +47,15 @@ pin-project-lite = "0.2.16" [dev-dependencies] tempfile = "3.23.0" tdx-quote = { version = "0.0.5", features = ["mock"] } -attested-tls = { path = "attested-tls", features = ["test-helpers", "mock"] } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate", features = ["mock"] } +tokio = { version = "1.48.0", features = ["full"] } jsonrpsee = { version = "0.26.0", features = ["server"] } [features] default = [] # Adds support for Microsoft Azure attestation generation and verification -azure = ["attested-tls/azure"] +azure = ["attestation/azure"] [package.metadata.deb] maintainer = "Flashbots Team " diff --git a/src/lib.rs b/src/lib.rs index 8e0ce80..3830941 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,24 +1,29 @@ //! An attested TLS protocol and HTTPS proxy -pub mod attested_get; +// pub mod attested_get; pub mod file_server; pub mod health_check; pub mod normalize_pem; -pub mod self_signed; +// pub mod self_signed; -pub use attested_tls; -pub use attested_tls::attestation; -pub use attested_tls::attestation::AttestationGenerator; +pub use attestation; +pub use attestation::AttestationGenerator; mod http_version; #[cfg(test)] mod test_helpers; +use attestation::{ + AttestationError, AttestationType, AttestationVerifier, measurements::MultiMeasurements, +}; +use attested_tls::{AttestedCertificateResolver, AttestedCertificateVerifier}; use bytes::Bytes; use http::{HeaderMap, HeaderName, HeaderValue}; use http_body_util::{BodyExt, combinators::BoxBody}; use hyper::{Response, service::service_fn}; use hyper_util::rt::TokioIo; +use nested_tls::server::NestingTlsStream; +use nested_tls::{client::NestingTlsConnector, server::NestingTlsAcceptor}; use std::{net::SocketAddr, num::TryFromIntError, sync::Arc, time::Duration}; use thiserror::Error; use tokio::io; @@ -26,17 +31,12 @@ use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{mpsc, oneshot}; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; use tokio_rustls::rustls::{ - self, ClientConfig, RootCertStore, ServerConfig, pki_types::CertificateDer, + self, ClientConfig, RootCertStore, ServerConfig, + pki_types::{CertificateDer, PrivateKeyDer, ServerName}, }; use tracing::{debug, error, warn}; use crate::http_version::{ALPN_H2, ALPN_HTTP11, HttpConnection, HttpSender, HttpVersion}; -use attested_tls::{ - AttestedTlsClient, AttestedTlsError, AttestedTlsServer, TlsCertAndKey, - attestation::{ - AttestationError, AttestationType, AttestationVerifier, measurements::MultiMeasurements, - }, -}; /// The header name for giving attestation type const ATTESTATION_TYPE_HEADER: &str = "X-Flashbots-Attestation-Type"; @@ -61,6 +61,14 @@ type RequestWithResponseSender = ( oneshot::Sender>, hyper::Error>>, ); +/// TLS Credentials +pub struct TlsCertAndKey { + /// Der-encoded TLS certificate chain + pub cert_chain: Vec>, + /// Der-encoded TLS private key + pub key: PrivateKeyDer<'static>, +} + /// Adds HTTP 1 and 2 to the list of allowed protocols fn ensure_proxy_alpn_protocols(alpn_protocols: &mut Vec>) { for protocol in [ALPN_H2, ALPN_HTTP11] { @@ -72,33 +80,32 @@ fn ensure_proxy_alpn_protocols(alpn_protocols: &mut Vec>) { } } -/// Retrieve the attested remote TLS certificate. -pub async fn get_tls_cert( - server_name: String, - attestation_verifier: AttestationVerifier, - remote_certificate: Option>, - allow_self_signed: bool, -) -> Result<(Vec>, Option), AttestedTlsError> { - let (cert, measurements) = if allow_self_signed { - let client_tls_config = self_signed::client_tls_config_allow_self_signed()?; - attested_tls::get_tls_cert_with_config( - &server_name, - attestation_verifier, - client_tls_config, - ) - .await? - } else { - attested_tls::get_tls_cert(server_name, attestation_verifier, remote_certificate).await? - }; - - debug!("[get-tls-cert] Connected to proxy server with measurements: {measurements:?}"); - Ok((cert, measurements)) -} +// /// Retrieve the attested remote TLS certificate. +// pub async fn get_tls_cert( +// server_name: String, +// attestation_verifier: AttestationVerifier, +// remote_certificate: Option>, +// allow_self_signed: bool, +// ) -> Result<(Vec>, Option), AttestedTlsError> { +// let (cert, measurements) = if allow_self_signed { +// let client_tls_config = self_signed::client_tls_config_allow_self_signed()?; +// attested_tls::get_tls_cert_with_config( +// &server_name, +// attestation_verifier, +// client_tls_config, +// ) +// .await? +// } else { +// attested_tls::get_tls_cert(server_name, attestation_verifier, remote_certificate).await? +// }; +// +// debug!("[get-tls-cert] Connected to proxy server with measurements: {measurements:?}"); +// Ok((cert, measurements)) +// } /// A TLS over TCP server which provides an attestation before forwarding traffic to a given target address pub struct ProxyServer { - /// The underlying attested TLS server - attested_tls_server: AttestedTlsServer, + nesting_tls_acceptor: NestingTlsAcceptor, /// The underlying TCP listener listener: Arc, /// The address/hostname of the target service we are proxying to @@ -114,7 +121,7 @@ impl ProxyServer { attestation_verifier: AttestationVerifier, client_auth: bool, ) -> Result { - let mut server_config = if client_auth { + let outer_server_config = if client_auth { let root_store = RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?; @@ -133,46 +140,49 @@ impl ProxyServer { cert_and_key.key.clone_key(), )? }; - ensure_proxy_alpn_protocols(&mut server_config.alpn_protocols); - let attested_tls_server = AttestedTlsServer::new_with_tls_config( + Self::new_with_tls_config( cert_and_key.cert_chain, - server_config, + outer_server_config, + local, + target, attestation_generator, attestation_verifier, - )?; - - let listener = TcpListener::bind(local).await?; - - Ok(Self { - attested_tls_server, - listener: listener.into(), - target, - }) + ) + .await } /// Start with preconfigured TLS pub async fn new_with_tls_config( cert_chain: Vec>, - mut server_config: ServerConfig, + mut outer_server_config: ServerConfig, local: impl ToSocketAddrs, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, ) -> Result { - ensure_proxy_alpn_protocols(&mut server_config.alpn_protocols); - - let attested_tls_server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, + ensure_proxy_alpn_protocols(&mut outer_server_config.alpn_protocols); + let server_name = hostname_from_cert(cert_chain.get(0).unwrap()).unwrap(); + let inner_cert_resolver = AttestedCertificateResolver::new( attestation_generator, - attestation_verifier, - )?; + None, + server_name.to_string(), // TODO get name from outer certificate + vec![], + ) + .await + .unwrap(); + + let inner_server_config = + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_no_client_auth() // TODO + .with_cert_resolver(Arc::new(inner_cert_resolver)); + let nesting_tls_acceptor = + NestingTlsAcceptor::new(Arc::new(outer_server_config), Arc::new(inner_server_config)); let listener = TcpListener::bind(local).await?; Ok(Self { - attested_tls_server, + nesting_tls_acceptor, listener: listener.into(), target, }) @@ -184,19 +194,12 @@ impl ProxyServer { pub async fn accept(&self) -> Result, ProxyError> { let target = self.target.clone(); let (inbound, client_addr) = self.listener.accept().await?; - let attested_tls_server = self.attested_tls_server.clone(); + let nesting_tls_acceptor = self.nesting_tls_acceptor.clone(); let join_handle = tokio::spawn(async move { - match attested_tls_server.handle_connection(inbound).await { - Ok((tls_stream, measurements, attestation_type)) => { - if let Err(err) = Self::handle_connection( - tls_stream, - measurements, - attestation_type, - target, - client_addr, - ) - .await + match nesting_tls_acceptor.accept(inbound).await { + Ok(tls_stream) => { + if let Err(err) = Self::handle_connection(tls_stream, target, client_addr).await { warn!("Failed to handle connection: {err}"); } @@ -217,13 +220,11 @@ impl ProxyServer { /// Handle an incoming connection from a proxy-client async fn handle_connection( - tls_stream: tokio_rustls::server::TlsStream, - measurements: Option, - remote_attestation_type: AttestationType, + tls_stream: NestingTlsStream, target: String, client_addr: SocketAddr, ) -> Result<(), ProxyError> { - debug!("[proxy-server] accepted connection with measurements: {measurements:?}"); + debug!("[proxy-server] accepted connection"); let http_version = HttpVersion::from_negotiated_protocol_server(&tls_stream); @@ -251,27 +252,6 @@ impl ProxyServer { update_header(headers, &X_FORWARDED_FOR, &new_x_forwarded_for); - // If we have measurements, from the remote peer, add them to the request header - let measurements = measurements.clone(); - if let Some(measurements) = measurements { - match measurements.to_header_format() { - Ok(header_value) => { - headers.insert(MEASUREMENT_HEADER, header_value); - } - Err(e) => { - // This error is highly unlikely - that the measurement values fail to - // encode to JSON or fit in an HTTP header - error!("Failed to encode measurement values: {e}"); - } - } - } - - update_header( - headers, - ATTESTATION_TYPE_HEADER, - remote_attestation_type.as_str(), - ); - let target = target.clone(); async move { match Self::handle_http_request(req, target).await { @@ -381,7 +361,7 @@ impl ProxyClient { None => RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()), }; - let mut client_config = if let Some(ref cert_and_key) = cert_and_key { + let mut outer_client_config = if let Some(ref cert_and_key) = cert_and_key { ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .with_root_certificates(root_store) .with_client_auth_cert( @@ -393,43 +373,48 @@ impl ProxyClient { .with_root_certificates(root_store) .with_no_client_auth() }; - ensure_proxy_alpn_protocols(&mut client_config.alpn_protocols); - let attested_tls_client = AttestedTlsClient::new_with_tls_config( - client_config, + Self::new_with_tls_config( + outer_client_config, + address, + server_name, attestation_generator, attestation_verifier, - cert_and_key.map(|c| c.cert_chain), - )?; - - Self::new_with_inner(address, attested_tls_client, &server_name).await + remote_certificate, + ) + .await } /// Create a new proxy client with given TLS configuration pub async fn new_with_tls_config( - mut client_config: ClientConfig, + mut outer_client_config: ClientConfig, address: impl ToSocketAddrs, target_name: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, cert_chain: Option>>, ) -> Result { - ensure_proxy_alpn_protocols(&mut client_config.alpn_protocols); + ensure_proxy_alpn_protocols(&mut outer_client_config.alpn_protocols); - let attested_tls_client = AttestedTlsClient::new_with_tls_config( - client_config, - attestation_generator, - attestation_verifier, - cert_chain, - )?; + let attested_cert_verifier = + AttestedCertificateVerifier::new(None, attestation_verifier).unwrap(); + + let inner_client_config = + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .dangerous() + .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) + .with_no_client_auth(); + + let nesting_tls_connector = + NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); - Self::new_with_inner(address, attested_tls_client, &target_name).await + Self::new_with_inner(address, nesting_tls_connector, &target_name).await } /// Create a new proxy client with given [AttestedTlsClient] pub async fn new_with_inner( address: impl ToSocketAddrs, - attested_tls_client: AttestedTlsClient, + nesting_tls_connector: NestingTlsConnector, target_name: &str, ) -> Result { let listener = TcpListener::bind(address).await?; @@ -452,9 +437,9 @@ impl ProxyClient { let mut first = true; let mut ready_tx = Some(ready_tx); 'reconnect: loop { - let (mut sender, conn, measurements, remote_attestation_type) = + let (mut sender, conn) = // Connect to the proxy server and provide / verify attestation - match Self::setup_connection_with_backoff(&target, &attested_tls_client, first) + match Self::setup_connection_with_backoff(&target, &nesting_tls_connector, first) .await { Ok(output) => { @@ -494,29 +479,8 @@ impl ProxyClient { debug!("[proxy-client] Read incoming request from source client: {req:?}"); // Attempt to forward it to the proxy server let (response, should_reconnect) = match sender.send_request(req).await { - Ok(mut resp) => { + Ok(resp) => { debug!("[proxy-client] Read response from proxy-server: {resp:?}"); - // If we have measurements from the proxy-server, inject them into the - // response header - let headers = resp.headers_mut(); - if let Some(measurements) = measurements.clone() { - match measurements.to_header_format() { - Ok(header_value) => { - headers.insert(MEASUREMENT_HEADER, header_value); - } - Err(e) => { - // This error is highly unlikely - that the measurement values fail to - // encode to JSON or fit in an HTTP header - error!("Failed to encode measurement values: {e}"); - } - } - } - - update_header( - headers, - ATTESTATION_TYPE_HEADER, - remote_attestation_type.as_str(), - ); (Ok(resp.map(|b| b.boxed())), false) } Err(e) => { @@ -622,22 +586,14 @@ impl ProxyClient { // If it fails retry with a backoff (indefinately) async fn setup_connection_with_backoff( target: &str, - attested_tls_client: &AttestedTlsClient, + nesting_tls_connector: &NestingTlsConnector, should_bail: bool, - ) -> Result< - ( - HttpSender, - HttpConnection, - Option, - AttestationType, - ), - ProxyError, - > { + ) -> Result<(HttpSender, HttpConnection), ProxyError> { let mut delay = Duration::from_secs(1); let max_delay = Duration::from_secs(SERVER_RECONNECT_MAX_BACKOFF_SECS); loop { - match Self::setup_connection(attested_tls_client, target).await { + match Self::setup_connection(nesting_tls_connector, target).await { Ok(output) => { return Ok(output); } @@ -659,19 +615,16 @@ impl ProxyClient { /// Connect to the proxy-server, do TLS handshake and remote attestation async fn setup_connection( - inner: &AttestedTlsClient, + nesting_tls_connector: &NestingTlsConnector, target: &str, - ) -> Result< - ( - HttpSender, - HttpConnection, - Option, - AttestationType, - ), - ProxyError, - > { - let (tls_stream, measurements, remote_attestation_type) = inner.connect_tcp(target).await?; - debug!("[proxy-client] Connected to proxy server with measurements: {measurements:?}"); + ) -> Result<(HttpSender, HttpConnection), ProxyError> { + let outbound_stream = tokio::net::TcpStream::connect(target).await?; + + let domain = ServerName::try_from(target).unwrap(); + let tls_stream = nesting_tls_connector + .connect(domain, outbound_stream) + .await?; + debug!("[proxy-client] Connected to proxy server"); // The attestation exchange is now complete - setup an HTTP client let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); @@ -697,7 +650,7 @@ impl ProxyClient { }; // Return the HTTP client, as well as remote measurements - Ok((sender, conn, measurements, remote_attestation_type)) + Ok((sender, conn)) } // Handle a request from the source client to the proxy server @@ -755,8 +708,8 @@ pub enum ProxyError { OneShotRecv(#[from] oneshot::error::RecvError), #[error("Failed to send request, connection to proxy-server dropped")] MpscSend, - #[error("Attested TLS: {0}")] - AttestedTls(#[from] AttestedTlsError), + // #[error("Attested TLS: {0}")] + // AttestedTls(#[from] AttestedTlsError), } impl From> for ProxyError { @@ -765,6 +718,24 @@ impl From> for ProxyError { } } +/// Given a certifcate, get the hostname +fn hostname_from_cert(cert: &CertificateDer<'static>) -> Result { + let cert = x509_parser::parse_x509_certificate(cert.as_ref()) + .map(|(_, parsed)| parsed) + .unwrap(); + + Ok(cert + .subject() + .iter_common_name() + .next() + .unwrap() + // .ok_or_else(|| Self::bad_encoding("Missing common name"))? + .as_str() + // .map_err(|err| Self::bad_encoding(format!("Invalid common name: {err}"))) + .unwrap() + .to_string()) +} + /// If no port was provided, default to 443 pub(crate) fn host_to_host_with_port(host: &str) -> String { if host.contains(':') { @@ -1437,6 +1408,7 @@ mod tests { .unwrap() .to_str() .unwrap(); + assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); From cec9c9e95c152ff2225d14b8baa0337bd196120b Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 17 Mar 2026 10:37:29 +0100 Subject: [PATCH 02/44] Basic tests pass --- src/http_version.rs | 11 +- src/lib.rs | 1223 ++++++++++++++++++------------------- src/{main.rs => main.rs_} | 0 src/test_helpers.rs | 41 +- 4 files changed, 634 insertions(+), 641 deletions(-) rename src/{main.rs => main.rs_} (100%) diff --git a/src/http_version.rs b/src/http_version.rs index bef817c..901df66 100644 --- a/src/http_version.rs +++ b/src/http_version.rs @@ -7,6 +7,9 @@ use std::task::{Context, Poll}; pub const ALPN_H2: &[u8] = b"h2"; pub const ALPN_HTTP11: &[u8] = b"http/1.1"; +type ProxyClientTlsStream = + tokio_rustls::client::TlsStream>; + /// Supported HTTP versions #[derive(Debug)] pub enum HttpVersion { @@ -55,13 +58,11 @@ impl HttpVersion { type Http1Sender = hyper::client::conn::http1::SendRequest; type Http2Sender = hyper::client::conn::http2::SendRequest; -type Http1Connection = hyper::client::conn::http1::Connection< - TokioIo>, - hyper::body::Incoming, ->; +type Http1Connection = + hyper::client::conn::http1::Connection, hyper::body::Incoming>; type Http2Connection = hyper::client::conn::http2::Connection< - TokioIo>, + TokioIo, hyper::body::Incoming, crate::TokioExecutor, >; diff --git a/src/lib.rs b/src/lib.rs index 3830941..1d48551 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! An attested TLS protocol and HTTPS proxy // pub mod attested_get; -pub mod file_server; +// pub mod file_server; pub mod health_check; pub mod normalize_pem; // pub mod self_signed; @@ -13,9 +13,7 @@ mod http_version; #[cfg(test)] mod test_helpers; -use attestation::{ - AttestationError, AttestationType, AttestationVerifier, measurements::MultiMeasurements, -}; +use attestation::{AttestationError, AttestationVerifier}; use attested_tls::{AttestedCertificateResolver, AttestedCertificateVerifier}; use bytes::Bytes; use http::{HeaderMap, HeaderName, HeaderValue}; @@ -38,12 +36,6 @@ use tracing::{debug, error, warn}; use crate::http_version::{ALPN_H2, ALPN_HTTP11, HttpConnection, HttpSender, HttpVersion}; -/// The header name for giving attestation type -const ATTESTATION_TYPE_HEADER: &str = "X-Flashbots-Attestation-Type"; - -/// The header name for giving measurements -const MEASUREMENT_HEADER: &str = "X-Flashbots-Measurement"; - /// The header name for giving the forwarded for IP static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); @@ -352,16 +344,16 @@ impl ProxyClient { attestation_verifier: AttestationVerifier, remote_certificate: Option>, ) -> Result { - let root_store = match remote_certificate { + let root_store = match remote_certificate.as_ref() { Some(remote_certificate) => { let mut root_store = RootCertStore::empty(); - root_store.add(remote_certificate)?; + root_store.add(remote_certificate.clone())?; root_store } None => RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()), }; - let mut outer_client_config = if let Some(ref cert_and_key) = cert_and_key { + let outer_client_config = if let Some(ref cert_and_key) = cert_and_key { ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .with_root_certificates(root_store) .with_client_auth_cert( @@ -380,7 +372,7 @@ impl ProxyClient { server_name, attestation_generator, attestation_verifier, - remote_certificate, + remote_certificate.map(|certificate| vec![certificate]), ) .await } @@ -620,7 +612,7 @@ impl ProxyClient { ) -> Result<(HttpSender, HttpConnection), ProxyError> { let outbound_stream = tokio::net::TcpStream::connect(target).await?; - let domain = ServerName::try_from(target).unwrap(); + let domain = server_name_from_host(target).unwrap(); let tls_stream = nesting_tls_connector .connect(domain, outbound_stream) .await?; @@ -745,6 +737,16 @@ pub(crate) fn host_to_host_with_port(host: &str) -> String { } } +/// Given a hostname with or without port number, create a TLS [ServerName] with just the host part +fn server_name_from_host( + host: &str, +) -> Result, tokio_rustls::rustls::pki_types::InvalidDnsNameError> { + let host_part = host.rsplit_once(':').map(|(h, _)| h).unwrap_or(host); + let host_part = host_part.trim_matches(|c| c == '[' || c == ']'); + + ServerName::try_from(host_part.to_string()) +} + /// An Executor for hyper that uses the tokio runtime #[derive(Clone)] pub(crate) struct TokioExecutor; @@ -763,13 +765,12 @@ where #[cfg(test)] mod tests { - use crate::{ - attestation::measurements::MeasurementPolicy, attested_tls::get_tls_cert_with_config, - }; + use attestation::{AttestationType, measurements::MeasurementPolicy}; use super::*; use test_helpers::{ - example_http_service, generate_certificate_chain, generate_tls_config, + example_http_service, generate_certificate_chain, generate_certificate_chain_for_host, + generate_tls_config, generate_tls_config_with_client_auth, init_tracing, mock_dcap_measurements, }; @@ -789,11 +790,11 @@ mod tests { assert_eq!(protocols, vec![ALPN_HTTP11.to_vec(), ALPN_H2.to_vec()]); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn http_proxy_default_constructors_work() { let target_addr = example_http_service().await; - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let server_cert = cert_chain[0].clone(); let proxy_server = ProxyServer::new( @@ -819,7 +820,7 @@ mod tests { let proxy_client = ProxyClient::new( None, "127.0.0.1:0".to_string(), - proxy_addr.to_string(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), Some(server_cert), @@ -837,510 +838,17 @@ mod tests { .await .unwrap(); - let headers = res.headers(); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - - let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - let measurements = - MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - .unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - let res_body = res.text().await.unwrap(); assert_eq!(res_body, "No measurements"); } // Server has mock DCAP, client has no attestation and no client auth - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn http_proxy_with_server_attestation() { let _ = tracing_subscriber::fmt::try_init(); let target_addr = example_http_service().await; - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .await - .unwrap(); - - let proxy_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_server.accept().await.unwrap(); - }); - - let proxy_client = ProxyClient::new_with_tls_config( - client_config, - "127.0.0.1:0".to_string(), - proxy_addr.to_string(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .await - .unwrap(); - - let proxy_client_addr = proxy_client.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_client.accept().await.unwrap(); - }); - - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - .await - .unwrap(); - - let headers = res.headers(); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - - let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - let measurements = - MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - .unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - - let res_body = res.text().await.unwrap(); - assert_eq!(res_body, "No measurements"); - } - - // Server has no attestation, client has mock DCAP and client auth - #[tokio::test] - async fn http_proxy_client_attestation() { - let target_addr = example_http_service().await; - - let (server_cert_chain, server_private_key) = - generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (client_cert_chain, client_private_key) = - generate_certificate_chain("127.0.0.1".parse().unwrap()); - - let ( - (_client_tls_server_config, client_tls_client_config), - (server_tls_server_config, _server_tls_client_config), - ) = generate_tls_config_with_client_auth( - client_cert_chain.clone(), - client_private_key, - server_cert_chain.clone(), - server_private_key, - ); - - let proxy_server = ProxyServer::new_with_tls_config( - server_cert_chain, - server_tls_server_config, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - ) - .await - .unwrap(); - - let proxy_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - // Accept one connection, then finish - proxy_server.accept().await.unwrap(); - }); - - let proxy_client = ProxyClient::new_with_tls_config( - client_tls_client_config, - "127.0.0.1:0", - proxy_addr.to_string(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - Some(client_cert_chain), - ) - .await - .unwrap(); - - let proxy_client_addr = proxy_client.local_addr().unwrap(); - - tokio::spawn(async move { - // Accept two connections, then finish - proxy_client.accept().await.unwrap(); - proxy_client.accept().await.unwrap(); - }); - - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - .await - .unwrap(); - - // We expect no measurements from the server - let headers = res.headers(); - assert!(headers.get(MEASUREMENT_HEADER).is_none()); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(attestation_type, AttestationType::None.as_str()); - - let res_body = res.text().await.unwrap(); - - // The response body shows us what was in the request header (as the test http server - // handler puts them there) - let measurements = - MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - } - - // Server has no attestation, client has mock DCAP but no client auth - #[tokio::test] - async fn http_proxy_client_attestation_no_client_auth() { - let target_addr = example_http_service().await; - - let (server_cert_chain, server_private_key) = - generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = - generate_tls_config(server_cert_chain.clone(), server_private_key); - - let proxy_server = ProxyServer::new_with_tls_config( - server_cert_chain, - server_config, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - ) - .await - .unwrap(); - - let proxy_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - // Accept one connection, then finish - proxy_server.accept().await.unwrap(); - }); - - let proxy_client = ProxyClient::new_with_tls_config( - client_config, - "127.0.0.1:0", - proxy_addr.to_string(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - None, - ) - .await - .unwrap(); - - let proxy_client_addr = proxy_client.local_addr().unwrap(); - - tokio::spawn(async move { - // Accept two connections, then finish - proxy_client.accept().await.unwrap(); - proxy_client.accept().await.unwrap(); - }); - - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - .await - .unwrap(); - - // We expect no measurements from the server - let headers = res.headers(); - assert!(headers.get(MEASUREMENT_HEADER).is_none()); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(attestation_type, AttestationType::None.as_str()); - - let res_body = res.text().await.unwrap(); - - // The response body shows us what was in the request header (as the test http server - // handler puts them there) - let measurements = - MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - } - - // Server has mock DCAP, client has mock DCAP and client auth - #[tokio::test] - async fn http_proxy_mutual_attestation() { - let target_addr = example_http_service().await; - - let (server_cert_chain, server_private_key) = - generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (client_cert_chain, client_private_key) = - generate_certificate_chain("127.0.0.1".parse().unwrap()); - - let ( - (_client_tls_server_config, client_tls_client_config), - (server_tls_server_config, _server_tls_client_config), - ) = generate_tls_config_with_client_auth( - client_cert_chain.clone(), - client_private_key, - server_cert_chain.clone(), - server_private_key, - ); - - let proxy_server = ProxyServer::new_with_tls_config( - server_cert_chain, - server_tls_server_config, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::mock(), - ) - .await - .unwrap(); - - let proxy_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - // Accept one connection, then finish - proxy_server.accept().await.unwrap(); - }); - - let proxy_client = ProxyClient::new_with_tls_config( - client_tls_client_config, - "127.0.0.1:0", - proxy_addr.to_string(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::mock(), - Some(client_cert_chain), - ) - .await - .unwrap(); - - let proxy_client_addr = proxy_client.local_addr().unwrap(); - - tokio::spawn(async move { - // Accept two connections, then finish - proxy_client.accept().await.unwrap(); - proxy_client.accept().await.unwrap(); - }); - - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - .await - .unwrap(); - - let headers = res.headers(); - let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - let measurements = - MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - .unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - - let res_body = res.text().await.unwrap(); - - // The response body shows us what was in the request header (as the test http server - // handler puts them there) - let measurements = - MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - - // Now do another request - to check that the connection has stayed open - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - .await - .unwrap(); - - let headers = res.headers(); - let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - let measurements = - MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - .unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - - let res_body = res.text().await.unwrap(); - - // The response body shows us what was in the request header (as the test http server - // handler puts them there) - let measurements = - MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - } - - // Server has mock DCAP, client no attestation - just get the server certificate - #[tokio::test] - async fn test_get_tls_cert() { - let target_addr = example_http_service().await; - - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let proxy_server = ProxyServer::new_with_tls_config( - cert_chain.clone(), - server_config, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .await - .unwrap(); - - let proxy_server_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_server.accept().await.unwrap(); - }); - - let (retrieved_chain, _measurements) = get_tls_cert_with_config( - &proxy_server_addr.to_string(), - AttestationVerifier::mock(), - client_config, - ) - .await - .unwrap(); - - assert_eq!(retrieved_chain, cert_chain); - } - - // Negative test - server does not provide attestation but client requires it - // Server has no attestaion, client has no attestation and no client auth - #[tokio::test] - async fn fails_on_no_attestation_when_expected() { - let target_addr = example_http_service().await; - - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::expect_none(), - ) - .await - .unwrap(); - - let proxy_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_server.accept().await.unwrap(); - }); - - let proxy_client_result = ProxyClient::new_with_tls_config( - client_config, - "127.0.0.1:0".to_string(), - proxy_addr.to_string(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .await; - - assert!(matches!( - proxy_client_result.unwrap_err(), - ProxyError::AttestedTls(AttestedTlsError::Attestation( - AttestationError::AttestationTypeNotAccepted - )) - )); - } - - // Negative test - server does not provide attestation but client requires it - // Server has no attestaion, client has no attestation and no client auth - #[tokio::test] - async fn fails_on_bad_measurements() { - let target_addr = example_http_service().await; - - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .await - .unwrap(); - - let proxy_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_server.accept().await.unwrap(); - }); - - let measurement_policy = MeasurementPolicy::from_json_bytes( - br#" - [{ - "measurement_id": "test", - "attestation_type": "dcap-tdx", - "measurements": { - "0": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - "1": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - "2": { "expected": "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" }, - "3": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - "4": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } - } - }] - "# - .to_vec(), - ) - .unwrap(); - - let attestation_verifier = AttestationVerifier { - measurement_policy, - pccs_url: None, - log_dcap_quote: false, - override_azure_outdated_tcb: false, - }; - - let proxy_client_result = ProxyClient::new_with_tls_config( - client_config, - "127.0.0.1:0".to_string(), - proxy_addr.to_string(), - AttestationGenerator::with_no_attestation(), - attestation_verifier, - None, - ) - .await; - - assert!(matches!( - proxy_client_result.unwrap_err(), - ProxyError::AttestedTls(AttestedTlsError::Attestation( - AttestationError::MeasurementsNotAccepted - )) - )); - } - - #[tokio::test] - async fn http_proxy_client_reconnects_on_lost_connection() { - init_tracing(); - - let target_addr = example_http_service().await; - - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); let proxy_server = ProxyServer::new_with_tls_config( @@ -1356,25 +864,14 @@ mod tests { let proxy_addr = proxy_server.local_addr().unwrap(); - // This is used to trigger a dropped connection to the proxy server - let (connection_breaker_tx, connection_breaker_rx) = oneshot::channel(); - tokio::spawn(async move { - let connection_handle = proxy_server.accept().await.unwrap(); - - // Wait for a signal to simulate a dropped connection, then drop the task handling the - // connection - connection_breaker_rx.await.unwrap(); - connection_handle.abort(); - - // Now accept another connection proxy_server.accept().await.unwrap(); }); let proxy_client = ProxyClient::new_with_tls_config( client_config, "127.0.0.1:0".to_string(), - proxy_addr.to_string(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), None, @@ -1386,111 +883,585 @@ mod tests { tokio::spawn(async move { proxy_client.accept().await.unwrap(); - proxy_client.accept().await.unwrap(); }); - let _initial_response = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - .await - .unwrap(); - - // Now break the connection - connection_breaker_tx.send(()).unwrap(); - - // Make another request let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) .await .unwrap(); - let headers = res.headers(); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - - let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - let measurements = - MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - .unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - let res_body = res.text().await.unwrap(); assert_eq!(res_body, "No measurements"); } - // Use HTTP 1.1 - #[tokio::test] - async fn http_proxy_with_http1() { - let target_addr = example_http_service().await; - - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (mut server_config, client_config) = - generate_tls_config(cert_chain.clone(), private_key); - - server_config.alpn_protocols.push(ALPN_HTTP11.to_vec()); - - let attested_tls_server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - - let proxy_server = ProxyServer { - attested_tls_server, - listener: listener.into(), - target: target_addr.to_string(), - }; - - let proxy_addr = proxy_server.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_server.accept().await.unwrap(); - }); - - let proxy_client = ProxyClient::new_with_tls_config( - client_config, - "127.0.0.1:0".to_string(), - proxy_addr.to_string(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .await - .unwrap(); - - let proxy_client_addr = proxy_client.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_client.accept().await.unwrap(); - }); - - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - .await - .unwrap(); - - let headers = res.headers(); - - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - - let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - let measurements = - MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - .unwrap(); - assert_eq!(measurements, mock_dcap_measurements()); - - let res_body = res.text().await.unwrap(); - assert_eq!(res_body, "No measurements"); - } + // // Server has no attestation, client has mock DCAP and client auth + // #[tokio::test] + // async fn http_proxy_client_attestation() { + // let target_addr = example_http_service().await; + // + // let (server_cert_chain, server_private_key) = + // generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (client_cert_chain, client_private_key) = + // generate_certificate_chain("127.0.0.1".parse().unwrap()); + // + // let ( + // (_client_tls_server_config, client_tls_client_config), + // (server_tls_server_config, _server_tls_client_config), + // ) = generate_tls_config_with_client_auth( + // client_cert_chain.clone(), + // client_private_key, + // server_cert_chain.clone(), + // server_private_key, + // ); + // + // let proxy_server = ProxyServer::new_with_tls_config( + // server_cert_chain, + // server_tls_server_config, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::with_no_attestation(), + // AttestationVerifier::mock(), + // ) + // .await + // .unwrap(); + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // // Accept one connection, then finish + // proxy_server.accept().await.unwrap(); + // }); + // + // let proxy_client = ProxyClient::new_with_tls_config( + // client_tls_client_config, + // "127.0.0.1:0", + // proxy_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::expect_none(), + // Some(client_cert_chain), + // ) + // .await + // .unwrap(); + // + // let proxy_client_addr = proxy_client.local_addr().unwrap(); + // + // tokio::spawn(async move { + // // Accept two connections, then finish + // proxy_client.accept().await.unwrap(); + // proxy_client.accept().await.unwrap(); + // }); + // + // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + // .await + // .unwrap(); + // + // // We expect no measurements from the server + // let headers = res.headers(); + // assert!(headers.get(MEASUREMENT_HEADER).is_none()); + // + // let attestation_type = headers + // .get(ATTESTATION_TYPE_HEADER) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(attestation_type, AttestationType::None.as_str()); + // + // let res_body = res.text().await.unwrap(); + // + // // The response body shows us what was in the request header (as the test http server + // // handler puts them there) + // let measurements = + // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // } + // + // // Server has no attestation, client has mock DCAP but no client auth + // #[tokio::test] + // async fn http_proxy_client_attestation_no_client_auth() { + // let target_addr = example_http_service().await; + // + // let (server_cert_chain, server_private_key) = + // generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (server_config, client_config) = + // generate_tls_config(server_cert_chain.clone(), server_private_key); + // + // let proxy_server = ProxyServer::new_with_tls_config( + // server_cert_chain, + // server_config, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::with_no_attestation(), + // AttestationVerifier::mock(), + // ) + // .await + // .unwrap(); + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // // Accept one connection, then finish + // proxy_server.accept().await.unwrap(); + // }); + // + // let proxy_client = ProxyClient::new_with_tls_config( + // client_config, + // "127.0.0.1:0", + // proxy_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::expect_none(), + // None, + // ) + // .await + // .unwrap(); + // + // let proxy_client_addr = proxy_client.local_addr().unwrap(); + // + // tokio::spawn(async move { + // // Accept two connections, then finish + // proxy_client.accept().await.unwrap(); + // proxy_client.accept().await.unwrap(); + // }); + // + // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + // .await + // .unwrap(); + // + // // We expect no measurements from the server + // let headers = res.headers(); + // assert!(headers.get(MEASUREMENT_HEADER).is_none()); + // + // let attestation_type = headers + // .get(ATTESTATION_TYPE_HEADER) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(attestation_type, AttestationType::None.as_str()); + // + // let res_body = res.text().await.unwrap(); + // + // // The response body shows us what was in the request header (as the test http server + // // handler puts them there) + // let measurements = + // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // } + // + // // Server has mock DCAP, client has mock DCAP and client auth + // #[tokio::test] + // async fn http_proxy_mutual_attestation() { + // let target_addr = example_http_service().await; + // + // let (server_cert_chain, server_private_key) = + // generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (client_cert_chain, client_private_key) = + // generate_certificate_chain("127.0.0.1".parse().unwrap()); + // + // let ( + // (_client_tls_server_config, client_tls_client_config), + // (server_tls_server_config, _server_tls_client_config), + // ) = generate_tls_config_with_client_auth( + // client_cert_chain.clone(), + // client_private_key, + // server_cert_chain.clone(), + // server_private_key, + // ); + // + // let proxy_server = ProxyServer::new_with_tls_config( + // server_cert_chain, + // server_tls_server_config, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::mock(), + // ) + // .await + // .unwrap(); + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // // Accept one connection, then finish + // proxy_server.accept().await.unwrap(); + // }); + // + // let proxy_client = ProxyClient::new_with_tls_config( + // client_tls_client_config, + // "127.0.0.1:0", + // proxy_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::mock(), + // Some(client_cert_chain), + // ) + // .await + // .unwrap(); + // + // let proxy_client_addr = proxy_client.local_addr().unwrap(); + // + // tokio::spawn(async move { + // // Accept two connections, then finish + // proxy_client.accept().await.unwrap(); + // proxy_client.accept().await.unwrap(); + // }); + // + // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + // .await + // .unwrap(); + // + // let headers = res.headers(); + // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); + // let measurements = + // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) + // .unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // + // let attestation_type = headers + // .get(ATTESTATION_TYPE_HEADER) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); + // + // let res_body = res.text().await.unwrap(); + // + // // The response body shows us what was in the request header (as the test http server + // // handler puts them there) + // let measurements = + // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // + // // Now do another request - to check that the connection has stayed open + // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + // .await + // .unwrap(); + // + // let headers = res.headers(); + // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); + // let measurements = + // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) + // .unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // + // let attestation_type = headers + // .get(ATTESTATION_TYPE_HEADER) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); + // + // let res_body = res.text().await.unwrap(); + // + // // The response body shows us what was in the request header (as the test http server + // // handler puts them there) + // let measurements = + // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // } + // + // // Server has mock DCAP, client no attestation - just get the server certificate + // #[tokio::test] + // async fn test_get_tls_cert() { + // let target_addr = example_http_service().await; + // + // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + // + // let proxy_server = ProxyServer::new_with_tls_config( + // cert_chain.clone(), + // server_config, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::expect_none(), + // ) + // .await + // .unwrap(); + // + // let proxy_server_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_server.accept().await.unwrap(); + // }); + // + // let (retrieved_chain, _measurements) = get_tls_cert_with_config( + // &proxy_server_addr.to_string(), + // AttestationVerifier::mock(), + // client_config, + // ) + // .await + // .unwrap(); + // + // assert_eq!(retrieved_chain, cert_chain); + // } + // + // // Negative test - server does not provide attestation but client requires it + // // Server has no attestaion, client has no attestation and no client auth + // #[tokio::test] + // async fn fails_on_no_attestation_when_expected() { + // let target_addr = example_http_service().await; + // + // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + // + // let proxy_server = ProxyServer::new_with_tls_config( + // cert_chain, + // server_config, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::with_no_attestation(), + // AttestationVerifier::expect_none(), + // ) + // .await + // .unwrap(); + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_server.accept().await.unwrap(); + // }); + // + // let proxy_client_result = ProxyClient::new_with_tls_config( + // client_config, + // "127.0.0.1:0".to_string(), + // proxy_addr.to_string(), + // AttestationGenerator::with_no_attestation(), + // AttestationVerifier::mock(), + // None, + // ) + // .await; + // + // assert!(matches!( + // proxy_client_result.unwrap_err(), + // ProxyError::AttestedTls(AttestedTlsError::Attestation( + // AttestationError::AttestationTypeNotAccepted + // )) + // )); + // } + // + // // Negative test - server does not provide attestation but client requires it + // // Server has no attestaion, client has no attestation and no client auth + // #[tokio::test] + // async fn fails_on_bad_measurements() { + // let target_addr = example_http_service().await; + // + // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + // + // let proxy_server = ProxyServer::new_with_tls_config( + // cert_chain, + // server_config, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::expect_none(), + // ) + // .await + // .unwrap(); + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_server.accept().await.unwrap(); + // }); + // + // let measurement_policy = MeasurementPolicy::from_json_bytes( + // br#" + // [{ + // "measurement_id": "test", + // "attestation_type": "dcap-tdx", + // "measurements": { + // "0": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + // "1": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + // "2": { "expected": "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" }, + // "3": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + // "4": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } + // } + // }] + // "# + // .to_vec(), + // ) + // .unwrap(); + // + // let attestation_verifier = AttestationVerifier { + // measurement_policy, + // pccs_url: None, + // log_dcap_quote: false, + // override_azure_outdated_tcb: false, + // }; + // + // let proxy_client_result = ProxyClient::new_with_tls_config( + // client_config, + // "127.0.0.1:0".to_string(), + // proxy_addr.to_string(), + // AttestationGenerator::with_no_attestation(), + // attestation_verifier, + // None, + // ) + // .await; + // + // assert!(matches!( + // proxy_client_result.unwrap_err(), + // ProxyError::AttestedTls(AttestedTlsError::Attestation( + // AttestationError::MeasurementsNotAccepted + // )) + // )); + // } + // + // #[tokio::test] + // async fn http_proxy_client_reconnects_on_lost_connection() { + // init_tracing(); + // + // let target_addr = example_http_service().await; + // + // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + // + // let proxy_server = ProxyServer::new_with_tls_config( + // cert_chain, + // server_config, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::expect_none(), + // ) + // .await + // .unwrap(); + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // // This is used to trigger a dropped connection to the proxy server + // let (connection_breaker_tx, connection_breaker_rx) = oneshot::channel(); + // + // tokio::spawn(async move { + // let connection_handle = proxy_server.accept().await.unwrap(); + // + // // Wait for a signal to simulate a dropped connection, then drop the task handling the + // // connection + // connection_breaker_rx.await.unwrap(); + // connection_handle.abort(); + // + // // Now accept another connection + // proxy_server.accept().await.unwrap(); + // }); + // + // let proxy_client = ProxyClient::new_with_tls_config( + // client_config, + // "127.0.0.1:0".to_string(), + // proxy_addr.to_string(), + // AttestationGenerator::with_no_attestation(), + // AttestationVerifier::mock(), + // None, + // ) + // .await + // .unwrap(); + // + // let proxy_client_addr = proxy_client.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_client.accept().await.unwrap(); + // proxy_client.accept().await.unwrap(); + // }); + // + // let _initial_response = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + // .await + // .unwrap(); + // + // // Now break the connection + // connection_breaker_tx.send(()).unwrap(); + // + // // Make another request + // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + // .await + // .unwrap(); + // + // let headers = res.headers(); + // + // let attestation_type = headers + // .get(ATTESTATION_TYPE_HEADER) + // .unwrap() + // .to_str() + // .unwrap(); + // + // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); + // + // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); + // let measurements = + // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) + // .unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // + // let res_body = res.text().await.unwrap(); + // assert_eq!(res_body, "No measurements"); + // } + // + // // Use HTTP 1.1 + // #[tokio::test] + // async fn http_proxy_with_http1() { + // let target_addr = example_http_service().await; + // + // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + // let (mut server_config, client_config) = + // generate_tls_config(cert_chain.clone(), private_key); + // + // server_config.alpn_protocols.push(ALPN_HTTP11.to_vec()); + // + // let attested_tls_server = AttestedTlsServer::new_with_tls_config( + // cert_chain, + // server_config, + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::expect_none(), + // ) + // .unwrap(); + // + // let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + // + // let proxy_server = ProxyServer { + // attested_tls_server, + // listener: listener.into(), + // target: target_addr.to_string(), + // }; + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_server.accept().await.unwrap(); + // }); + // + // let proxy_client = ProxyClient::new_with_tls_config( + // client_config, + // "127.0.0.1:0".to_string(), + // proxy_addr.to_string(), + // AttestationGenerator::with_no_attestation(), + // AttestationVerifier::mock(), + // None, + // ) + // .await + // .unwrap(); + // + // let proxy_client_addr = proxy_client.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_client.accept().await.unwrap(); + // }); + // + // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + // .await + // .unwrap(); + // + // let headers = res.headers(); + // + // let attestation_type = headers + // .get(ATTESTATION_TYPE_HEADER) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); + // + // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); + // let measurements = + // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) + // .unwrap(); + // assert_eq!(measurements, mock_dcap_measurements()); + // + // let res_body = res.text().await.unwrap(); + // assert_eq!(res_body, "No measurements"); + // } } diff --git a/src/main.rs b/src/main.rs_ similarity index 100% rename from src/main.rs rename to src/main.rs_ diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 8990734..66ddc60 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -15,10 +15,7 @@ use tracing_subscriber::{EnvFilter, fmt}; static INIT: Once = Once::new(); -use crate::{ - MEASUREMENT_HEADER, - attestation::measurements::{DcapMeasurementRegister, MultiMeasurements}, -}; +use attestation::measurements::{DcapMeasurementRegister, MultiMeasurements}; /// Helper to generate a self-signed certificate for testing pub fn generate_certificate_chain( @@ -26,6 +23,9 @@ pub fn generate_certificate_chain( ) -> (Vec>, PrivateKeyDer<'static>) { let mut params = rcgen::CertificateParams::new(vec![]).unwrap(); params.subject_alt_names.push(rcgen::SanType::IpAddress(ip)); + params + .subject_alt_names + .push(rcgen::SanType::DnsName(ip.to_string().try_into().unwrap())); params .distinguished_name .push(rcgen::DnType::CommonName, ip.to_string()); @@ -38,6 +38,26 @@ pub fn generate_certificate_chain( (certs, key) } +/// Helper to generate a self-signed certificate for testing with a DNS subject name +pub fn generate_certificate_chain_for_host( + host: &str, +) -> (Vec>, PrivateKeyDer<'static>) { + let mut params = rcgen::CertificateParams::new(vec![host.to_string()]).unwrap(); + params + .subject_alt_names + .push(rcgen::SanType::DnsName(host.try_into().unwrap())); + params + .distinguished_name + .push(rcgen::DnType::CommonName, host); + + let keypair = rcgen::KeyPair::generate().unwrap(); + let cert = params.self_signed(&keypair).unwrap(); + + let certs = vec![CertificateDer::from(cert)]; + let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(keypair.serialize_der())); + (certs, key) +} + /// Helper to generate TLS configuration for testing /// /// For the server: A given self-signed certificate @@ -131,12 +151,13 @@ pub async fn example_http_service() -> SocketAddr { addr } -async fn get_handler(headers: http::HeaderMap) -> impl IntoResponse { - headers - .get(MEASUREMENT_HEADER) - .and_then(|v| v.to_str().ok()) - .unwrap_or("No measurements") - .to_string() +async fn get_handler(_headers: http::HeaderMap) -> impl IntoResponse { + // headers + // .get(MEASUREMENT_HEADER) + // .and_then(|v| v.to_str().ok()) + // .unwrap_or("No measurements") + // .to_string() + "No measurements".to_string() } /// All-zero measurment values used in some tests From feec7ca604a63bbb48055e4bb37b329392550cca Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 17 Mar 2026 11:54:27 +0100 Subject: [PATCH 03/44] Main compiles --- src/attested_get.rs | 40 +- src/file_server.rs | 12 +- src/lib.rs | 1235 ++++++++++++++++++------------------- src/{main.rs_ => main.rs} | 83 +-- src/self_signed.rs | 323 ---------- src/test_helpers.rs | 11 - 6 files changed, 663 insertions(+), 1041 deletions(-) rename src/{main.rs_ => main.rs} (89%) delete mode 100644 src/self_signed.rs diff --git a/src/attested_get.rs b/src/attested_get.rs index 27e7ad3..3b4575f 100644 --- a/src/attested_get.rs +++ b/src/attested_get.rs @@ -9,30 +9,16 @@ pub async fn attested_get( url_path: &str, attestation_verifier: AttestationVerifier, remote_certificate: Option>, - allow_self_signed: bool, ) -> Result { - let proxy_client = if allow_self_signed { - let client_config = crate::self_signed::client_tls_config_allow_self_signed()?; - ProxyClient::new_with_tls_config( - client_config, - "127.0.0.1:0".to_string(), - target_addr, - AttestationGenerator::with_no_attestation(), - attestation_verifier, - None, - ) - .await? - } else { - ProxyClient::new( - None, - "127.0.0.1:0".to_string(), - target_addr, - AttestationGenerator::with_no_attestation(), - attestation_verifier, - remote_certificate, - ) - .await? - }; + let proxy_client = ProxyClient::new( + None, + "127.0.0.1:0".to_string(), + target_addr, + AttestationGenerator::with_no_attestation(), + attestation_verifier, + remote_certificate, + ) + .await?; attested_get_with_client(proxy_client, url_path).await } @@ -72,11 +58,11 @@ mod tests { ProxyServer, attestation::AttestationType, file_server::static_file_server, - test_helpers::{generate_certificate_chain, generate_tls_config}, + test_helpers::{generate_certificate_chain_for_host, generate_tls_config}, }; use tempfile::tempdir; - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_attested_get() { // Create a temporary directory with a file to serve let dir = tempdir().unwrap(); @@ -87,7 +73,7 @@ mod tests { let target_addr = static_file_server(dir.path().to_path_buf()).await.unwrap(); // Create TLS configuration - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); // Setup a proxy server targetting the static file server @@ -113,7 +99,7 @@ mod tests { let proxy_client = ProxyClient::new_with_tls_config( client_config, "127.0.0.1:0".to_string(), - proxy_addr.to_string(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), None, diff --git a/src/file_server.rs b/src/file_server.rs index bce4804..424008e 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -55,7 +55,7 @@ mod tests { use crate::{ProxyClient, attestation::AttestationType}; use super::*; - use crate::test_helpers::{generate_certificate_chain, generate_tls_config}; + use crate::test_helpers::{generate_certificate_chain_for_host, generate_tls_config}; use tempfile::tempdir; /// Given a URL, fetch response body and content type header @@ -74,7 +74,7 @@ mod tests { (body.to_vec(), content_type) } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_static_file_server() { // Create a temporary directory with some files to serve let dir = tempdir().unwrap(); @@ -94,7 +94,7 @@ mod tests { let target_addr = static_file_server(dir.path().to_path_buf()).await.unwrap(); // Create TLS configuration - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); // Setup a proxy server targetting the static file server @@ -118,7 +118,7 @@ mod tests { let proxy_client = ProxyClient::new_with_tls_config( client_config, "127.0.0.1:0".to_string(), - proxy_addr.to_string(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), None, @@ -128,9 +128,11 @@ mod tests { let proxy_client_addr = proxy_client.local_addr().unwrap(); - // Proxy cient accepts a single connection + // Accept one client connection per request. tokio::spawn(async move { proxy_client.accept().await.unwrap(); + proxy_client.accept().await.unwrap(); + proxy_client.accept().await.unwrap(); }); let client = reqwest::Client::new(); diff --git a/src/lib.rs b/src/lib.rs index 1d48551..e711527 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,8 @@ //! An attested TLS protocol and HTTPS proxy -// pub mod attested_get; -// pub mod file_server; +pub mod attested_get; +pub mod file_server; pub mod health_check; pub mod normalize_pem; -// pub mod self_signed; pub use attestation; pub use attestation::AttestationGenerator; @@ -14,7 +13,7 @@ mod http_version; mod test_helpers; use attestation::{AttestationError, AttestationVerifier}; -use attested_tls::{AttestedCertificateResolver, AttestedCertificateVerifier}; +use attested_tls::{AttestedCertificateResolver, AttestedCertificateVerifier, AttestedTlsError}; use bytes::Bytes; use http::{HeaderMap, HeaderName, HeaderValue}; use http_body_util::{BodyExt, combinators::BoxBody}; @@ -24,7 +23,7 @@ use nested_tls::server::NestingTlsStream; use nested_tls::{client::NestingTlsConnector, server::NestingTlsAcceptor}; use std::{net::SocketAddr, num::TryFromIntError, sync::Arc, time::Duration}; use thiserror::Error; -use tokio::io; +use tokio::io::{self, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{mpsc, oneshot}; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; @@ -72,28 +71,65 @@ fn ensure_proxy_alpn_protocols(alpn_protocols: &mut Vec>) { } } -// /// Retrieve the attested remote TLS certificate. -// pub async fn get_tls_cert( -// server_name: String, -// attestation_verifier: AttestationVerifier, -// remote_certificate: Option>, -// allow_self_signed: bool, -// ) -> Result<(Vec>, Option), AttestedTlsError> { -// let (cert, measurements) = if allow_self_signed { -// let client_tls_config = self_signed::client_tls_config_allow_self_signed()?; -// attested_tls::get_tls_cert_with_config( -// &server_name, -// attestation_verifier, -// client_tls_config, -// ) -// .await? -// } else { -// attested_tls::get_tls_cert(server_name, attestation_verifier, remote_certificate).await? -// }; -// -// debug!("[get-tls-cert] Connected to proxy server with measurements: {measurements:?}"); -// Ok((cert, measurements)) -// } +/// Retrieve the inner attested remote TLS certificate. +pub async fn get_inner_tls_cert( + server_name: String, + attestation_verifier: AttestationVerifier, + remote_outer_certificate: Option>, +) -> Result>, ProxyError> { + let root_store = match remote_outer_certificate.as_ref() { + Some(remote_certificate) => { + let mut root_store = RootCertStore::empty(); + root_store.add(remote_certificate.clone())?; + root_store + } + None => RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()), + }; + + let outer_client_config = + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_root_certificates(root_store) + .with_no_client_auth(); + + get_inner_tls_cert_with_config(server_name, attestation_verifier, outer_client_config).await +} + +pub async fn get_inner_tls_cert_with_config( + server_name: String, + attestation_verifier: AttestationVerifier, + mut outer_client_config: ClientConfig, +) -> Result>, ProxyError> { + ensure_proxy_alpn_protocols(&mut outer_client_config.alpn_protocols); + let outbound_stream = tokio::net::TcpStream::connect(&server_name).await?; + + let domain = server_name_from_host(&server_name)?; + + let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; + let inner_client_config = + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .dangerous() + .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) + .with_no_client_auth(); + + let nested_tls_connector = + NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); + + let mut tls_stream = nested_tls_connector + .connect(domain, outbound_stream) + .await?; + debug!("[get-tls-cert] Connected to proxy server"); + + let (_io, server_connection) = tls_stream.get_ref(); + + let remote_cert_chain = server_connection + .peer_certificates() + .ok_or(ProxyError::NoCertificate)? + .to_owned(); + + tls_stream.shutdown().await?; + + Ok(remote_cert_chain) +} /// A TLS over TCP server which provides an attestation before forwarding traffic to a given target address pub struct ProxyServer { @@ -133,13 +169,14 @@ impl ProxyServer { )? }; - Self::new_with_tls_config( + Self::new_with_tls_config_and_client_auth( cert_and_key.cert_chain, outer_server_config, local, target, attestation_generator, attestation_verifier, + client_auth, ) .await } @@ -147,27 +184,51 @@ impl ProxyServer { /// Start with preconfigured TLS pub async fn new_with_tls_config( cert_chain: Vec>, - mut outer_server_config: ServerConfig, + outer_server_config: ServerConfig, local: impl ToSocketAddrs, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, ) -> Result { - ensure_proxy_alpn_protocols(&mut outer_server_config.alpn_protocols); - let server_name = hostname_from_cert(cert_chain.get(0).unwrap()).unwrap(); - let inner_cert_resolver = AttestedCertificateResolver::new( + Self::new_with_tls_config_and_client_auth( + cert_chain, + outer_server_config, + local, + target, attestation_generator, - None, - server_name.to_string(), // TODO get name from outer certificate - vec![], + attestation_verifier, + false, ) .await - .unwrap(); + } + + /// Start with preconfigured TLS and require client auth on both nested sessions + pub async fn new_with_tls_config_and_client_auth( + cert_chain: Vec>, + mut outer_server_config: ServerConfig, + local: impl ToSocketAddrs, + target: String, + attestation_generator: AttestationGenerator, + attestation_verifier: AttestationVerifier, + client_auth: bool, + ) -> Result { + ensure_proxy_alpn_protocols(&mut outer_server_config.alpn_protocols); + let server_name = certificate_identity_from_chain(&cert_chain)?; + let inner_cert_resolver = + build_attested_cert_resolver(attestation_generator, &cert_chain).await?; - let inner_server_config = + let inner_server_config = if client_auth { + let attested_cert_verifier = + AttestedCertificateVerifier::new(None, attestation_verifier)?; ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_no_client_auth() // TODO - .with_cert_resolver(Arc::new(inner_cert_resolver)); + .with_client_cert_verifier(Arc::new(attested_cert_verifier)) + .with_cert_resolver(Arc::new(inner_cert_resolver)) + } else { + let _ = server_name; + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_no_client_auth() + .with_cert_resolver(Arc::new(inner_cert_resolver)) + }; let nesting_tls_acceptor = NestingTlsAcceptor::new(Arc::new(outer_server_config), Arc::new(inner_server_config)); @@ -372,7 +433,7 @@ impl ProxyClient { server_name, attestation_generator, attestation_verifier, - remote_certificate.map(|certificate| vec![certificate]), + cert_and_key.map(|cert_and_key| cert_and_key.cert_chain), ) .await } @@ -387,15 +448,28 @@ impl ProxyClient { cert_chain: Option>>, ) -> Result { ensure_proxy_alpn_protocols(&mut outer_client_config.alpn_protocols); + let outer_has_client_auth = outer_client_config.client_auth_cert_resolver.has_certs(); + let inner_has_client_auth = cert_chain.is_some(); + + if outer_has_client_auth != inner_has_client_auth { + return Err(ProxyError::ClientAuthMisconfigured); + } - let attested_cert_verifier = - AttestedCertificateVerifier::new(None, attestation_verifier).unwrap(); + let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; - let inner_client_config = + let inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { + let inner_cert_resolver = + build_attested_cert_resolver(attestation_generator, cert_chain).await?; ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .dangerous() .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) - .with_no_client_auth(); + .with_client_cert_resolver(Arc::new(inner_cert_resolver)) + } else { + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .dangerous() + .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) + .with_no_client_auth() + }; let nesting_tls_connector = NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); @@ -590,7 +664,7 @@ impl ProxyClient { return Ok(output); } Err(e) => { - if matches!(e, ProxyError::Io(_)) || !should_bail { + if should_retry_setup_error(&e, should_bail) { warn!("Reconnect failed: {e}. Retrying in {:#?}...", delay); tokio::time::sleep(delay).await; @@ -656,6 +730,25 @@ impl ProxyClient { } } +fn should_retry_setup_error(error: &ProxyError, should_bail: bool) -> bool { + if !should_bail { + return true; + } + + match error { + ProxyError::Io(io_error) => matches!( + io_error.kind(), + std::io::ErrorKind::ConnectionRefused + | std::io::ErrorKind::ConnectionReset + | std::io::ErrorKind::ConnectionAborted + | std::io::ErrorKind::NotConnected + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::UnexpectedEof + ), + _ => false, + } +} + /// Update a request/response header if we are able to encode the header value /// /// This avoids bailing on bad header values - the headers are simply not updated @@ -694,14 +787,16 @@ pub enum ProxyError { BadDnsName(#[from] tokio_rustls::rustls::pki_types::InvalidDnsNameError), #[error("HTTP: {0}")] Hyper(#[from] hyper::Error), + #[error("Attested TLS: {0}")] + AttestedTls(#[from] AttestedTlsError), #[error("JSON: {0}")] Json(#[from] serde_json::Error), #[error("Could not forward response - sender was dropped")] OneShotRecv(#[from] oneshot::error::RecvError), #[error("Failed to send request, connection to proxy-server dropped")] MpscSend, - // #[error("Attested TLS: {0}")] - // AttestedTls(#[from] AttestedTlsError), + #[error("Client auth must be configured on both the inner and outer TLS sessions")] + ClientAuthMisconfigured, } impl From> for ProxyError { @@ -728,6 +823,23 @@ fn hostname_from_cert(cert: &CertificateDer<'static>) -> Result], +) -> Result { + hostname_from_cert(cert_chain.first().ok_or(ProxyError::NoCertificate)?) +} + +async fn build_attested_cert_resolver( + attestation_generator: AttestationGenerator, + cert_chain: &[CertificateDer<'static>], +) -> Result { + let certificate_name = certificate_identity_from_chain(cert_chain)?; + Ok( + AttestedCertificateResolver::new(attestation_generator, None, certificate_name, vec![]) + .await?, + ) +} + /// If no port was provided, default to 443 pub(crate) fn host_to_host_with_port(host: &str) -> String { if host.contains(':') { @@ -769,9 +881,8 @@ mod tests { use super::*; use test_helpers::{ - example_http_service, generate_certificate_chain, generate_certificate_chain_for_host, - generate_tls_config, - generate_tls_config_with_client_auth, init_tracing, mock_dcap_measurements, + example_http_service, generate_certificate_chain_for_host, generate_tls_config, + generate_tls_config_with_client_auth, init_tracing, }; #[test] @@ -893,575 +1004,457 @@ mod tests { assert_eq!(res_body, "No measurements"); } - // // Server has no attestation, client has mock DCAP and client auth - // #[tokio::test] - // async fn http_proxy_client_attestation() { - // let target_addr = example_http_service().await; - // - // let (server_cert_chain, server_private_key) = - // generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (client_cert_chain, client_private_key) = - // generate_certificate_chain("127.0.0.1".parse().unwrap()); - // - // let ( - // (_client_tls_server_config, client_tls_client_config), - // (server_tls_server_config, _server_tls_client_config), - // ) = generate_tls_config_with_client_auth( - // client_cert_chain.clone(), - // client_private_key, - // server_cert_chain.clone(), - // server_private_key, - // ); - // - // let proxy_server = ProxyServer::new_with_tls_config( - // server_cert_chain, - // server_tls_server_config, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::with_no_attestation(), - // AttestationVerifier::mock(), - // ) - // .await - // .unwrap(); - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // // Accept one connection, then finish - // proxy_server.accept().await.unwrap(); - // }); - // - // let proxy_client = ProxyClient::new_with_tls_config( - // client_tls_client_config, - // "127.0.0.1:0", - // proxy_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::expect_none(), - // Some(client_cert_chain), - // ) - // .await - // .unwrap(); - // - // let proxy_client_addr = proxy_client.local_addr().unwrap(); - // - // tokio::spawn(async move { - // // Accept two connections, then finish - // proxy_client.accept().await.unwrap(); - // proxy_client.accept().await.unwrap(); - // }); - // - // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - // .await - // .unwrap(); - // - // // We expect no measurements from the server - // let headers = res.headers(); - // assert!(headers.get(MEASUREMENT_HEADER).is_none()); - // - // let attestation_type = headers - // .get(ATTESTATION_TYPE_HEADER) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(attestation_type, AttestationType::None.as_str()); - // - // let res_body = res.text().await.unwrap(); - // - // // The response body shows us what was in the request header (as the test http server - // // handler puts them there) - // let measurements = - // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // } - // - // // Server has no attestation, client has mock DCAP but no client auth - // #[tokio::test] - // async fn http_proxy_client_attestation_no_client_auth() { - // let target_addr = example_http_service().await; - // - // let (server_cert_chain, server_private_key) = - // generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (server_config, client_config) = - // generate_tls_config(server_cert_chain.clone(), server_private_key); - // - // let proxy_server = ProxyServer::new_with_tls_config( - // server_cert_chain, - // server_config, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::with_no_attestation(), - // AttestationVerifier::mock(), - // ) - // .await - // .unwrap(); - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // // Accept one connection, then finish - // proxy_server.accept().await.unwrap(); - // }); - // - // let proxy_client = ProxyClient::new_with_tls_config( - // client_config, - // "127.0.0.1:0", - // proxy_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::expect_none(), - // None, - // ) - // .await - // .unwrap(); - // - // let proxy_client_addr = proxy_client.local_addr().unwrap(); - // - // tokio::spawn(async move { - // // Accept two connections, then finish - // proxy_client.accept().await.unwrap(); - // proxy_client.accept().await.unwrap(); - // }); - // - // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - // .await - // .unwrap(); - // - // // We expect no measurements from the server - // let headers = res.headers(); - // assert!(headers.get(MEASUREMENT_HEADER).is_none()); - // - // let attestation_type = headers - // .get(ATTESTATION_TYPE_HEADER) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(attestation_type, AttestationType::None.as_str()); - // - // let res_body = res.text().await.unwrap(); - // - // // The response body shows us what was in the request header (as the test http server - // // handler puts them there) - // let measurements = - // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // } - // - // // Server has mock DCAP, client has mock DCAP and client auth - // #[tokio::test] - // async fn http_proxy_mutual_attestation() { - // let target_addr = example_http_service().await; - // - // let (server_cert_chain, server_private_key) = - // generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (client_cert_chain, client_private_key) = - // generate_certificate_chain("127.0.0.1".parse().unwrap()); - // - // let ( - // (_client_tls_server_config, client_tls_client_config), - // (server_tls_server_config, _server_tls_client_config), - // ) = generate_tls_config_with_client_auth( - // client_cert_chain.clone(), - // client_private_key, - // server_cert_chain.clone(), - // server_private_key, - // ); - // - // let proxy_server = ProxyServer::new_with_tls_config( - // server_cert_chain, - // server_tls_server_config, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::mock(), - // ) - // .await - // .unwrap(); - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // // Accept one connection, then finish - // proxy_server.accept().await.unwrap(); - // }); - // - // let proxy_client = ProxyClient::new_with_tls_config( - // client_tls_client_config, - // "127.0.0.1:0", - // proxy_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::mock(), - // Some(client_cert_chain), - // ) - // .await - // .unwrap(); - // - // let proxy_client_addr = proxy_client.local_addr().unwrap(); - // - // tokio::spawn(async move { - // // Accept two connections, then finish - // proxy_client.accept().await.unwrap(); - // proxy_client.accept().await.unwrap(); - // }); - // - // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - // .await - // .unwrap(); - // - // let headers = res.headers(); - // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - // let measurements = - // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - // .unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // - // let attestation_type = headers - // .get(ATTESTATION_TYPE_HEADER) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - // - // let res_body = res.text().await.unwrap(); - // - // // The response body shows us what was in the request header (as the test http server - // // handler puts them there) - // let measurements = - // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // - // // Now do another request - to check that the connection has stayed open - // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - // .await - // .unwrap(); - // - // let headers = res.headers(); - // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - // let measurements = - // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - // .unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // - // let attestation_type = headers - // .get(ATTESTATION_TYPE_HEADER) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - // - // let res_body = res.text().await.unwrap(); - // - // // The response body shows us what was in the request header (as the test http server - // // handler puts them there) - // let measurements = - // MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // } - // - // // Server has mock DCAP, client no attestation - just get the server certificate - // #[tokio::test] - // async fn test_get_tls_cert() { - // let target_addr = example_http_service().await; - // - // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - // - // let proxy_server = ProxyServer::new_with_tls_config( - // cert_chain.clone(), - // server_config, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::expect_none(), - // ) - // .await - // .unwrap(); - // - // let proxy_server_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_server.accept().await.unwrap(); - // }); - // - // let (retrieved_chain, _measurements) = get_tls_cert_with_config( - // &proxy_server_addr.to_string(), - // AttestationVerifier::mock(), - // client_config, - // ) - // .await - // .unwrap(); - // - // assert_eq!(retrieved_chain, cert_chain); - // } - // - // // Negative test - server does not provide attestation but client requires it - // // Server has no attestaion, client has no attestation and no client auth - // #[tokio::test] - // async fn fails_on_no_attestation_when_expected() { - // let target_addr = example_http_service().await; - // - // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - // - // let proxy_server = ProxyServer::new_with_tls_config( - // cert_chain, - // server_config, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::with_no_attestation(), - // AttestationVerifier::expect_none(), - // ) - // .await - // .unwrap(); - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_server.accept().await.unwrap(); - // }); - // - // let proxy_client_result = ProxyClient::new_with_tls_config( - // client_config, - // "127.0.0.1:0".to_string(), - // proxy_addr.to_string(), - // AttestationGenerator::with_no_attestation(), - // AttestationVerifier::mock(), - // None, - // ) - // .await; - // - // assert!(matches!( - // proxy_client_result.unwrap_err(), - // ProxyError::AttestedTls(AttestedTlsError::Attestation( - // AttestationError::AttestationTypeNotAccepted - // )) - // )); - // } - // - // // Negative test - server does not provide attestation but client requires it - // // Server has no attestaion, client has no attestation and no client auth - // #[tokio::test] - // async fn fails_on_bad_measurements() { - // let target_addr = example_http_service().await; - // - // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - // - // let proxy_server = ProxyServer::new_with_tls_config( - // cert_chain, - // server_config, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::expect_none(), - // ) - // .await - // .unwrap(); - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_server.accept().await.unwrap(); - // }); - // - // let measurement_policy = MeasurementPolicy::from_json_bytes( - // br#" - // [{ - // "measurement_id": "test", - // "attestation_type": "dcap-tdx", - // "measurements": { - // "0": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - // "1": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - // "2": { "expected": "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" }, - // "3": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - // "4": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } - // } - // }] - // "# - // .to_vec(), - // ) - // .unwrap(); - // - // let attestation_verifier = AttestationVerifier { - // measurement_policy, - // pccs_url: None, - // log_dcap_quote: false, - // override_azure_outdated_tcb: false, - // }; - // - // let proxy_client_result = ProxyClient::new_with_tls_config( - // client_config, - // "127.0.0.1:0".to_string(), - // proxy_addr.to_string(), - // AttestationGenerator::with_no_attestation(), - // attestation_verifier, - // None, - // ) - // .await; - // - // assert!(matches!( - // proxy_client_result.unwrap_err(), - // ProxyError::AttestedTls(AttestedTlsError::Attestation( - // AttestationError::MeasurementsNotAccepted - // )) - // )); - // } - // - // #[tokio::test] - // async fn http_proxy_client_reconnects_on_lost_connection() { - // init_tracing(); - // - // let target_addr = example_http_service().await; - // - // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - // - // let proxy_server = ProxyServer::new_with_tls_config( - // cert_chain, - // server_config, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::expect_none(), - // ) - // .await - // .unwrap(); - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // // This is used to trigger a dropped connection to the proxy server - // let (connection_breaker_tx, connection_breaker_rx) = oneshot::channel(); - // - // tokio::spawn(async move { - // let connection_handle = proxy_server.accept().await.unwrap(); - // - // // Wait for a signal to simulate a dropped connection, then drop the task handling the - // // connection - // connection_breaker_rx.await.unwrap(); - // connection_handle.abort(); - // - // // Now accept another connection - // proxy_server.accept().await.unwrap(); - // }); - // - // let proxy_client = ProxyClient::new_with_tls_config( - // client_config, - // "127.0.0.1:0".to_string(), - // proxy_addr.to_string(), - // AttestationGenerator::with_no_attestation(), - // AttestationVerifier::mock(), - // None, - // ) - // .await - // .unwrap(); - // - // let proxy_client_addr = proxy_client.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_client.accept().await.unwrap(); - // proxy_client.accept().await.unwrap(); - // }); - // - // let _initial_response = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - // .await - // .unwrap(); - // - // // Now break the connection - // connection_breaker_tx.send(()).unwrap(); - // - // // Make another request - // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - // .await - // .unwrap(); - // - // let headers = res.headers(); - // - // let attestation_type = headers - // .get(ATTESTATION_TYPE_HEADER) - // .unwrap() - // .to_str() - // .unwrap(); - // - // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - // - // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - // let measurements = - // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - // .unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // - // let res_body = res.text().await.unwrap(); - // assert_eq!(res_body, "No measurements"); - // } - // - // // Use HTTP 1.1 - // #[tokio::test] - // async fn http_proxy_with_http1() { - // let target_addr = example_http_service().await; - // - // let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - // let (mut server_config, client_config) = - // generate_tls_config(cert_chain.clone(), private_key); - // - // server_config.alpn_protocols.push(ALPN_HTTP11.to_vec()); - // - // let attested_tls_server = AttestedTlsServer::new_with_tls_config( - // cert_chain, - // server_config, - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::expect_none(), - // ) - // .unwrap(); - // - // let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - // - // let proxy_server = ProxyServer { - // attested_tls_server, - // listener: listener.into(), - // target: target_addr.to_string(), - // }; - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_server.accept().await.unwrap(); - // }); - // - // let proxy_client = ProxyClient::new_with_tls_config( - // client_config, - // "127.0.0.1:0".to_string(), - // proxy_addr.to_string(), - // AttestationGenerator::with_no_attestation(), - // AttestationVerifier::mock(), - // None, - // ) - // .await - // .unwrap(); - // - // let proxy_client_addr = proxy_client.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_client.accept().await.unwrap(); - // }); - // - // let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) - // .await - // .unwrap(); - // - // let headers = res.headers(); - // - // let attestation_type = headers - // .get(ATTESTATION_TYPE_HEADER) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); - // - // let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); - // let measurements = - // MultiMeasurements::from_header_format(measurements_json, AttestationType::DcapTdx) - // .unwrap(); - // assert_eq!(measurements, mock_dcap_measurements()); - // - // let res_body = res.text().await.unwrap(); - // assert_eq!(res_body, "No measurements"); - // } + // Server has no attestation, client has mock DCAP and client auth + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_client_attestation() { + let target_addr = example_http_service().await; + + let (server_cert_chain, server_private_key) = + generate_certificate_chain_for_host("localhost"); + let (client_cert_chain, client_private_key) = + generate_certificate_chain_for_host("localhost"); + + let ( + (_client_tls_server_config, client_tls_client_config), + (server_tls_server_config, _server_tls_client_config), + ) = generate_tls_config_with_client_auth( + client_cert_chain.clone(), + client_private_key, + server_cert_chain.clone(), + server_private_key, + ); + + let proxy_server = ProxyServer::new_with_tls_config_and_client_auth( + server_cert_chain, + server_tls_server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + true, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_tls_client_config, + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + Some(client_cert_chain), + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await + .unwrap(); + + let res_body = res.text().await.unwrap(); + assert_eq!(res_body, "No measurements"); + } + + // Server has no attestation, client has mock DCAP but no client auth + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_client_attestation_no_client_auth() { + let target_addr = example_http_service().await; + + let (server_cert_chain, server_private_key) = + generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = + generate_tls_config(server_cert_chain.clone(), server_private_key); + + let proxy_server = ProxyServer::new_with_tls_config( + server_cert_chain, + server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + // Accept one connection, then finish + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + // Accept two connections, then finish + proxy_client.accept().await.unwrap(); + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + .await + .unwrap(); + + let _res_body = res.text().await.unwrap(); + } + + // Server has mock DCAP, client has mock DCAP and client auth + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_mutual_attestation() { + let target_addr = example_http_service().await; + + let (server_cert_chain, server_private_key) = + generate_certificate_chain_for_host("localhost"); + let (client_cert_chain, client_private_key) = + generate_certificate_chain_for_host("localhost"); + + let ( + (_client_tls_server_config, client_tls_client_config), + (server_tls_server_config, _server_tls_client_config), + ) = generate_tls_config_with_client_auth( + client_cert_chain.clone(), + client_private_key, + server_cert_chain.clone(), + server_private_key, + ); + + let proxy_server = ProxyServer::new_with_tls_config_and_client_auth( + server_cert_chain, + server_tls_server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::mock(), + true, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_tls_client_config, + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::mock(), + Some(client_cert_chain), + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await + .unwrap(); + assert_eq!(res.text().await.unwrap(), "No measurements"); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await + .unwrap(); + assert_eq!(res.text().await.unwrap(), "No measurements"); + } + + // Server has mock DCAP, client no attestation - just get the server certificate + #[tokio::test(flavor = "multi_thread")] + async fn test_get_tls_cert() { + let target_addr = example_http_service().await; + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + + let proxy_server = ProxyServer::new_with_tls_config( + cert_chain.clone(), + server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + ) + .await + .unwrap(); + + let proxy_server_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let retrieved_chain = get_inner_tls_cert_with_config( + format!("localhost:{}", proxy_server_addr.port()), + AttestationVerifier::mock(), + client_config, + ) + .await + .unwrap(); + + assert_eq!(retrieved_chain.len(), 1); + assert_eq!( + hostname_from_cert(&retrieved_chain[0]).unwrap(), + "localhost" + ); + assert_ne!(retrieved_chain, cert_chain); + } + + // Negative test - server does not provide attestation but client requires it + // Server has no attestaion, client has no attestation and no client auth + #[tokio::test(flavor = "multi_thread")] + async fn fails_on_no_attestation_when_expected() { + let target_addr = example_http_service().await; + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + + let proxy_server = ProxyServer::new_with_tls_config( + cert_chain, + server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client_result = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0".to_string(), + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + None, + ) + .await; + + let err = proxy_client_result.unwrap_err().to_string(); + assert!(err.contains("ApplicationVerificationFailure"), "{err}"); + } + + // Negative test - server does not provide attestation but client requires it + // Server has no attestaion, client has no attestation and no client auth + #[tokio::test(flavor = "multi_thread")] + async fn fails_on_bad_measurements() { + let target_addr = example_http_service().await; + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + + let proxy_server = ProxyServer::new_with_tls_config( + cert_chain, + server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let measurement_policy = MeasurementPolicy::from_json_bytes( + br#" + [{ + "measurement_id": "test", + "attestation_type": "dcap-tdx", + "measurements": { + "0": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + "1": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + "2": { "expected": "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" }, + "3": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + "4": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } + } + }] + "# + .to_vec(), + ) + .unwrap(); + + let attestation_verifier = AttestationVerifier { + measurement_policy, + pccs_url: None, + log_dcap_quote: false, + override_azure_outdated_tcb: false, + }; + + let proxy_client_result = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0".to_string(), + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + attestation_verifier, + None, + ) + .await; + + let err = proxy_client_result.unwrap_err().to_string(); + assert!(err.contains("ApplicationVerificationFailure"), "{err}"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_client_reconnects_on_lost_connection() { + init_tracing(); + + let target_addr = example_http_service().await; + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + + let proxy_server = ProxyServer::new_with_tls_config( + cert_chain, + server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + // This is used to trigger a dropped connection to the proxy server + let (connection_breaker_tx, connection_breaker_rx) = oneshot::channel(); + + tokio::spawn(async move { + let connection_handle = proxy_server.accept().await.unwrap(); + + // Wait for a signal to simulate a dropped connection, then drop the task handling the + // connection + connection_breaker_rx.await.unwrap(); + connection_handle.abort(); + + // Now accept another connection + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0".to_string(), + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + proxy_client.accept().await.unwrap(); + }); + + let _initial_response = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + .await + .unwrap(); + + // Now break the connection + connection_breaker_tx.send(()).unwrap(); + + // Make another request + let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + .await + .unwrap(); + + let res_body = res.text().await.unwrap(); + assert_eq!(res_body, "No measurements"); + } + + // Use HTTP 1.1 + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_with_http1() { + let target_addr = example_http_service().await; + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (mut server_config, client_config) = + generate_tls_config(cert_chain.clone(), private_key); + + server_config.alpn_protocols.push(ALPN_HTTP11.to_vec()); + + let proxy_server = ProxyServer::new_with_tls_config( + cert_chain, + server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0".to_string(), + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + .await + .unwrap(); + + let res_body = res.text().await.unwrap(); + assert_eq!(res_body, "No measurements"); + } } diff --git a/src/main.rs_ b/src/main.rs similarity index 89% rename from src/main.rs_ rename to src/main.rs index d929778..4704059 100644 --- a/src/main.rs_ +++ b/src/main.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, ensure}; -use attested_tls::attestation::measurements::MultiMeasurements; +use attestation::{AttestationType, AttestationVerifier, measurements::MeasurementPolicy}; use clap::{Parser, Subcommand}; use std::{ fs::File, @@ -11,14 +11,8 @@ use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; use tracing::level_filters::LevelFilter; use attested_tls_proxy::{ - AttestationGenerator, ProxyClient, ProxyServer, - attested_get::attested_get, - attested_tls::{ - TlsCertAndKey, - attestation::{AttestationType, AttestationVerifier, measurements::MeasurementPolicy}, - }, - file_server::attested_file_server, - get_tls_cert, health_check, + AttestationGenerator, ProxyClient, ProxyServer, TlsCertAndKey, attested_get::attested_get, + file_server::attested_file_server, get_inner_tls_cert, health_check, normalize_pem::normalize_private_key_pem_to_pkcs8, }; @@ -280,29 +274,15 @@ async fn main() -> anyhow::Result<()> { AttestationGenerator::new_with_detection(client_attestation_type, dev_dummy_dcap) .await?; - let client = if allow_self_signed { - let client_tls_config = - attested_tls_proxy::self_signed::client_tls_config_allow_self_signed()?; - ProxyClient::new_with_tls_config( - client_tls_config, - listen_addr, - target_addr, - client_attestation_generator, - attestation_verifier, - None, - ) - .await? - } else { - ProxyClient::new( - tls_cert_and_chain, - listen_addr, - target_addr, - client_attestation_generator, - attestation_verifier, - remote_tls_cert, - ) - .await? - }; + let client = ProxyClient::new( + tls_cert_and_chain, + listen_addr, + target_addr, + client_attestation_generator, + attestation_verifier, + remote_tls_cert, + ) + .await?; loop { if let Err(err) = client.accept().await { @@ -365,24 +345,19 @@ async fn main() -> anyhow::Result<()> { ), None => None, }; - let (cert_chain, measurements) = get_tls_cert( - server, - attestation_verifier, - remote_tls_cert, - allow_self_signed, - ) - .await?; - - // If the user chose to write measurements to a file as JSON - if let Some(path_to_write_measurements) = out_measurements { - std::fs::write( - path_to_write_measurements, - measurements - .unwrap_or(MultiMeasurements::NoAttestation) - .to_header_format()? - .as_bytes(), - )?; - } + let cert_chain = + get_inner_tls_cert(server, attestation_verifier, remote_tls_cert).await?; + + // // If the user chose to write measurements to a file as JSON + // if let Some(path_to_write_measurements) = out_measurements { + // std::fs::write( + // path_to_write_measurements, + // measurements + // .unwrap_or(MultiMeasurements::NoAttestation) + // .to_header_format()? + // .as_bytes(), + // )?; + // } println!("{}", certs_to_pem_string(&cert_chain)?); } CliCommand::AttestedFileServer { @@ -434,7 +409,6 @@ async fn main() -> anyhow::Result<()> { &url_path.unwrap_or_default(), attestation_verifier, remote_tls_cert, - allow_self_signed, ) .await?; @@ -467,9 +441,10 @@ fn load_tls_cert_and_key_server( return Err(anyhow!("Certificate chain provided but no private key")); } tracing::warn!("No TLS ceritifcate provided - generating self-signed"); - Ok(attested_tls_proxy::self_signed::generate_self_signed_cert( - ip, - )?) + todo!() + // Ok(attested_tls_proxy::self_signed::generate_self_signed_cert( + // ip, + // )?) } } diff --git a/src/self_signed.rs b/src/self_signed.rs deleted file mode 100644 index e41e826..0000000 --- a/src/self_signed.rs +++ /dev/null @@ -1,323 +0,0 @@ -use std::{net::IpAddr, sync::Arc}; -use tokio_rustls::rustls::{ - self, - crypto::CryptoProvider, - pki_types::{self, CertificateDer, PrivatePkcs8KeyDer}, -}; -use x509_parser::prelude::{FromDer, X509Certificate}; - -use crate::attested_tls::{AttestedTlsError, TlsCertAndKey}; - -/// Generate a self signed certifcate -pub fn generate_self_signed_cert(ip_address: IpAddr) -> Result { - let keypair = rcgen::KeyPair::generate()?; - let mut params = rcgen::CertificateParams::default(); - params - .subject_alt_names - .push(rcgen::SanType::IpAddress(ip_address)); - - let cert = params.self_signed(&keypair)?; - Ok(TlsCertAndKey { - cert_chain: vec![cert.der().clone()], - key: PrivatePkcs8KeyDer::from(keypair.serialize_der()).into(), - }) -} - -/// Client TLS configuration which accepts self-signed remote certificates -pub fn client_tls_config_allow_self_signed() -> Result { - Ok(rustls::ClientConfig::builder() - .dangerous() - .with_custom_certificate_verifier(SkipServerVerification::new()?) - .with_no_client_auth()) -} - -/// Used to allow verification of self-signed certificates -#[derive(Debug, Clone)] -pub struct SkipServerVerification { - supported_algs: rustls::crypto::WebPkiSupportedAlgorithms, -} - -impl SkipServerVerification { - pub fn new() -> Result, AttestedTlsError> { - Ok(Arc::new(Self { - supported_algs: Arc::new( - CryptoProvider::get_default().ok_or(AttestedTlsError::NoCryptoProvider)?, - ) - .clone() - .signature_verification_algorithms, - })) - } -} - -impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &pki_types::ServerName<'_>, - _ocsp_response: &[u8], - _now: pki_types::UnixTime, - ) -> Result { - // Parse the certificate - let (_, cert) = X509Certificate::from_der(end_entity).map_err(|_| { - rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding) - })?; - - // Verify signature - cert.verify_signature(None).map_err(|_| { - rustls::Error::InvalidCertificate(rustls::CertificateError::BadSignature) - })?; - - Ok(rustls::client::danger::ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { - let provider = rustls::crypto::CryptoProvider::get_default() - .ok_or_else(|| rustls::Error::General("No crypto provider installed".into()))?; - - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &provider.signature_verification_algorithms, - )?; - - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { - let provider = rustls::crypto::CryptoProvider::get_default() - .ok_or_else(|| rustls::Error::General("No crypto provider installed".into()))?; - - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &provider.signature_verification_algorithms, - )?; - - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) - } - - fn supported_verify_schemes(&self) -> Vec { - self.supported_algs.supported_schemes() - } -} - -/// Used to allow verification of self-signed certificates during client authentication -#[derive(Debug)] -pub struct SkipClientVerification { - supported_algs: rustls::crypto::WebPkiSupportedAlgorithms, -} - -impl SkipClientVerification { - pub fn new() -> std::sync::Arc { - std::sync::Arc::new(Self { - supported_algs: Arc::new(CryptoProvider::get_default().unwrap()) - .clone() - .signature_verification_algorithms, - }) - } -} - -impl rustls::server::danger::ClientCertVerifier for SkipClientVerification { - fn verify_client_cert( - &self, - end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer], - _now: rustls::pki_types::UnixTime, - ) -> Result { - // Parse the certificate - let (_, cert) = X509Certificate::from_der(end_entity).map_err(|_| { - rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding) - })?; - - // Verify signature - cert.verify_signature(None).map_err(|_| { - rustls::Error::InvalidCertificate(rustls::CertificateError::BadSignature) - })?; - Ok(rustls::server::danger::ClientCertVerified::assertion()) - } - - fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] { - &[] - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { - let provider = rustls::crypto::CryptoProvider::get_default() - .ok_or_else(|| rustls::Error::General("No crypto provider installed".into()))?; - - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &provider.signature_verification_algorithms, - )?; - - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { - let provider = rustls::crypto::CryptoProvider::get_default() - .ok_or_else(|| rustls::Error::General("No crypto provider installed".into()))?; - - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &provider.signature_verification_algorithms, - )?; - - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) - } - - fn supported_verify_schemes(&self) -> Vec { - self.supported_algs.supported_schemes() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - AttestationGenerator, - attestation::{AttestationType, AttestationVerifier}, - attested_tls::{AttestedTlsClient, AttestedTlsServer}, - test_helpers::{generate_certificate_chain, generate_tls_config}, - }; - use tokio::net::TcpListener; - use tokio_rustls::rustls::pki_types::ServerName; - - #[tokio::test] - async fn self_signed_server_attestation() { - let cert_and_key = generate_self_signed_cert("127.0.0.1".parse().unwrap()).unwrap(); - - let server_config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert( - cert_and_key.cert_chain.clone().to_vec(), - cert_and_key.key.clone_key(), - ) - .unwrap(); - - let server = AttestedTlsServer::new_with_tls_config( - cert_and_key.cert_chain, - server_config.into(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - - tokio::spawn(async move { - let (tcp_stream, _) = listener.accept().await.unwrap(); - let (_stream, _measurements, _attestation_type) = - server.handle_connection(tcp_stream).await.unwrap(); - }); - - let client_config = client_tls_config_allow_self_signed().unwrap(); - - let client = AttestedTlsClient::new_with_tls_config( - client_config.into(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .unwrap(); - - let (_stream, _measurements, _attestation_type) = - client.connect_tcp(&server_addr.to_string()).await.unwrap(); - } - - #[tokio::test] - async fn nested_tls_with_self_signed_server_attestation() { - // Outer TLS setup - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (outer_server_config, outer_client_config) = - generate_tls_config(cert_chain.clone(), private_key); - - let outer_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(outer_server_config)); - let outer_connector = tokio_rustls::TlsConnector::from(Arc::new(outer_client_config)); - - // Inner TLS setup - let cert_and_key = generate_self_signed_cert("127.0.0.1".parse().unwrap()).unwrap(); - - let server_config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert( - cert_and_key.cert_chain.clone().to_vec(), - cert_and_key.key.clone_key(), - ) - .unwrap(); - - let server = AttestedTlsServer::new_with_tls_config( - cert_and_key.cert_chain, - server_config.into(), - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - - tokio::spawn(async move { - let (tcp_stream, _) = listener.accept().await.unwrap(); - - // Do outer TLS handshake - let tls_stream = outer_acceptor.accept(tcp_stream).await.unwrap(); - - // Do inner (attested) TLS - let (_stream, _measurements, _attestation_type) = - server.handle_connection(tls_stream).await.unwrap(); - }); - - // Inner TLS config - let client_config = client_tls_config_allow_self_signed().unwrap(); - - let client = AttestedTlsClient::new_with_tls_config( - client_config.into(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .unwrap(); - - let client_tcp_stream = tokio::net::TcpStream::connect(&server_addr).await.unwrap(); - - // Outer TLS handshake - let server_name = ServerName::try_from(server_addr.ip().to_string()).unwrap(); - let tls_stream = outer_connector - .connect(server_name, client_tcp_stream) - .await - .unwrap(); - - // Inner (attested) TLS - let (_stream, _measurements, _attestation_type) = client - .connect(&server_addr.to_string(), tls_stream) - .await - .unwrap(); - } -} diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 66ddc60..939deb4 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -160,17 +160,6 @@ async fn get_handler(_headers: http::HeaderMap) -> impl IntoResponse { "No measurements".to_string() } -/// All-zero measurment values used in some tests -pub fn mock_dcap_measurements() -> MultiMeasurements { - MultiMeasurements::Dcap(HashMap::from([ - (DcapMeasurementRegister::MRTD, [0u8; 48]), - (DcapMeasurementRegister::RTMR0, [0u8; 48]), - (DcapMeasurementRegister::RTMR1, [0u8; 48]), - (DcapMeasurementRegister::RTMR2, [0u8; 48]), - (DcapMeasurementRegister::RTMR3, [0u8; 48]), - ])) -} - pub fn init_tracing() { INIT.call_once(|| { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); From 91e35e479fc2e899af9fa3a735bf905d0d8cdb73 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 17 Mar 2026 12:13:42 +0100 Subject: [PATCH 04/44] Rm no longer needed allow self signed option --- src/main.rs | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4704059..1c56f20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,7 @@ use anyhow::{anyhow, ensure}; use attestation::{AttestationType, AttestationVerifier, measurements::MeasurementPolicy}; use clap::{Parser, Subcommand}; -use std::{ - fs::File, - net::{IpAddr, SocketAddr}, - path::PathBuf, -}; +use std::{fs::File, net::SocketAddr, path::PathBuf}; use tokio::io::AsyncWriteExt; use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; use tracing::level_filters::LevelFilter; @@ -78,9 +74,6 @@ enum CliCommand { // Address to listen on for health checks #[arg(long)] listen_addr_healthcheck: Option, - /// Enables verification of self-signed TLS certificates - #[arg(long)] - allow_self_signed: bool, }, /// Run a proxy server Server { @@ -118,9 +111,6 @@ enum CliCommand { /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. #[arg(long)] tls_ca_certificate: Option, - /// Enables verification of self-signed TLS certificates - #[arg(long)] - allow_self_signed: bool, /// Filename to write measurements as JSON to #[arg(long)] out_measurements: Option, @@ -158,9 +148,6 @@ enum CliCommand { /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. #[arg(long)] tls_ca_certificate: Option, - /// Enables verification of self-signed TLS certificates - #[arg(long)] - allow_self_signed: bool, }, } @@ -235,7 +222,6 @@ async fn main() -> anyhow::Result<()> { tls_ca_certificate, dev_dummy_dcap, listen_addr_healthcheck, - allow_self_signed, } => { let target_addr = target_addr .strip_prefix("https://") @@ -304,11 +290,8 @@ async fn main() -> anyhow::Result<()> { health_check::server(listen_addr_healthcheck).await?; } - let tls_cert_and_chain = load_tls_cert_and_key_server( - tls_certificate_path, - tls_private_key_path, - listen_addr.ip(), - )?; + let tls_cert_and_chain = + load_tls_cert_and_key_server(tls_certificate_path, tls_private_key_path)?; let local_attestation_generator = AttestationGenerator::new_with_detection(server_attestation_type, dev_dummy_dcap) @@ -333,7 +316,6 @@ async fn main() -> anyhow::Result<()> { CliCommand::GetTlsCert { server, tls_ca_certificate, - allow_self_signed, out_measurements, } => { let remote_tls_cert = match tls_ca_certificate { @@ -392,7 +374,6 @@ async fn main() -> anyhow::Result<()> { target_addr, url_path, tls_ca_certificate, - allow_self_signed, } => { let remote_tls_cert = match tls_ca_certificate { Some(remote_cert_filename) => Some( @@ -429,7 +410,6 @@ async fn main() -> anyhow::Result<()> { fn load_tls_cert_and_key_server( cert_chain: Option, private_key: Option, - ip: IpAddr, ) -> anyhow::Result { if let Some(private_key) = private_key { load_tls_cert_and_key( @@ -438,13 +418,10 @@ fn load_tls_cert_and_key_server( ) } else { if cert_chain.is_some() { - return Err(anyhow!("Certificate chain provided but no private key")); + Err(anyhow!("Certificate chain provided but no private key")) + } else { + Err(anyhow!("No private key provided")) } - tracing::warn!("No TLS ceritifcate provided - generating self-signed"); - todo!() - // Ok(attested_tls_proxy::self_signed::generate_self_signed_cert( - // ip, - // )?) } } From ad6ff187238a4e76375242a1fa9405f1fc05076c Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 17 Mar 2026 12:15:10 +0100 Subject: [PATCH 05/44] Bump dcap-qvl --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e01c501..9ed81b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1676,7 +1676,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1900,7 +1900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3045,7 +3045,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3684,7 +3684,7 @@ dependencies = [ "once_cell", "socket2 0.6.1", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4124,7 +4124,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4743,7 +4743,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] From 3e6140ed52becf1744a50a6307a87464786f86bf Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 17 Mar 2026 12:26:14 +0100 Subject: [PATCH 06/44] Fixes attested-tls crate branch --- Cargo.lock | 38 +++----------------------------------- attested-tls/Cargo.toml | 4 ++-- src/main.rs | 10 ++++------ src/test_helpers.rs | 26 +------------------------- 4 files changed, 10 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ed81b1..a169090 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,38 +558,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "attestation" -version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fadd-attestation-crate#4ebc03703510e65fd1317736b8887fc388860481" -dependencies = [ - "anyhow", - "az-tdx-vtpm", - "base64 0.22.1", - "configfs-tsm", - "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override)", - "hex", - "http", - "num-bigint", - "once_cell", - "openssl", - "parity-scale-codec", - "pem-rfc7468", - "rand_core 0.6.4", - "reqwest", - "rustls-webpki", - "serde", - "serde_json", - "tdx-quote", - "thiserror 2.0.17", - "time", - "tokio", - "tokio-rustls", - "tracing", - "tss-esapi", - "x509-parser 0.18.1", -] - [[package]] name = "attestation" version = "0.0.1" @@ -644,7 +612,7 @@ version = "0.0.1" dependencies = [ "alloy-rpc-client", "alloy-transport-http", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fadd-attestation-crate)", + "attestation", "bytes", "futures-util", "http", @@ -673,7 +641,7 @@ version = "0.0.1" source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" dependencies = [ "anyhow", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", + "attestation", "ra-tls", "rcgen 0.14.7", "rustls", @@ -691,7 +659,7 @@ name = "attested-tls-proxy" version = "1.1.1" dependencies = [ "anyhow", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", + "attestation", "attested-tls 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", "axum", "bytes", diff --git a/attested-tls/Cargo.toml b/attested-tls/Cargo.toml index 38a810e..d40e7b6 100644 --- a/attested-tls/Cargo.toml +++ b/attested-tls/Cargo.toml @@ -18,7 +18,7 @@ http = "1.3.1" serde_json = "1.0.145" tracing = "0.1.41" parity-scale-codec = "3.7.5" -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/add-attestation-crate" } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } # Used for websocket support tokio-tungstenite = { version = "0.28.0", optional = true } @@ -40,7 +40,7 @@ rcgen = { version = "0.14.5", optional = true } [dev-dependencies] rcgen = "0.14.5" tempfile = "3.23.0" -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/add-attestation-crate", features = ["mock"] } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate", features = ["mock"] } [features] default = ["ws", "rpc"] diff --git a/src/main.rs b/src/main.rs index 1c56f20..c14c414 100644 --- a/src/main.rs +++ b/src/main.rs @@ -316,7 +316,7 @@ async fn main() -> anyhow::Result<()> { CliCommand::GetTlsCert { server, tls_ca_certificate, - out_measurements, + out_measurements: _, // TODO } => { let remote_tls_cert = match tls_ca_certificate { Some(remote_cert_filename) => Some( @@ -416,12 +416,10 @@ fn load_tls_cert_and_key_server( cert_chain.ok_or(anyhow!("Private key given but no certificate chain"))?, private_key, ) + } else if cert_chain.is_some() { + Err(anyhow!("Certificate chain provided but no private key")) } else { - if cert_chain.is_some() { - Err(anyhow!("Certificate chain provided but no private key")) - } else { - Err(anyhow!("No private key provided")) - } + Err(anyhow!("No private key provided")) } } diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 939deb4..431c5f8 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -1,8 +1,7 @@ //! Helper functions used in tests use axum::response::IntoResponse; use std::{ - collections::HashMap, - net::{IpAddr, SocketAddr}, + net::SocketAddr, sync::{Arc, Once}, }; use tokio::net::TcpListener; @@ -15,29 +14,6 @@ use tracing_subscriber::{EnvFilter, fmt}; static INIT: Once = Once::new(); -use attestation::measurements::{DcapMeasurementRegister, MultiMeasurements}; - -/// Helper to generate a self-signed certificate for testing -pub fn generate_certificate_chain( - ip: IpAddr, -) -> (Vec>, PrivateKeyDer<'static>) { - let mut params = rcgen::CertificateParams::new(vec![]).unwrap(); - params.subject_alt_names.push(rcgen::SanType::IpAddress(ip)); - params - .subject_alt_names - .push(rcgen::SanType::DnsName(ip.to_string().try_into().unwrap())); - params - .distinguished_name - .push(rcgen::DnType::CommonName, ip.to_string()); - - let keypair = rcgen::KeyPair::generate().unwrap(); - let cert = params.self_signed(&keypair).unwrap(); - - let certs = vec![CertificateDer::from(cert)]; - let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(keypair.serialize_der())); - (certs, key) -} - /// Helper to generate a self-signed certificate for testing with a DNS subject name pub fn generate_certificate_chain_for_host( host: &str, From 9dfb3f2b9bb6628400fb66b11a796ef5c0fa08b6 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 17 Mar 2026 12:35:17 +0100 Subject: [PATCH 07/44] Rm unwraps --- src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e711527..c8a7841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -686,7 +686,7 @@ impl ProxyClient { ) -> Result<(HttpSender, HttpConnection), ProxyError> { let outbound_stream = tokio::net::TcpStream::connect(target).await?; - let domain = server_name_from_host(target).unwrap(); + let domain = server_name_from_host(target)?; let tls_stream = nesting_tls_connector .connect(domain, outbound_stream) .await?; @@ -785,6 +785,12 @@ pub enum ProxyError { IntConversion(#[from] TryFromIntError), #[error("Bad host name: {0}")] BadDnsName(#[from] tokio_rustls::rustls::pki_types::InvalidDnsNameError), + #[error("Invalid certificate encoding")] + InvalidCertificateEncoding, + #[error("Missing common name in certificate subject")] + MissingCertificateName, + #[error("Certificate common name is not valid UTF-8")] + InvalidCertificateName, #[error("HTTP: {0}")] Hyper(#[from] hyper::Error), #[error("Attested TLS: {0}")] @@ -809,17 +815,15 @@ impl From> for ProxyError { fn hostname_from_cert(cert: &CertificateDer<'static>) -> Result { let cert = x509_parser::parse_x509_certificate(cert.as_ref()) .map(|(_, parsed)| parsed) - .unwrap(); + .map_err(|_| ProxyError::InvalidCertificateEncoding)?; Ok(cert .subject() .iter_common_name() .next() - .unwrap() - // .ok_or_else(|| Self::bad_encoding("Missing common name"))? + .ok_or(ProxyError::MissingCertificateName)? .as_str() - // .map_err(|err| Self::bad_encoding(format!("Invalid common name: {err}"))) - .unwrap() + .map_err(|_| ProxyError::InvalidCertificateName)? .to_string()) } From 4650de63620f288675bb75dafbfc7c0f2299583b Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 19 Mar 2026 10:19:03 +0100 Subject: [PATCH 08/44] Fix ALPN --- src/lib.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c8a7841..319d07e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,9 +97,8 @@ pub async fn get_inner_tls_cert( pub async fn get_inner_tls_cert_with_config( server_name: String, attestation_verifier: AttestationVerifier, - mut outer_client_config: ClientConfig, + outer_client_config: ClientConfig, ) -> Result>, ProxyError> { - ensure_proxy_alpn_protocols(&mut outer_client_config.alpn_protocols); let outbound_stream = tokio::net::TcpStream::connect(&server_name).await?; let domain = server_name_from_host(&server_name)?; @@ -205,19 +204,18 @@ impl ProxyServer { /// Start with preconfigured TLS and require client auth on both nested sessions pub async fn new_with_tls_config_and_client_auth( cert_chain: Vec>, - mut outer_server_config: ServerConfig, + outer_server_config: ServerConfig, local: impl ToSocketAddrs, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, ) -> Result { - ensure_proxy_alpn_protocols(&mut outer_server_config.alpn_protocols); let server_name = certificate_identity_from_chain(&cert_chain)?; let inner_cert_resolver = build_attested_cert_resolver(attestation_generator, &cert_chain).await?; - let inner_server_config = if client_auth { + let mut inner_server_config = if client_auth { let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) @@ -230,6 +228,8 @@ impl ProxyServer { .with_cert_resolver(Arc::new(inner_cert_resolver)) }; + ensure_proxy_alpn_protocols(&mut inner_server_config.alpn_protocols); + let nesting_tls_acceptor = NestingTlsAcceptor::new(Arc::new(outer_server_config), Arc::new(inner_server_config)); let listener = TcpListener::bind(local).await?; @@ -440,14 +440,13 @@ impl ProxyClient { /// Create a new proxy client with given TLS configuration pub async fn new_with_tls_config( - mut outer_client_config: ClientConfig, + outer_client_config: ClientConfig, address: impl ToSocketAddrs, target_name: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, cert_chain: Option>>, ) -> Result { - ensure_proxy_alpn_protocols(&mut outer_client_config.alpn_protocols); let outer_has_client_auth = outer_client_config.client_auth_cert_resolver.has_certs(); let inner_has_client_auth = cert_chain.is_some(); @@ -457,7 +456,7 @@ impl ProxyClient { let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; - let inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { + let mut inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { let inner_cert_resolver = build_attested_cert_resolver(attestation_generator, cert_chain).await?; ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) @@ -470,6 +469,7 @@ impl ProxyClient { .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) .with_no_client_auth() }; + ensure_proxy_alpn_protocols(&mut inner_client_config.alpn_protocols); let nesting_tls_connector = NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); @@ -905,6 +905,56 @@ mod tests { assert_eq!(protocols, vec![ALPN_HTTP11.to_vec(), ALPN_H2.to_vec()]); } + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_negotiates_http2_by_default() { + let target_addr = example_http_service().await; + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (server_config, outer_client_config) = + generate_tls_config(cert_chain.clone(), private_key); + + let proxy_server = ProxyServer::new_with_tls_config( + cert_chain, + server_config, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let attested_cert_verifier = + AttestedCertificateVerifier::new(None, AttestationVerifier::mock()).unwrap(); + let mut inner_client_config = + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .dangerous() + .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) + .with_no_client_auth(); + ensure_proxy_alpn_protocols(&mut inner_client_config.alpn_protocols); + + let nesting_tls_connector = NestingTlsConnector::new( + Arc::new(outer_client_config), + Arc::new(inner_client_config), + ); + + let (sender, conn) = ProxyClient::setup_connection( + &nesting_tls_connector, + &format!("localhost:{}", proxy_addr.port()), + ) + .await + .unwrap(); + + assert!(matches!(sender, HttpSender::Http2(_))); + assert!(matches!(conn, HttpConnection::Http2 { .. })); + } + #[tokio::test(flavor = "multi_thread")] async fn http_proxy_default_constructors_work() { let target_addr = example_http_service().await; From 4fb29e2e4fad5a864f71cbe31a40bed737cd56dd Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 19 Mar 2026 10:21:50 +0100 Subject: [PATCH 09/44] Fmt --- src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 319d07e..cf4c327 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -939,10 +939,8 @@ mod tests { .with_no_client_auth(); ensure_proxy_alpn_protocols(&mut inner_client_config.alpn_protocols); - let nesting_tls_connector = NestingTlsConnector::new( - Arc::new(outer_client_config), - Arc::new(inner_client_config), - ); + let nesting_tls_connector = + NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); let (sender, conn) = ProxyClient::setup_connection( &nesting_tls_connector, From f6817210c96c15b5edbcfeab5f971b364825b83d Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 19 Mar 2026 11:57:37 +0100 Subject: [PATCH 10/44] Make both nested and inner only listeners --- src/attested_get.rs | 6 +- src/file_server.rs | 16 +- src/lib.rs | 593 ++++++++++++++++++++++++++++++++------------ src/main.rs | 54 ++-- 4 files changed, 474 insertions(+), 195 deletions(-) diff --git a/src/attested_get.rs b/src/attested_get.rs index 3b4575f..97b346d 100644 --- a/src/attested_get.rs +++ b/src/attested_get.rs @@ -78,12 +78,14 @@ mod tests { // Setup a proxy server targetting the static file server let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, + Some("localhost".to_string()), ) .await .unwrap(); diff --git a/src/file_server.rs b/src/file_server.rs index 424008e..8390c9f 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -7,8 +7,9 @@ use tower_http::services::ServeDir; /// Setup a static file server serving the given directory, and a proxy server targetting it pub async fn attested_file_server( path_to_serve: PathBuf, - cert_and_key: TlsCertAndKey, - listen_addr: impl ToSocketAddrs, + outer_cert_and_key: Option, + outer_listen_addr: impl ToSocketAddrs, + inner_listen_addr: impl ToSocketAddrs, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, @@ -16,8 +17,9 @@ pub async fn attested_file_server( let target_addr = static_file_server(path_to_serve).await?; let server = ProxyServer::new( - cert_and_key, - listen_addr, + outer_cert_and_key, + Some(outer_listen_addr), + inner_listen_addr, target_addr.to_string(), attestation_generator, attestation_verifier, @@ -99,12 +101,14 @@ mod tests { // Setup a proxy server targetting the static file server let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, + Some("localhost".to_string()), ) .await .unwrap(); diff --git a/src/lib.rs b/src/lib.rs index cf4c327..a36d5b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ use thiserror::Error; use tokio::io::{self, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{mpsc, oneshot}; +use tokio_rustls::TlsAcceptor; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; use tokio_rustls::rustls::{ self, ClientConfig, RootCertStore, ServerConfig, @@ -46,6 +47,7 @@ const SERVER_RECONNECT_MAX_BACKOFF_SECS: u64 = 120; const KEEP_ALIVE_INTERVAL: u64 = 30; const KEEP_ALIVE_TIMEOUT: u64 = 10; +const DEFAULT_INNER_CERTIFICATE_NAME: &str = "localhost"; type RequestWithResponseSender = ( http::Request, @@ -132,111 +134,123 @@ pub async fn get_inner_tls_cert_with_config( /// A TLS over TCP server which provides an attestation before forwarding traffic to a given target address pub struct ProxyServer { - nesting_tls_acceptor: NestingTlsAcceptor, - /// The underlying TCP listener - listener: Arc, + outer_listener: Option>, + outer_tls_acceptor: Option, + inner_listener: Arc, + inner_tls_acceptor: TlsAcceptor, /// The address/hostname of the target service we are proxying to target: String, } impl ProxyServer { - pub async fn new( - cert_and_key: TlsCertAndKey, - local: impl ToSocketAddrs, + /// Start with dual listeners. The outer nested-TLS listener is optional. + pub async fn new( + outer_cert_and_key: Option, + outer_local: Option, + inner_local: impl ToSocketAddrs, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, - ) -> Result { - let outer_server_config = if client_auth { - let root_store = - RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?; - - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_client_cert_verifier(verifier) - .with_single_cert( - cert_and_key.cert_chain.clone(), - cert_and_key.key.clone_key(), - )? - } else { - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_no_client_auth() - .with_single_cert( - cert_and_key.cert_chain.clone(), - cert_and_key.key.clone_key(), - )? + ) -> Result + where + O: ToSocketAddrs, + { + if outer_cert_and_key.is_some() && outer_local.is_none() { + return Err(ProxyError::OuterTlsWithoutOuterListener); + } + + let outer_certificate_name = outer_cert_and_key + .as_ref() + .map(|cert_and_key| certificate_identity_from_chain(&cert_and_key.cert_chain)) + .transpose()?; + let outer_server_config = match outer_cert_and_key { + Some(cert_and_key) => { + let config = if client_auth { + let root_store = + RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?; + + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_client_cert_verifier(verifier) + .with_single_cert( + cert_and_key.cert_chain.clone(), + cert_and_key.key.clone_key(), + )? + } else { + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_no_client_auth() + .with_single_cert( + cert_and_key.cert_chain.clone(), + cert_and_key.key.clone_key(), + )? + }; + Some(config) + } + None => None, }; - Self::new_with_tls_config_and_client_auth( - cert_and_key.cert_chain, + Self::new_with_tls_config( outer_server_config, - local, + outer_local, + inner_local, target, attestation_generator, attestation_verifier, client_auth, + outer_certificate_name, ) .await } /// Start with preconfigured TLS - pub async fn new_with_tls_config( - cert_chain: Vec>, - outer_server_config: ServerConfig, - local: impl ToSocketAddrs, - target: String, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - ) -> Result { - Self::new_with_tls_config_and_client_auth( - cert_chain, - outer_server_config, - local, - target, - attestation_generator, - attestation_verifier, - false, - ) - .await - } - - /// Start with preconfigured TLS and require client auth on both nested sessions - pub async fn new_with_tls_config_and_client_auth( - cert_chain: Vec>, - outer_server_config: ServerConfig, - local: impl ToSocketAddrs, + pub async fn new_with_tls_config( + outer_server_config: Option, + outer_local: Option, + inner_local: impl ToSocketAddrs, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, - ) -> Result { - let server_name = certificate_identity_from_chain(&cert_chain)?; - let inner_cert_resolver = - build_attested_cert_resolver(attestation_generator, &cert_chain).await?; - - let mut inner_server_config = if client_auth { - let attested_cert_verifier = - AttestedCertificateVerifier::new(None, attestation_verifier)?; - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_client_cert_verifier(Arc::new(attested_cert_verifier)) - .with_cert_resolver(Arc::new(inner_cert_resolver)) - } else { - let _ = server_name; - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_no_client_auth() - .with_cert_resolver(Arc::new(inner_cert_resolver)) - }; - - ensure_proxy_alpn_protocols(&mut inner_server_config.alpn_protocols); + certificate_name: Option, + ) -> Result + where + O: ToSocketAddrs, + { + if outer_server_config.is_some() && outer_local.is_none() { + return Err(ProxyError::OuterTlsWithoutOuterListener); + } - let nesting_tls_acceptor = - NestingTlsAcceptor::new(Arc::new(outer_server_config), Arc::new(inner_server_config)); - let listener = TcpListener::bind(local).await?; + let inner_server_config = Arc::new( + build_inner_server_config( + attestation_generator, + attestation_verifier, + client_auth, + certificate_name, + ) + .await?, + ); + let inner_listener = Arc::new(TcpListener::bind(inner_local).await?); + let inner_tls_acceptor = TlsAcceptor::from(inner_server_config.clone()); + + let (outer_listener, outer_tls_acceptor) = match (outer_server_config, outer_local) { + (Some(outer_server_config), Some(outer_local)) => { + let outer_listener = Arc::new(TcpListener::bind(outer_local).await?); + let acceptor = NestingTlsAcceptor::new( + Arc::new(outer_server_config), + inner_server_config.clone(), + ); + (Some(outer_listener), Some(acceptor)) + } + (Some(_), None) => return Err(ProxyError::OuterTlsWithoutOuterListener), + (None, _) => (None, None), + }; Ok(Self { - nesting_tls_acceptor, - listener: listener.into(), + outer_listener, + outer_tls_acceptor, + inner_listener, + inner_tls_acceptor, target, }) } @@ -246,33 +260,92 @@ impl ProxyServer { /// Returns the handle for the task handling the connection pub async fn accept(&self) -> Result, ProxyError> { let target = self.target.clone(); - let (inbound, client_addr) = self.listener.accept().await?; - let nesting_tls_acceptor = self.nesting_tls_acceptor.clone(); + let outer_listener = self.outer_listener.clone(); + let outer_tls_acceptor = self.outer_tls_acceptor.clone(); + let inner_listener = self.inner_listener.clone(); + let inner_tls_acceptor = self.inner_tls_acceptor.clone(); + + let join_handle = match (outer_listener, outer_tls_acceptor) { + (Some(outer_listener), Some(outer_tls_acceptor)) => { + let ((inbound, client_addr), use_outer) = tokio::select! { + accepted = outer_listener.accept() => (accepted?, true), + accepted = inner_listener.accept() => (accepted?, false), + }; - let join_handle = tokio::spawn(async move { - match nesting_tls_acceptor.accept(inbound).await { - Ok(tls_stream) => { - if let Err(err) = Self::handle_connection(tls_stream, target, client_addr).await - { - warn!("Failed to handle connection: {err}"); + tokio::spawn(async move { + if use_outer { + match outer_tls_acceptor.accept(inbound).await { + Ok(tls_stream) => { + if let Err(err) = + Self::handle_outer_connection(tls_stream, target, client_addr) + .await + { + warn!("Failed to handle outer connection: {err}"); + } + } + Err(err) => { + warn!("Outer attestation exchange failed: {err}"); + } + } + } else { + match inner_tls_acceptor.accept(inbound).await { + Ok(tls_stream) => { + if let Err(err) = + Self::handle_inner_connection(tls_stream, target, client_addr) + .await + { + warn!("Failed to handle inner connection: {err}"); + } + } + Err(err) => { + warn!("Inner attestation exchange failed: {err}"); + } + } } - } - Err(err) => { - warn!("Attestation exchange failed: {err}"); - } + }) } - }); + _ => { + let (inbound, client_addr) = inner_listener.accept().await?; + tokio::spawn(async move { + match inner_tls_acceptor.accept(inbound).await { + Ok(tls_stream) => { + if let Err(err) = + Self::handle_inner_connection(tls_stream, target, client_addr).await + { + warn!("Failed to handle inner connection: {err}"); + } + } + Err(err) => { + warn!("Inner attestation exchange failed: {err}"); + } + } + }) + } + }; Ok(join_handle) } /// Helper to get the socket address of the underlying TCP listener pub fn local_addr(&self) -> std::io::Result { - self.listener.local_addr() + match &self.outer_listener { + Some(listener) => listener.local_addr(), + None => self.inner_listener.local_addr(), + } } - /// Handle an incoming connection from a proxy-client - async fn handle_connection( + pub fn outer_local_addr(&self) -> std::io::Result> { + self.outer_listener + .as_ref() + .map(|listener| listener.local_addr()) + .transpose() + } + + pub fn inner_local_addr(&self) -> std::io::Result { + self.inner_listener.local_addr() + } + + async fn handle_outer_connection( tls_stream: NestingTlsStream, target: String, client_addr: SocketAddr, @@ -280,7 +353,29 @@ impl ProxyServer { debug!("[proxy-server] accepted connection"); let http_version = HttpVersion::from_negotiated_protocol_server(&tls_stream); + Self::serve_tls_stream(tls_stream, http_version, target, client_addr).await + } + async fn handle_inner_connection( + tls_stream: tokio_rustls::server::TlsStream, + target: String, + client_addr: SocketAddr, + ) -> Result<(), ProxyError> { + debug!("[proxy-server] accepted inner-only connection"); + + let http_version = HttpVersion::from_negotiated_protocol_server(&tls_stream); + Self::serve_tls_stream(tls_stream, http_version, target, client_addr).await + } + + async fn serve_tls_stream( + tls_stream: IO, + http_version: HttpVersion, + target: String, + client_addr: SocketAddr, + ) -> Result<(), ProxyError> + where + IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static, + { // Setup a request handler let service = service_fn(move |mut req| { debug!("[proxy-server] Handling request {req:?}"); @@ -457,8 +552,11 @@ impl ProxyClient { let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; let mut inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { - let inner_cert_resolver = - build_attested_cert_resolver(attestation_generator, cert_chain).await?; + let inner_cert_resolver = build_attested_cert_resolver( + attestation_generator, + Some(certificate_identity_from_chain(cert_chain)?), + ) + .await?; ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .dangerous() .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) @@ -803,6 +901,8 @@ pub enum ProxyError { MpscSend, #[error("Client auth must be configured on both the inner and outer TLS sessions")] ClientAuthMisconfigured, + #[error("Outer TLS configuration requires an outer listener address")] + OuterTlsWithoutOuterListener, } impl From> for ProxyError { @@ -835,13 +935,40 @@ fn certificate_identity_from_chain( async fn build_attested_cert_resolver( attestation_generator: AttestationGenerator, - cert_chain: &[CertificateDer<'static>], + certificate_name: Option, ) -> Result { - let certificate_name = certificate_identity_from_chain(cert_chain)?; - Ok( - AttestedCertificateResolver::new(attestation_generator, None, certificate_name, vec![]) - .await?, + Ok(AttestedCertificateResolver::new( + attestation_generator, + None, + certificate_name.unwrap_or_else(|| DEFAULT_INNER_CERTIFICATE_NAME.to_string()), + vec![], ) + .await?) +} + +async fn build_inner_server_config( + attestation_generator: AttestationGenerator, + attestation_verifier: AttestationVerifier, + client_auth: bool, + certificate_name: Option, +) -> Result { + let inner_cert_resolver = + build_attested_cert_resolver(attestation_generator, certificate_name).await?; + + let mut inner_server_config = if client_auth { + let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_client_cert_verifier(Arc::new(attested_cert_verifier)) + .with_cert_resolver(Arc::new(inner_cert_resolver)) + } else { + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_no_client_auth() + .with_cert_resolver(Arc::new(inner_cert_resolver)) + }; + + ensure_proxy_alpn_protocols(&mut inner_server_config.alpn_protocols); + + Ok(inner_server_config) } /// If no port was provided, default to 443 @@ -882,6 +1009,7 @@ where #[cfg(test)] mod tests { use attestation::{AttestationType, measurements::MeasurementPolicy}; + use tokio_rustls::TlsConnector; use super::*; use test_helpers::{ @@ -906,25 +1034,94 @@ mod tests { } #[tokio::test(flavor = "multi_thread")] - async fn http_proxy_negotiates_http2_by_default() { + async fn dual_listener_server_reports_expected_addresses() { let target_addr = example_http_service().await; let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); - let (server_config, outer_client_config) = - generate_tls_config(cert_chain.clone(), private_key); + let tls_cert_and_key = TlsCertAndKey { + cert_chain, + key: private_key, + }; - let proxy_server = ProxyServer::new_with_tls_config( + let dual_listener_server = ProxyServer::new( + Some(tls_cert_and_key), + Some("127.0.0.1:0"), + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + false, + ) + .await + .unwrap(); + + let outer_addr = dual_listener_server.outer_local_addr().unwrap().unwrap(); + let inner_addr = dual_listener_server.inner_local_addr().unwrap(); + assert_eq!(dual_listener_server.local_addr().unwrap(), outer_addr); + assert_ne!(outer_addr, inner_addr); + + let inner_only_server = ProxyServer::new( + None, + None::<&str>, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + false, + ) + .await + .unwrap(); + + let inner_only_addr = inner_only_server.inner_local_addr().unwrap(); + assert!(inner_only_server.outer_local_addr().unwrap().is_none()); + assert_eq!(inner_only_server.local_addr().unwrap(), inner_only_addr); + } + + #[tokio::test(flavor = "multi_thread")] + async fn outer_tls_requires_outer_listener_address() { + let target_addr = example_http_service().await; + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let tls_cert_and_key = TlsCertAndKey { cert_chain, - server_config, + key: private_key, + }; + + let result = ProxyServer::new( + Some(tls_cert_and_key), + None::<&str>, + "127.0.0.1:0", + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + false, + ) + .await; + + assert!(matches!( + result, + Err(ProxyError::OuterTlsWithoutOuterListener) + )); + } + + #[tokio::test(flavor = "multi_thread")] + async fn inner_only_listener_negotiates_http2_by_default() { + let _ = rustls::crypto::ring::default_provider().install_default(); + let target_addr = example_http_service().await; + + let proxy_server = ProxyServer::new( + None, + None::<&str>, "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, ) .await .unwrap(); - let proxy_addr = proxy_server.local_addr().unwrap(); + let inner_addr = proxy_server.inner_local_addr().unwrap(); tokio::spawn(async move { proxy_server.accept().await.unwrap(); @@ -932,44 +1129,46 @@ mod tests { let attested_cert_verifier = AttestedCertificateVerifier::new(None, AttestationVerifier::mock()).unwrap(); - let mut inner_client_config = + let mut client_config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .dangerous() .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) .with_no_client_auth(); - ensure_proxy_alpn_protocols(&mut inner_client_config.alpn_protocols); + ensure_proxy_alpn_protocols(&mut client_config.alpn_protocols); - let nesting_tls_connector = - NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); + let tls_connector = TlsConnector::from(Arc::new(client_config)); + let outbound_stream = TcpStream::connect(inner_addr).await.unwrap(); + let domain = ServerName::try_from("localhost".to_string()).unwrap(); + let mut tls_stream = tls_connector + .connect(domain, outbound_stream) + .await + .unwrap(); - let (sender, conn) = ProxyClient::setup_connection( - &nesting_tls_connector, - &format!("localhost:{}", proxy_addr.port()), - ) - .await - .unwrap(); + assert!(matches!( + HttpVersion::from_negotiated_protocol_client(&tls_stream), + HttpVersion::Http2 + )); - assert!(matches!(sender, HttpSender::Http2(_))); - assert!(matches!(conn, HttpConnection::Http2 { .. })); + tls_stream.shutdown().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] - async fn http_proxy_default_constructors_work() { + async fn http_proxy_negotiates_http2_by_default() { let target_addr = example_http_service().await; let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); - let server_cert = cert_chain[0].clone(); + let (server_config, outer_client_config) = + generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new( - TlsCertAndKey { - cert_chain, - key: private_key, - }, + let proxy_server = ProxyServer::new_with_tls_config( + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, + Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -980,31 +1179,81 @@ mod tests { proxy_server.accept().await.unwrap(); }); - let proxy_client = ProxyClient::new( - None, - "127.0.0.1:0".to_string(), - format!("localhost:{}", proxy_addr.port()), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - Some(server_cert), + let attested_cert_verifier = + AttestedCertificateVerifier::new(None, AttestationVerifier::mock()).unwrap(); + let mut inner_client_config = + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .dangerous() + .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) + .with_no_client_auth(); + ensure_proxy_alpn_protocols(&mut inner_client_config.alpn_protocols); + + let nesting_tls_connector = + NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); + + let (sender, conn) = ProxyClient::setup_connection( + &nesting_tls_connector, + &format!("localhost:{}", proxy_addr.port()), ) .await .unwrap(); - let proxy_client_addr = proxy_client.local_addr().unwrap(); - - tokio::spawn(async move { - proxy_client.accept().await.unwrap(); - }); - - let res = reqwest::get(format!("http://{}", proxy_client_addr)) - .await - .unwrap(); - - let res_body = res.text().await.unwrap(); - assert_eq!(res_body, "No measurements"); + assert!(matches!(sender, HttpSender::Http2(_))); + assert!(matches!(conn, HttpConnection::Http2 { .. })); } + // #[tokio::test(flavor = "multi_thread")] + // async fn http_proxy_default_constructors_work() { + // let target_addr = example_http_service().await; + // + // let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + // let server_cert = cert_chain[0].clone(); + // + // let proxy_server = ProxyServer::new( + // TlsCertAndKey { + // cert_chain, + // key: private_key, + // }, + // "127.0.0.1:0", + // target_addr.to_string(), + // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + // AttestationVerifier::expect_none(), + // false, + // ) + // .await + // .unwrap(); + // + // let proxy_addr = proxy_server.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_server.accept().await.unwrap(); + // }); + // + // let proxy_client = ProxyClient::new( + // None, + // "127.0.0.1:0".to_string(), + // format!("localhost:{}", proxy_addr.port()), + // AttestationGenerator::with_no_attestation(), + // AttestationVerifier::mock(), + // Some(server_cert), + // ) + // .await + // .unwrap(); + // + // let proxy_client_addr = proxy_client.local_addr().unwrap(); + // + // tokio::spawn(async move { + // proxy_client.accept().await.unwrap(); + // }); + // + // let res = reqwest::get(format!("http://{}", proxy_client_addr)) + // .await + // .unwrap(); + // + // let res_body = res.text().await.unwrap(); + // assert_eq!(res_body, "No measurements"); + // } + // Server has mock DCAP, client has no attestation and no client auth #[tokio::test(flavor = "multi_thread")] async fn http_proxy_with_server_attestation() { @@ -1015,12 +1264,14 @@ mod tests { let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, + Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1076,14 +1327,15 @@ mod tests { server_private_key, ); - let proxy_server = ProxyServer::new_with_tls_config_and_client_auth( - server_cert_chain, - server_tls_server_config, + let proxy_server = ProxyServer::new_with_tls_config( + Some(server_tls_server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), true, + Some(certificate_identity_from_chain(&server_cert_chain).unwrap()), ) .await .unwrap(); @@ -1130,12 +1382,14 @@ mod tests { generate_tls_config(server_cert_chain.clone(), server_private_key); let proxy_server = ProxyServer::new_with_tls_config( - server_cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), + false, + Some(certificate_identity_from_chain(&server_cert_chain).unwrap()), ) .await .unwrap(); @@ -1193,14 +1447,15 @@ mod tests { server_private_key, ); - let proxy_server = ProxyServer::new_with_tls_config_and_client_auth( - server_cert_chain, - server_tls_server_config, + let proxy_server = ProxyServer::new_with_tls_config( + Some(server_tls_server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::mock(), true, + Some(certificate_identity_from_chain(&server_cert_chain).unwrap()), ) .await .unwrap(); @@ -1249,12 +1504,14 @@ mod tests { let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); let proxy_server = ProxyServer::new_with_tls_config( - cert_chain.clone(), - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, + Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1291,12 +1548,14 @@ mod tests { let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), + false, + Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1331,12 +1590,14 @@ mod tests { let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, + Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1396,12 +1657,14 @@ mod tests { let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, + Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1469,12 +1732,14 @@ mod tests { server_config.alpn_protocols.push(ALPN_HTTP11.to_vec()); let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, + Some(server_config), + Some("127.0.0.1:0"), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, + Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); diff --git a/src/main.rs b/src/main.rs index c14c414..e198b3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,9 +77,12 @@ enum CliCommand { }, /// Run a proxy server Server { - /// Socket address to listen on - #[arg(short, long, default_value = "0.0.0.0:0", env = "LISTEN_ADDR")] - listen_addr: SocketAddr, + /// Socket address to listen on for the outer nested-TLS listener + #[arg(long, default_value = "0.0.0.0:443")] + outer_listen_addr: SocketAddr, + /// Socket address to listen on for the inner-only attested TLS listener + #[arg(long, default_value = "0.0.0.0:4433")] + inner_listen_addr: SocketAddr, /// The hostname:port or ip:port of the target service to forward traffic to target_addr: String, /// Type of attestation to present (dafaults to 'auto' for automatic detection) @@ -119,19 +122,22 @@ enum CliCommand { AttestedFileServer { /// Filesystem path to statically serve path_to_serve: PathBuf, - /// Socket address to listen on - #[arg(short, long, default_value = "0.0.0.0:0", env = "LISTEN_ADDR")] - listen_addr: SocketAddr, + /// Socket address to listen on for the outer nested-TLS listener + #[arg(long, default_value = "0.0.0.0:443")] + outer_listen_addr: SocketAddr, + /// Socket address to listen on for the inner-only attested TLS listener + #[arg(long, default_value = "0.0.0.0:4433")] + inner_listen_addr: SocketAddr, /// Type of attestation to present (dafaults to none) /// If other than None, a TLS key and certicate must also be given #[arg(long, env = "SERVER_ATTESTATION_TYPE")] server_attestation_type: Option, /// The path to a PEM encoded private key #[arg(long, env = "TLS_PRIVATE_KEY_PATH")] - tls_private_key_path: PathBuf, + tls_private_key_path: Option, /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. #[arg(long, env = "TLS_CERTIFICATE_PATH")] - tls_certificate_path: PathBuf, + tls_certificate_path: Option, /// URL of the remote dummy attestation service. Only use with --server-attestation-type /// dummy #[arg(long)] @@ -277,7 +283,8 @@ async fn main() -> anyhow::Result<()> { } } CliCommand::Server { - listen_addr, + outer_listen_addr, + inner_listen_addr, target_addr, tls_private_key_path, tls_certificate_path, @@ -299,7 +306,8 @@ async fn main() -> anyhow::Result<()> { let server = ProxyServer::new( tls_cert_and_chain, - listen_addr, + Some(outer_listen_addr), + inner_listen_addr, target_addr, local_attestation_generator, attestation_verifier, @@ -344,14 +352,15 @@ async fn main() -> anyhow::Result<()> { } CliCommand::AttestedFileServer { path_to_serve, - listen_addr, + outer_listen_addr, + inner_listen_addr, server_attestation_type, tls_private_key_path, tls_certificate_path, dev_dummy_dcap, } => { let tls_cert_and_chain = - load_tls_cert_and_key(tls_certificate_path, tls_private_key_path)?; + load_tls_cert_and_key_server(tls_certificate_path, tls_private_key_path)?; let server_attestation_type: AttestationType = serde_json::from_value( serde_json::Value::String(server_attestation_type.unwrap_or("none".to_string())), @@ -363,7 +372,8 @@ async fn main() -> anyhow::Result<()> { attested_file_server( path_to_serve, tls_cert_and_chain, - listen_addr, + outer_listen_addr, + inner_listen_addr, attestation_generator, attestation_verifier, false, @@ -410,16 +420,14 @@ async fn main() -> anyhow::Result<()> { fn load_tls_cert_and_key_server( cert_chain: Option, private_key: Option, -) -> anyhow::Result { - if let Some(private_key) = private_key { - load_tls_cert_and_key( - cert_chain.ok_or(anyhow!("Private key given but no certificate chain"))?, - private_key, - ) - } else if cert_chain.is_some() { - Err(anyhow!("Certificate chain provided but no private key")) - } else { - Err(anyhow!("No private key provided")) +) -> anyhow::Result> { + match (cert_chain, private_key) { + (Some(cert_chain), Some(private_key)) => { + Ok(Some(load_tls_cert_and_key(cert_chain, private_key)?)) + } + (Some(_), None) => Err(anyhow!("Certificate chain provided but no private key")), + (None, Some(_)) => Err(anyhow!("Private key given but no certificate chain")), + (None, None) => Ok(None), } } From b52645ac6e1afcd44e04ed7aefa8f586f68c453f Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 19 Mar 2026 12:14:12 +0100 Subject: [PATCH 11/44] Clippy --- src/file_server.rs | 18 +++++++----------- src/lib.rs | 12 +++++++----- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/file_server.rs b/src/file_server.rs index 8390c9f..1e07e0d 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -142,27 +142,23 @@ mod tests { let client = reqwest::Client::new(); // This makes the request - let (body, content_type) = get_body_and_content_type( - format!("http://{}/foo.txt", proxy_client_addr.to_string()), - &client, - ) - .await; + let (body, content_type) = + get_body_and_content_type(format!("http://{}/foo.txt", proxy_client_addr), &client) + .await; assert_eq!(content_type, "text/plain"); assert_eq!(body, b"bar"); let (body, content_type) = get_body_and_content_type( - format!("http://{}/index.html", proxy_client_addr.to_string()), + format!("http://{}/index.html", proxy_client_addr), &client, ) .await; assert_eq!(content_type, "text/html"); assert_eq!(body, b"foo"); - let (body, content_type) = get_body_and_content_type( - format!("http://{}/data.bin", proxy_client_addr.to_string()), - &client, - ) - .await; + let (body, content_type) = + get_body_and_content_type(format!("http://{}/data.bin", proxy_client_addr), &client) + .await; assert_eq!(content_type, "application/octet-stream"); assert_eq!(body, [0u8; 32]); } diff --git a/src/lib.rs b/src/lib.rs index a36d5b3..e6098bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,6 +144,7 @@ pub struct ProxyServer { impl ProxyServer { /// Start with dual listeners. The outer nested-TLS listener is optional. + #[allow(clippy::too_many_arguments)] pub async fn new( outer_cert_and_key: Option, outer_local: Option, @@ -204,6 +205,7 @@ impl ProxyServer { } /// Start with preconfigured TLS + #[allow(clippy::too_many_arguments)] pub async fn new_with_tls_config( outer_server_config: Option, outer_local: Option, @@ -1299,7 +1301,7 @@ mod tests { proxy_client.accept().await.unwrap(); }); - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + let res = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); @@ -1420,7 +1422,7 @@ mod tests { proxy_client.accept().await.unwrap(); }); - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + let res = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); @@ -1704,7 +1706,7 @@ mod tests { proxy_client.accept().await.unwrap(); }); - let _initial_response = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + let _initial_response = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); @@ -1712,7 +1714,7 @@ mod tests { connection_breaker_tx.send(()).unwrap(); // Make another request - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + let res = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); @@ -1767,7 +1769,7 @@ mod tests { proxy_client.accept().await.unwrap(); }); - let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + let res = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); From edf728ee65cc617ebeae6b640feb55c4cdade4cd Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 19 Mar 2026 12:26:57 +0100 Subject: [PATCH 12/44] Improve constructors --- src/attested_get.rs | 14 ++- src/file_server.rs | 33 +++--- src/lib.rs | 263 ++++++++++++++++++++++++++------------------ src/main.rs | 12 +- 4 files changed, 194 insertions(+), 128 deletions(-) diff --git a/src/attested_get.rs b/src/attested_get.rs index 97b346d..16fd82e 100644 --- a/src/attested_get.rs +++ b/src/attested_get.rs @@ -55,7 +55,7 @@ async fn attested_get_with_client( mod tests { use super::*; use crate::{ - ProxyServer, + OuterTlsConfig, OuterTlsMode, ProxyServer, attestation::AttestationType, file_server::static_file_server, test_helpers::{generate_certificate_chain_for_host, generate_tls_config}, @@ -77,15 +77,19 @@ mod tests { let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); // Setup a proxy server targetting the static file server - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some("localhost".to_string()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, - Some("localhost".to_string()), ) .await .unwrap(); diff --git a/src/file_server.rs b/src/file_server.rs index 1e07e0d..d2cfd90 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -1,5 +1,8 @@ //! Static HTTP file server provided by an attested TLS proxy server -use crate::{AttestationGenerator, AttestationVerifier, ProxyError, ProxyServer, TlsCertAndKey}; +use crate::{ + AttestationGenerator, AttestationVerifier, OuterTlsConfig, OuterTlsMode, ProxyError, + ProxyServer, TlsCertAndKey, +}; use std::{net::SocketAddr, path::PathBuf}; use tokio::net::ToSocketAddrs; use tower_http::services::ServeDir; @@ -17,8 +20,10 @@ pub async fn attested_file_server( let target_addr = static_file_server(path_to_serve).await?; let server = ProxyServer::new( - outer_cert_and_key, - Some(outer_listen_addr), + outer_cert_and_key.map(|cert_and_key| OuterTlsConfig { + listen_addr: outer_listen_addr, + tls: OuterTlsMode::CertAndKey(cert_and_key), + }), inner_listen_addr, target_addr.to_string(), attestation_generator, @@ -54,7 +59,7 @@ pub(crate) async fn static_file_server(path: PathBuf) -> Resultfoo"); diff --git a/src/lib.rs b/src/lib.rs index e6098bb..64bb2de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,19 @@ pub struct TlsCertAndKey { pub key: PrivateKeyDer<'static>, } +pub struct OuterTlsConfig { + pub listen_addr: A, + pub tls: OuterTlsMode, +} + +pub enum OuterTlsMode { + CertAndKey(TlsCertAndKey), + Preconfigured { + server_config: ServerConfig, + certificate_name: Option, + }, +} + /// Adds HTTP 1 and 2 to the list of allowed protocols fn ensure_proxy_alpn_protocols(alpn_protocols: &mut Vec>) { for protocol in [ALPN_H2, ALPN_HTTP11] { @@ -144,10 +157,8 @@ pub struct ProxyServer { impl ProxyServer { /// Start with dual listeners. The outer nested-TLS listener is optional. - #[allow(clippy::too_many_arguments)] pub async fn new( - outer_cert_and_key: Option, - outer_local: Option, + outer_session: Option>, inner_local: impl ToSocketAddrs, target: String, attestation_generator: AttestationGenerator, @@ -157,17 +168,14 @@ impl ProxyServer { where O: ToSocketAddrs, { - if outer_cert_and_key.is_some() && outer_local.is_none() { - return Err(ProxyError::OuterTlsWithoutOuterListener); - } - - let outer_certificate_name = outer_cert_and_key - .as_ref() - .map(|cert_and_key| certificate_identity_from_chain(&cert_and_key.cert_chain)) - .transpose()?; - let outer_server_config = match outer_cert_and_key { - Some(cert_and_key) => { - let config = if client_auth { + let outer_session = match outer_session { + Some(OuterTlsConfig { + listen_addr, + tls: OuterTlsMode::CertAndKey(cert_and_key), + }) => { + let certificate_name = + Some(certificate_identity_from_chain(&cert_and_key.cert_chain)?); + let server_config = if client_auth { let root_store = RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?; @@ -186,43 +194,69 @@ impl ProxyServer { cert_and_key.key.clone_key(), )? }; - Some(config) + + Some(OuterTlsConfig { + listen_addr, + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name, + }, + }) } + Some(OuterTlsConfig { + listen_addr, + tls: + OuterTlsMode::Preconfigured { + server_config, + certificate_name, + }, + }) => Some(OuterTlsConfig { + listen_addr, + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name, + }, + }), None => None, }; - Self::new_with_tls_config( - outer_server_config, - outer_local, + Self::new_inner( + outer_session, inner_local, target, attestation_generator, attestation_verifier, client_auth, - outer_certificate_name, ) .await } - /// Start with preconfigured TLS - #[allow(clippy::too_many_arguments)] - pub async fn new_with_tls_config( - outer_server_config: Option, - outer_local: Option, + async fn new_inner( + outer_session: Option>, inner_local: impl ToSocketAddrs, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, - certificate_name: Option, ) -> Result where O: ToSocketAddrs, { - if outer_server_config.is_some() && outer_local.is_none() { - return Err(ProxyError::OuterTlsWithoutOuterListener); - } - + let (outer_server_config, certificate_name, outer_local) = match outer_session { + Some(OuterTlsConfig { + listen_addr, + tls: + OuterTlsMode::Preconfigured { + server_config, + certificate_name, + }, + }) => (Some(server_config), certificate_name, Some(listen_addr)), + Some(OuterTlsConfig { + listen_addr: _, + tls: OuterTlsMode::CertAndKey(_), + }) => unreachable!("cert/key outer session should be normalized via ProxyServer::new"), + None => (None, None, None), + }; let inner_server_config = Arc::new( build_inner_server_config( attestation_generator, @@ -244,7 +278,9 @@ impl ProxyServer { ); (Some(outer_listener), Some(acceptor)) } - (Some(_), None) => return Err(ProxyError::OuterTlsWithoutOuterListener), + (Some(_), None) => { + unreachable!("outer config without outer listener is unrepresentable") + } (None, _) => (None, None), }; @@ -903,8 +939,6 @@ pub enum ProxyError { MpscSend, #[error("Client auth must be configured on both the inner and outer TLS sessions")] ClientAuthMisconfigured, - #[error("Outer TLS configuration requires an outer listener address")] - OuterTlsWithoutOuterListener, } impl From> for ProxyError { @@ -1046,8 +1080,10 @@ mod tests { }; let dual_listener_server = ProxyServer::new( - Some(tls_cert_and_key), - Some("127.0.0.1:0"), + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::CertAndKey(tls_cert_and_key), + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), @@ -1063,8 +1099,7 @@ mod tests { assert_ne!(outer_addr, inner_addr); let inner_only_server = ProxyServer::new( - None, - None::<&str>, + None::>, "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), @@ -1079,41 +1114,13 @@ mod tests { assert_eq!(inner_only_server.local_addr().unwrap(), inner_only_addr); } - #[tokio::test(flavor = "multi_thread")] - async fn outer_tls_requires_outer_listener_address() { - let target_addr = example_http_service().await; - - let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); - let tls_cert_and_key = TlsCertAndKey { - cert_chain, - key: private_key, - }; - - let result = ProxyServer::new( - Some(tls_cert_and_key), - None::<&str>, - "127.0.0.1:0", - target_addr.to_string(), - AttestationGenerator::with_no_attestation(), - AttestationVerifier::expect_none(), - false, - ) - .await; - - assert!(matches!( - result, - Err(ProxyError::OuterTlsWithoutOuterListener) - )); - } - #[tokio::test(flavor = "multi_thread")] async fn inner_only_listener_negotiates_http2_by_default() { let _ = rustls::crypto::ring::default_provider().install_default(); let target_addr = example_http_service().await; let proxy_server = ProxyServer::new( - None, - None::<&str>, + None::>, "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), @@ -1162,15 +1169,19 @@ mod tests { let (server_config, outer_client_config) = generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, - Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1265,15 +1276,19 @@ mod tests { let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, - Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1329,15 +1344,21 @@ mod tests { server_private_key, ); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_tls_server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config: server_tls_server_config, + certificate_name: Some( + certificate_identity_from_chain(&server_cert_chain).unwrap(), + ), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), true, - Some(certificate_identity_from_chain(&server_cert_chain).unwrap()), ) .await .unwrap(); @@ -1383,15 +1404,21 @@ mod tests { let (server_config, client_config) = generate_tls_config(server_cert_chain.clone(), server_private_key); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some( + certificate_identity_from_chain(&server_cert_chain).unwrap(), + ), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), false, - Some(certificate_identity_from_chain(&server_cert_chain).unwrap()), ) .await .unwrap(); @@ -1449,15 +1476,21 @@ mod tests { server_private_key, ); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_tls_server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config: server_tls_server_config, + certificate_name: Some( + certificate_identity_from_chain(&server_cert_chain).unwrap(), + ), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::mock(), true, - Some(certificate_identity_from_chain(&server_cert_chain).unwrap()), ) .await .unwrap(); @@ -1505,15 +1538,19 @@ mod tests { let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, - Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1549,15 +1586,19 @@ mod tests { let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), false, - Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1591,15 +1632,19 @@ mod tests { let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, - Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1658,15 +1703,19 @@ mod tests { let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, - Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); @@ -1733,15 +1782,19 @@ mod tests { server_config.alpn_protocols.push(ALPN_HTTP11.to_vec()); - let proxy_server = ProxyServer::new_with_tls_config( - Some(server_config), - Some("127.0.0.1:0"), + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + }, + }), "127.0.0.1:0", target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), false, - Some(certificate_identity_from_chain(&cert_chain).unwrap()), ) .await .unwrap(); diff --git a/src/main.rs b/src/main.rs index e198b3a..ad8ad33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,9 @@ use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; use tracing::level_filters::LevelFilter; use attested_tls_proxy::{ - AttestationGenerator, ProxyClient, ProxyServer, TlsCertAndKey, attested_get::attested_get, - file_server::attested_file_server, get_inner_tls_cert, health_check, - normalize_pem::normalize_private_key_pem_to_pkcs8, + AttestationGenerator, OuterTlsConfig, OuterTlsMode, ProxyClient, ProxyServer, TlsCertAndKey, + attested_get::attested_get, file_server::attested_file_server, get_inner_tls_cert, + health_check, normalize_pem::normalize_private_key_pem_to_pkcs8, }; const GIT_REV: &str = match option_env!("GIT_REV") { @@ -305,8 +305,10 @@ async fn main() -> anyhow::Result<()> { .await?; let server = ProxyServer::new( - tls_cert_and_chain, - Some(outer_listen_addr), + tls_cert_and_chain.map(|cert_and_key| OuterTlsConfig { + listen_addr: outer_listen_addr, + tls: OuterTlsMode::CertAndKey(cert_and_key), + }), inner_listen_addr, target_addr, local_attestation_generator, From 22991befcf33c086230330fa8b017c1b37972fba Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 19 Mar 2026 12:37:24 +0100 Subject: [PATCH 13/44] Improve constructors --- src/lib.rs | 177 +++++++++++++++++++++++------------------------------ 1 file changed, 76 insertions(+), 101 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 64bb2de..9b9f4fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,19 +62,83 @@ pub struct TlsCertAndKey { pub key: PrivateKeyDer<'static>, } +/// Configuration for the optional outer nested-TLS listener. pub struct OuterTlsConfig { + /// The socket address to bind for the outer listener. pub listen_addr: A, + /// How the outer TLS server configuration should be constructed. pub tls: OuterTlsMode, } +/// TLS configuration sources for the outer nested-TLS listener. pub enum OuterTlsMode { + /// Build the outer TLS server config from certificate and key material. CertAndKey(TlsCertAndKey), + /// Use an already-constructed outer TLS server config. Preconfigured { + /// The outer TLS server configuration to expose on the listener. server_config: ServerConfig, + /// The server identity to embed into the inner attested certificate. certificate_name: Option, }, } +impl OuterTlsConfig +where + A: ToSocketAddrs, +{ + fn certificate_name(&self) -> Result, ProxyError> { + match &self.tls { + OuterTlsMode::CertAndKey(cert_and_key) => { + Ok(Some(certificate_identity_from_chain(&cert_and_key.cert_chain)?)) + } + OuterTlsMode::Preconfigured { + certificate_name, .. + } => Ok(certificate_name.clone()), + } + } + + async fn into_listener_and_acceptor( + self, + inner_server_config: Arc, + client_auth: bool, + ) -> Result<(Arc, NestingTlsAcceptor), ProxyError> { + let listen_addr = self.listen_addr; + let outer_server_config = match self.tls { + OuterTlsMode::CertAndKey(cert_and_key) => { + if client_auth { + let root_store = + RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?; + + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_client_cert_verifier(verifier) + .with_single_cert( + cert_and_key.cert_chain.clone(), + cert_and_key.key.clone_key(), + )? + } else { + ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .with_no_client_auth() + .with_single_cert( + cert_and_key.cert_chain.clone(), + cert_and_key.key.clone_key(), + )? + } + } + OuterTlsMode::Preconfigured { server_config, .. } => server_config, + }; + + let outer_listener = Arc::new(TcpListener::bind(listen_addr).await?); + let outer_tls_acceptor = NestingTlsAcceptor::new( + Arc::new(outer_server_config), + inner_server_config, + ); + + Ok((outer_listener, outer_tls_acceptor)) + } +} + /// Adds HTTP 1 and 2 to the list of allowed protocols fn ensure_proxy_alpn_protocols(alpn_protocols: &mut Vec>) { for protocol in [ALPN_H2, ALPN_HTTP11] { @@ -168,95 +232,11 @@ impl ProxyServer { where O: ToSocketAddrs, { - let outer_session = match outer_session { - Some(OuterTlsConfig { - listen_addr, - tls: OuterTlsMode::CertAndKey(cert_and_key), - }) => { - let certificate_name = - Some(certificate_identity_from_chain(&cert_and_key.cert_chain)?); - let server_config = if client_auth { - let root_store = - RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?; - - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_client_cert_verifier(verifier) - .with_single_cert( - cert_and_key.cert_chain.clone(), - cert_and_key.key.clone_key(), - )? - } else { - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_no_client_auth() - .with_single_cert( - cert_and_key.cert_chain.clone(), - cert_and_key.key.clone_key(), - )? - }; - - Some(OuterTlsConfig { - listen_addr, - tls: OuterTlsMode::Preconfigured { - server_config, - certificate_name, - }, - }) - } - Some(OuterTlsConfig { - listen_addr, - tls: - OuterTlsMode::Preconfigured { - server_config, - certificate_name, - }, - }) => Some(OuterTlsConfig { - listen_addr, - tls: OuterTlsMode::Preconfigured { - server_config, - certificate_name, - }, - }), - None => None, - }; - - Self::new_inner( - outer_session, - inner_local, - target, - attestation_generator, - attestation_verifier, - client_auth, - ) - .await - } - - async fn new_inner( - outer_session: Option>, - inner_local: impl ToSocketAddrs, - target: String, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - client_auth: bool, - ) -> Result - where - O: ToSocketAddrs, - { - let (outer_server_config, certificate_name, outer_local) = match outer_session { - Some(OuterTlsConfig { - listen_addr, - tls: - OuterTlsMode::Preconfigured { - server_config, - certificate_name, - }, - }) => (Some(server_config), certificate_name, Some(listen_addr)), - Some(OuterTlsConfig { - listen_addr: _, - tls: OuterTlsMode::CertAndKey(_), - }) => unreachable!("cert/key outer session should be normalized via ProxyServer::new"), - None => (None, None, None), - }; + let certificate_name = outer_session + .as_ref() + .map(OuterTlsConfig::certificate_name) + .transpose()? + .flatten(); let inner_server_config = Arc::new( build_inner_server_config( attestation_generator, @@ -269,19 +249,14 @@ impl ProxyServer { let inner_listener = Arc::new(TcpListener::bind(inner_local).await?); let inner_tls_acceptor = TlsAcceptor::from(inner_server_config.clone()); - let (outer_listener, outer_tls_acceptor) = match (outer_server_config, outer_local) { - (Some(outer_server_config), Some(outer_local)) => { - let outer_listener = Arc::new(TcpListener::bind(outer_local).await?); - let acceptor = NestingTlsAcceptor::new( - Arc::new(outer_server_config), - inner_server_config.clone(), - ); - (Some(outer_listener), Some(acceptor)) - } - (Some(_), None) => { - unreachable!("outer config without outer listener is unrepresentable") + let (outer_listener, outer_tls_acceptor) = match outer_session { + Some(outer_session) => { + let (outer_listener, outer_tls_acceptor) = outer_session + .into_listener_and_acceptor(inner_server_config.clone(), client_auth) + .await?; + (Some(outer_listener), Some(outer_tls_acceptor)) } - (None, _) => (None, None), + None => (None, None), }; Ok(Self { From f82d5632bebbb6f0c63ac7f48d008f6229710a5c Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 19 Mar 2026 13:37:49 +0100 Subject: [PATCH 14/44] Fmt --- src/lib.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9b9f4fd..9c636e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,9 +89,9 @@ where { fn certificate_name(&self) -> Result, ProxyError> { match &self.tls { - OuterTlsMode::CertAndKey(cert_and_key) => { - Ok(Some(certificate_identity_from_chain(&cert_and_key.cert_chain)?)) - } + OuterTlsMode::CertAndKey(cert_and_key) => Ok(Some(certificate_identity_from_chain( + &cert_and_key.cert_chain, + )?)), OuterTlsMode::Preconfigured { certificate_name, .. } => Ok(certificate_name.clone()), @@ -130,10 +130,8 @@ where }; let outer_listener = Arc::new(TcpListener::bind(listen_addr).await?); - let outer_tls_acceptor = NestingTlsAcceptor::new( - Arc::new(outer_server_config), - inner_server_config, - ); + let outer_tls_acceptor = + NestingTlsAcceptor::new(Arc::new(outer_server_config), inner_server_config); Ok((outer_listener, outer_tls_acceptor)) } From cfcb77fba3e0898f44ee2a4080509afae50d8ac4 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 20 Mar 2026 08:32:03 +0100 Subject: [PATCH 15/44] Preconfigured server name should not be optional --- src/attested_get.rs | 2 +- src/file_server.rs | 2 +- src/lib.rs | 61 +++++++++++++++++++-------------------------- 3 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/attested_get.rs b/src/attested_get.rs index 16fd82e..9bd7118 100644 --- a/src/attested_get.rs +++ b/src/attested_get.rs @@ -82,7 +82,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some("localhost".to_string()), + certificate_name: "localhost".to_string(), }, }), "127.0.0.1:0", diff --git a/src/file_server.rs b/src/file_server.rs index d2cfd90..2867972 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -110,7 +110,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some("localhost".to_string()), + certificate_name: "localhost".to_string(), }, }), "127.0.0.1:0", diff --git a/src/lib.rs b/src/lib.rs index 9c636e7..084f70c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,8 +47,6 @@ const SERVER_RECONNECT_MAX_BACKOFF_SECS: u64 = 120; const KEEP_ALIVE_INTERVAL: u64 = 30; const KEEP_ALIVE_TIMEOUT: u64 = 10; -const DEFAULT_INNER_CERTIFICATE_NAME: &str = "localhost"; - type RequestWithResponseSender = ( http::Request, oneshot::Sender>, hyper::Error>>, @@ -79,7 +77,7 @@ pub enum OuterTlsMode { /// The outer TLS server configuration to expose on the listener. server_config: ServerConfig, /// The server identity to embed into the inner attested certificate. - certificate_name: Option, + certificate_name: String, }, } @@ -87,11 +85,11 @@ impl OuterTlsConfig where A: ToSocketAddrs, { - fn certificate_name(&self) -> Result, ProxyError> { + fn certificate_name(&self) -> Result { match &self.tls { - OuterTlsMode::CertAndKey(cert_and_key) => Ok(Some(certificate_identity_from_chain( - &cert_and_key.cert_chain, - )?)), + OuterTlsMode::CertAndKey(cert_and_key) => { + Ok(certificate_identity_from_chain(&cert_and_key.cert_chain)?) + } OuterTlsMode::Preconfigured { certificate_name, .. } => Ok(certificate_name.clone()), @@ -233,8 +231,7 @@ impl ProxyServer { let certificate_name = outer_session .as_ref() .map(OuterTlsConfig::certificate_name) - .transpose()? - .flatten(); + .transpose()?; let inner_server_config = Arc::new( build_inner_server_config( attestation_generator, @@ -565,7 +562,7 @@ impl ProxyClient { let mut inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { let inner_cert_resolver = build_attested_cert_resolver( attestation_generator, - Some(certificate_identity_from_chain(cert_chain)?), + certificate_identity_from_chain(cert_chain)?, ) .await?; ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) @@ -944,15 +941,12 @@ fn certificate_identity_from_chain( async fn build_attested_cert_resolver( attestation_generator: AttestationGenerator, - certificate_name: Option, + certificate_name: String, ) -> Result { - Ok(AttestedCertificateResolver::new( - attestation_generator, - None, - certificate_name.unwrap_or_else(|| DEFAULT_INNER_CERTIFICATE_NAME.to_string()), - vec![], + Ok( + AttestedCertificateResolver::new(attestation_generator, None, certificate_name, vec![]) + .await?, ) - .await?) } async fn build_inner_server_config( @@ -961,8 +955,11 @@ async fn build_inner_server_config( client_auth: bool, certificate_name: Option, ) -> Result { - let inner_cert_resolver = - build_attested_cert_resolver(attestation_generator, certificate_name).await?; + let inner_cert_resolver = build_attested_cert_resolver( + attestation_generator, + certificate_name.unwrap_or_else(|| "localhost".to_string()), + ) + .await?; let mut inner_server_config = if client_auth { let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; @@ -1147,7 +1144,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1254,7 +1251,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1322,9 +1319,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config: server_tls_server_config, - certificate_name: Some( - certificate_identity_from_chain(&server_cert_chain).unwrap(), - ), + certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1382,9 +1377,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some( - certificate_identity_from_chain(&server_cert_chain).unwrap(), - ), + certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1454,9 +1447,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config: server_tls_server_config, - certificate_name: Some( - certificate_identity_from_chain(&server_cert_chain).unwrap(), - ), + certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1516,7 +1507,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1564,7 +1555,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1610,7 +1601,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1681,7 +1672,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), "127.0.0.1:0", @@ -1760,7 +1751,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::Preconfigured { server_config, - certificate_name: Some(certificate_identity_from_chain(&cert_chain).unwrap()), + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), "127.0.0.1:0", From b3ebc9cd6be9378e1f35459b5b13419d71b84190 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 20 Mar 2026 08:38:45 +0100 Subject: [PATCH 16/44] Update CLI documentation --- src/main.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index ad8ad33..0d7eb61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,7 +77,7 @@ enum CliCommand { }, /// Run a proxy server Server { - /// Socket address to listen on for the outer nested-TLS listener + /// Socket address to listen on for the outer nested-TLS listener, if enabled #[arg(long, default_value = "0.0.0.0:443")] outer_listen_addr: SocketAddr, /// Socket address to listen on for the inner-only attested TLS listener @@ -86,13 +86,13 @@ enum CliCommand { /// The hostname:port or ip:port of the target service to forward traffic to target_addr: String, /// Type of attestation to present (dafaults to 'auto' for automatic detection) - /// If other than None, a TLS key and certicate must also be given + /// This configures the inner attested TLS listener and does not require outer TLS certs. #[arg(long, env = "SERVER_ATTESTATION_TYPE")] server_attestation_type: Option, - /// The path to a PEM encoded private key + /// The path to a PEM encoded private key for the optional outer nested-TLS listener #[arg(long, env = "TLS_PRIVATE_KEY_PATH")] tls_private_key_path: Option, - /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. + /// PEM certificate chain for the optional outer nested-TLS listener #[arg(long, env = "TLS_CERTIFICATE_PATH")] tls_certificate_path: Option, /// Whether to use client authentication. If the client is running in a CVM this must be @@ -122,20 +122,20 @@ enum CliCommand { AttestedFileServer { /// Filesystem path to statically serve path_to_serve: PathBuf, - /// Socket address to listen on for the outer nested-TLS listener + /// Socket address to listen on for the outer nested-TLS listener, if enabled #[arg(long, default_value = "0.0.0.0:443")] outer_listen_addr: SocketAddr, /// Socket address to listen on for the inner-only attested TLS listener #[arg(long, default_value = "0.0.0.0:4433")] inner_listen_addr: SocketAddr, /// Type of attestation to present (dafaults to none) - /// If other than None, a TLS key and certicate must also be given + /// This configures the inner attested TLS listener and does not require outer TLS certs. #[arg(long, env = "SERVER_ATTESTATION_TYPE")] server_attestation_type: Option, - /// The path to a PEM encoded private key + /// The path to a PEM encoded private key for the optional outer nested-TLS listener #[arg(long, env = "TLS_PRIVATE_KEY_PATH")] tls_private_key_path: Option, - /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. + /// PEM certificate chain for the optional outer nested-TLS listener #[arg(long, env = "TLS_CERTIFICATE_PATH")] tls_certificate_path: Option, /// URL of the remote dummy attestation service. Only use with --server-attestation-type From a42c81d99c9f09db71e9a85423ea4b3703d167e4 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 20 Mar 2026 09:15:27 +0100 Subject: [PATCH 17/44] Make inner and outer session optional and dont use default ports --- src/attested_get.rs | 2 +- src/file_server.rs | 16 ++--- src/lib.rs | 146 ++++++++++++++++++++++++++++++-------------- src/main.rs | 62 +++++++++++++++---- 4 files changed, 160 insertions(+), 66 deletions(-) diff --git a/src/attested_get.rs b/src/attested_get.rs index 9bd7118..7fc40b9 100644 --- a/src/attested_get.rs +++ b/src/attested_get.rs @@ -85,7 +85,7 @@ mod tests { certificate_name: "localhost".to_string(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), diff --git a/src/file_server.rs b/src/file_server.rs index 2867972..a8dfbd0 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -11,8 +11,8 @@ use tower_http::services::ServeDir; pub async fn attested_file_server( path_to_serve: PathBuf, outer_cert_and_key: Option, - outer_listen_addr: impl ToSocketAddrs, - inner_listen_addr: impl ToSocketAddrs, + outer_listen_addr: Option, + inner_listen_addr: Option, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, @@ -20,10 +20,12 @@ pub async fn attested_file_server( let target_addr = static_file_server(path_to_serve).await?; let server = ProxyServer::new( - outer_cert_and_key.map(|cert_and_key| OuterTlsConfig { - listen_addr: outer_listen_addr, - tls: OuterTlsMode::CertAndKey(cert_and_key), - }), + outer_cert_and_key + .zip(outer_listen_addr) + .map(|(cert_and_key, listen_addr)| OuterTlsConfig { + listen_addr, + tls: OuterTlsMode::CertAndKey(cert_and_key), + }), inner_listen_addr, target_addr.to_string(), attestation_generator, @@ -113,7 +115,7 @@ mod tests { certificate_name: "localhost".to_string(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), diff --git a/src/lib.rs b/src/lib.rs index 084f70c..213ed3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,9 @@ type RequestWithResponseSender = ( oneshot::Sender>, hyper::Error>>, ); +type OuterProxySession = (Arc, NestingTlsAcceptor); +type InnerProxySession = (Arc, TlsAcceptor); + /// TLS Credentials pub struct TlsCertAndKey { /// Der-encoded TLS certificate chain @@ -207,19 +210,17 @@ pub async fn get_inner_tls_cert_with_config( /// A TLS over TCP server which provides an attestation before forwarding traffic to a given target address pub struct ProxyServer { - outer_listener: Option>, - outer_tls_acceptor: Option, - inner_listener: Arc, - inner_tls_acceptor: TlsAcceptor, + outer: Option, + inner: Option, /// The address/hostname of the target service we are proxying to target: String, } impl ProxyServer { /// Start with dual listeners. The outer nested-TLS listener is optional. - pub async fn new( + pub async fn new( outer_session: Option>, - inner_local: impl ToSocketAddrs, + inner_local: Option, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, @@ -227,7 +228,12 @@ impl ProxyServer { ) -> Result where O: ToSocketAddrs, + I: ToSocketAddrs, { + if outer_session.is_none() && inner_local.is_none() { + return Err(ProxyError::NoListenersConfigured); + } + let certificate_name = outer_session .as_ref() .map(OuterTlsConfig::certificate_name) @@ -241,24 +247,28 @@ impl ProxyServer { ) .await?, ); - let inner_listener = Arc::new(TcpListener::bind(inner_local).await?); - let inner_tls_acceptor = TlsAcceptor::from(inner_server_config.clone()); + let inner = match inner_local { + Some(inner_local) => { + let inner_listener = Arc::new(TcpListener::bind(inner_local).await?); + let inner_tls_acceptor = TlsAcceptor::from(inner_server_config.clone()); + Some((inner_listener, inner_tls_acceptor)) + } + None => None, + }; - let (outer_listener, outer_tls_acceptor) = match outer_session { + let outer = match outer_session { Some(outer_session) => { let (outer_listener, outer_tls_acceptor) = outer_session .into_listener_and_acceptor(inner_server_config.clone(), client_auth) .await?; - (Some(outer_listener), Some(outer_tls_acceptor)) + Some((outer_listener, outer_tls_acceptor)) } - None => (None, None), + None => None, }; Ok(Self { - outer_listener, - outer_tls_acceptor, - inner_listener, - inner_tls_acceptor, + outer, + inner, target, }) } @@ -268,13 +278,14 @@ impl ProxyServer { /// Returns the handle for the task handling the connection pub async fn accept(&self) -> Result, ProxyError> { let target = self.target.clone(); - let outer_listener = self.outer_listener.clone(); - let outer_tls_acceptor = self.outer_tls_acceptor.clone(); - let inner_listener = self.inner_listener.clone(); - let inner_tls_acceptor = self.inner_tls_acceptor.clone(); - - let join_handle = match (outer_listener, outer_tls_acceptor) { - (Some(outer_listener), Some(outer_tls_acceptor)) => { + let outer = self.outer.clone(); + let inner = self.inner.clone(); + + let join_handle = match (outer, inner) { + ( + Some((outer_listener, outer_tls_acceptor)), + Some((inner_listener, inner_tls_acceptor)), + ) => { let ((inbound, client_addr), use_outer) = tokio::select! { accepted = outer_listener.accept() => (accepted?, true), accepted = inner_listener.accept() => (accepted?, false), @@ -312,7 +323,7 @@ impl ProxyServer { } }) } - _ => { + (None, Some((inner_listener, inner_tls_acceptor))) => { let (inbound, client_addr) = inner_listener.accept().await?; tokio::spawn(async move { match inner_tls_acceptor.accept(inbound).await { @@ -329,6 +340,24 @@ impl ProxyServer { } }) } + (Some((outer_listener, outer_tls_acceptor)), None) => { + let (inbound, client_addr) = outer_listener.accept().await?; + tokio::spawn(async move { + match outer_tls_acceptor.accept(inbound).await { + Ok(tls_stream) => { + if let Err(err) = + Self::handle_outer_connection(tls_stream, target, client_addr).await + { + warn!("Failed to handle outer connection: {err}"); + } + } + Err(err) => { + warn!("Outer attestation exchange failed: {err}"); + } + } + }) + } + _ => return Err(ProxyError::NoListenersConfigured), }; Ok(join_handle) @@ -336,21 +365,29 @@ impl ProxyServer { /// Helper to get the socket address of the underlying TCP listener pub fn local_addr(&self) -> std::io::Result { - match &self.outer_listener { - Some(listener) => listener.local_addr(), - None => self.inner_listener.local_addr(), + match &self.outer { + Some((listener, _)) => listener.local_addr(), + None => self + .inner + .as_ref() + .map(|(listener, _)| listener) + .ok_or_else(|| std::io::Error::other("no listeners configured"))? + .local_addr(), } } pub fn outer_local_addr(&self) -> std::io::Result> { - self.outer_listener + self.outer .as_ref() - .map(|listener| listener.local_addr()) + .map(|(listener, _)| listener.local_addr()) .transpose() } - pub fn inner_local_addr(&self) -> std::io::Result { - self.inner_listener.local_addr() + pub fn inner_local_addr(&self) -> std::io::Result> { + self.inner + .as_ref() + .map(|(listener, _)| listener.local_addr()) + .transpose() } async fn handle_outer_connection( @@ -909,6 +946,8 @@ pub enum ProxyError { MpscSend, #[error("Client auth must be configured on both the inner and outer TLS sessions")] ClientAuthMisconfigured, + #[error("At least one server listener must be configured")] + NoListenersConfigured, } impl From> for ProxyError { @@ -1039,6 +1078,21 @@ mod tests { assert_eq!(protocols, vec![ALPN_HTTP11.to_vec(), ALPN_H2.to_vec()]); } + #[tokio::test(flavor = "multi_thread")] + async fn proxy_server_requires_at_least_one_listener() { + let result = ProxyServer::new( + None::>, + None::<&str>, + "127.0.0.1:1".to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + false, + ) + .await; + + assert!(matches!(result, Err(ProxyError::NoListenersConfigured))); + } + #[tokio::test(flavor = "multi_thread")] async fn dual_listener_server_reports_expected_addresses() { let target_addr = example_http_service().await; @@ -1054,7 +1108,7 @@ mod tests { listen_addr: "127.0.0.1:0", tls: OuterTlsMode::CertAndKey(tls_cert_and_key), }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -1064,13 +1118,13 @@ mod tests { .unwrap(); let outer_addr = dual_listener_server.outer_local_addr().unwrap().unwrap(); - let inner_addr = dual_listener_server.inner_local_addr().unwrap(); + let inner_addr = dual_listener_server.inner_local_addr().unwrap().unwrap(); assert_eq!(dual_listener_server.local_addr().unwrap(), outer_addr); assert_ne!(outer_addr, inner_addr); let inner_only_server = ProxyServer::new( None::>, - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -1079,7 +1133,7 @@ mod tests { .await .unwrap(); - let inner_only_addr = inner_only_server.inner_local_addr().unwrap(); + let inner_only_addr = inner_only_server.inner_local_addr().unwrap().unwrap(); assert!(inner_only_server.outer_local_addr().unwrap().is_none()); assert_eq!(inner_only_server.local_addr().unwrap(), inner_only_addr); } @@ -1091,7 +1145,7 @@ mod tests { let proxy_server = ProxyServer::new( None::>, - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1100,7 +1154,7 @@ mod tests { .await .unwrap(); - let inner_addr = proxy_server.inner_local_addr().unwrap(); + let inner_addr = proxy_server.inner_local_addr().unwrap().unwrap(); tokio::spawn(async move { proxy_server.accept().await.unwrap(); @@ -1147,7 +1201,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1254,7 +1308,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1322,7 +1376,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), @@ -1380,7 +1434,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), @@ -1450,7 +1504,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::mock(), @@ -1510,7 +1564,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1558,7 +1612,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -1604,7 +1658,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1675,7 +1729,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1754,7 +1808,7 @@ mod tests { certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), }, }), - "127.0.0.1:0", + Some("127.0.0.1:0"), target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), diff --git a/src/main.rs b/src/main.rs index 0d7eb61..a80a54b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,11 +78,11 @@ enum CliCommand { /// Run a proxy server Server { /// Socket address to listen on for the outer nested-TLS listener, if enabled - #[arg(long, default_value = "0.0.0.0:443")] - outer_listen_addr: SocketAddr, + #[arg(long)] + outer_listen_addr: Option, /// Socket address to listen on for the inner-only attested TLS listener - #[arg(long, default_value = "0.0.0.0:4433")] - inner_listen_addr: SocketAddr, + #[arg(long)] + inner_listen_addr: Option, /// The hostname:port or ip:port of the target service to forward traffic to target_addr: String, /// Type of attestation to present (dafaults to 'auto' for automatic detection) @@ -123,11 +123,11 @@ enum CliCommand { /// Filesystem path to statically serve path_to_serve: PathBuf, /// Socket address to listen on for the outer nested-TLS listener, if enabled - #[arg(long, default_value = "0.0.0.0:443")] - outer_listen_addr: SocketAddr, + #[arg(long)] + outer_listen_addr: Option, /// Socket address to listen on for the inner-only attested TLS listener - #[arg(long, default_value = "0.0.0.0:4433")] - inner_listen_addr: SocketAddr, + #[arg(long)] + inner_listen_addr: Option, /// Type of attestation to present (dafaults to none) /// This configures the inner attested TLS listener and does not require outer TLS certs. #[arg(long, env = "SERVER_ATTESTATION_TYPE")] @@ -299,16 +299,23 @@ async fn main() -> anyhow::Result<()> { let tls_cert_and_chain = load_tls_cert_and_key_server(tls_certificate_path, tls_private_key_path)?; + validate_listener_args( + inner_listen_addr, + outer_listen_addr, + tls_cert_and_chain.is_some(), + )?; let local_attestation_generator = AttestationGenerator::new_with_detection(server_attestation_type, dev_dummy_dcap) .await?; let server = ProxyServer::new( - tls_cert_and_chain.map(|cert_and_key| OuterTlsConfig { - listen_addr: outer_listen_addr, - tls: OuterTlsMode::CertAndKey(cert_and_key), - }), + tls_cert_and_chain + .zip(outer_listen_addr) + .map(|(cert_and_key, listen_addr)| OuterTlsConfig { + listen_addr, + tls: OuterTlsMode::CertAndKey(cert_and_key), + }), inner_listen_addr, target_addr, local_attestation_generator, @@ -363,6 +370,11 @@ async fn main() -> anyhow::Result<()> { } => { let tls_cert_and_chain = load_tls_cert_and_key_server(tls_certificate_path, tls_private_key_path)?; + validate_listener_args( + inner_listen_addr, + outer_listen_addr, + tls_cert_and_chain.is_some(), + )?; let server_attestation_type: AttestationType = serde_json::from_value( serde_json::Value::String(server_attestation_type.unwrap_or("none".to_string())), @@ -433,6 +445,32 @@ fn load_tls_cert_and_key_server( } } +fn validate_listener_args( + inner_listen_addr: Option, + outer_listen_addr: Option, + has_outer_tls: bool, +) -> anyhow::Result<()> { + if inner_listen_addr.is_none() && outer_listen_addr.is_none() { + return Err(anyhow!( + "At least one of --inner-listen-addr or --outer-listen-addr must be provided" + )); + } + + if has_outer_tls && outer_listen_addr.is_none() { + return Err(anyhow!( + "--outer-listen-addr is required when TLS certificate and key are provided" + )); + } + + if !has_outer_tls && outer_listen_addr.is_some() { + return Err(anyhow!( + "--outer-listen-addr requires TLS certificate and key" + )); + } + + Ok(()) +} + /// Load TLS details from storage fn load_tls_cert_and_key( cert_chain: PathBuf, From d2d9b7c5561d8a684dc273762479d20e4a009102 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 20 Mar 2026 09:18:05 +0100 Subject: [PATCH 18/44] Small fix for attested file server --- src/file_server.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/file_server.rs b/src/file_server.rs index a8dfbd0..4c5c9bb 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -18,14 +18,19 @@ pub async fn attested_file_server( client_auth: bool, ) -> Result<(), ProxyError> { let target_addr = static_file_server(path_to_serve).await?; + let outer_session = match (outer_cert_and_key, outer_listen_addr) { + (Some(cert_and_key), Some(listen_addr)) => Some(OuterTlsConfig { + listen_addr, + tls: OuterTlsMode::CertAndKey(cert_and_key), + }), + (Some(_), None) | (None, Some(_)) => { + return Err(ProxyError::NoListenersConfigured); + } + (None, None) => None, + }; let server = ProxyServer::new( - outer_cert_and_key - .zip(outer_listen_addr) - .map(|(cert_and_key, listen_addr)| OuterTlsConfig { - listen_addr, - tls: OuterTlsMode::CertAndKey(cert_and_key), - }), + outer_session, inner_listen_addr, target_addr.to_string(), attestation_generator, From 634fb4fc94cb73387b72325665b76ee228302207 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 20 Mar 2026 09:28:10 +0100 Subject: [PATCH 19/44] Tidy, rm unneeded test --- src/lib.rs | 58 ++++-------------------------------------------------- 1 file changed, 4 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 213ed3f..b3b69c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -363,7 +363,7 @@ impl ProxyServer { Ok(join_handle) } - /// Helper to get the socket address of the underlying TCP listener + /// Helper to get the socket address of either underlying TCP listener pub fn local_addr(&self) -> std::io::Result { match &self.outer { Some((listener, _)) => listener.local_addr(), @@ -376,6 +376,7 @@ impl ProxyServer { } } + /// Helper to get the socket address of the underlying outer TCP listener if present pub fn outer_local_addr(&self) -> std::io::Result> { self.outer .as_ref() @@ -383,6 +384,7 @@ impl ProxyServer { .transpose() } + /// Helper to get the socket address of the underlying inner TCP listener if present pub fn inner_local_addr(&self) -> std::io::Result> { self.inner .as_ref() @@ -1239,58 +1241,6 @@ mod tests { assert!(matches!(conn, HttpConnection::Http2 { .. })); } - // #[tokio::test(flavor = "multi_thread")] - // async fn http_proxy_default_constructors_work() { - // let target_addr = example_http_service().await; - // - // let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); - // let server_cert = cert_chain[0].clone(); - // - // let proxy_server = ProxyServer::new( - // TlsCertAndKey { - // cert_chain, - // key: private_key, - // }, - // "127.0.0.1:0", - // target_addr.to_string(), - // AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - // AttestationVerifier::expect_none(), - // false, - // ) - // .await - // .unwrap(); - // - // let proxy_addr = proxy_server.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_server.accept().await.unwrap(); - // }); - // - // let proxy_client = ProxyClient::new( - // None, - // "127.0.0.1:0".to_string(), - // format!("localhost:{}", proxy_addr.port()), - // AttestationGenerator::with_no_attestation(), - // AttestationVerifier::mock(), - // Some(server_cert), - // ) - // .await - // .unwrap(); - // - // let proxy_client_addr = proxy_client.local_addr().unwrap(); - // - // tokio::spawn(async move { - // proxy_client.accept().await.unwrap(); - // }); - // - // let res = reqwest::get(format!("http://{}", proxy_client_addr)) - // .await - // .unwrap(); - // - // let res_body = res.text().await.unwrap(); - // assert_eq!(res_body, "No measurements"); - // } - // Server has mock DCAP, client has no attestation and no client auth #[tokio::test(flavor = "multi_thread")] async fn http_proxy_with_server_attestation() { @@ -1334,7 +1284,7 @@ mod tests { .await .unwrap(); - let proxy_client_addr = proxy_client.local_addr().unwrap(); + let proy_client_addr = proxy_client.local_addr().unwrap(); tokio::spawn(async move { proxy_client.accept().await.unwrap(); From a9bb332e9f77750b3e04b9d46f1751b67d57d3a6 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 20 Mar 2026 09:35:42 +0100 Subject: [PATCH 20/44] Typo --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b3b69c7..88aa200 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1284,7 +1284,7 @@ mod tests { .await .unwrap(); - let proy_client_addr = proxy_client.local_addr().unwrap(); + let proxy_client_addr = proxy_client.local_addr().unwrap(); tokio::spawn(async move { proxy_client.accept().await.unwrap(); From 86eabbaa1abb640ba89f8be9e8d51bb53c84288c Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 23 Mar 2026 10:04:11 +0100 Subject: [PATCH 21/44] Add measurement header injection --- Cargo.lock | 56 ++++++++++--- Cargo.toml | 8 +- src/http_version.rs | 15 ++-- src/lib.rs | 190 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 220 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a169090..45f4bd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,6 +590,38 @@ dependencies = [ "x509-parser 0.18.1", ] +[[package]] +name = "attestation" +version = "0.0.1" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#ed0a0c5125562b45631a2522d1212b4ece143393" +dependencies = [ + "anyhow", + "az-tdx-vtpm", + "base64 0.22.1", + "configfs-tsm", + "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override)", + "hex", + "http", + "num-bigint", + "once_cell", + "openssl", + "parity-scale-codec", + "pem-rfc7468", + "rand_core 0.6.4", + "reqwest", + "rustls-webpki", + "serde", + "serde_json", + "tdx-quote", + "thiserror 2.0.17", + "time", + "tokio", + "tokio-rustls", + "tracing", + "tss-esapi", + "x509-parser 0.18.1", +] + [[package]] name = "attestation-provider-server" version = "0.1.0" @@ -612,7 +644,7 @@ version = "0.0.1" dependencies = [ "alloy-rpc-client", "alloy-transport-http", - "attestation", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", "bytes", "futures-util", "http", @@ -638,10 +670,10 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#ed0a0c5125562b45631a2522d1212b4ece143393" dependencies = [ "anyhow", - "attestation", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", "ra-tls", "rcgen 0.14.7", "rustls", @@ -659,8 +691,8 @@ name = "attested-tls-proxy" version = "1.1.1" dependencies = [ "anyhow", - "attestation", - "attested-tls 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", + "attested-tls 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", "axum", "bytes", "clap", @@ -1072,7 +1104,7 @@ dependencies = [ [[package]] name = "cc-eventlog" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git#f87c97728ad222a3f3553cf0fb756830f7634eb6" dependencies = [ "anyhow", "digest 0.10.7", @@ -1661,7 +1693,7 @@ dependencies = [ [[package]] name = "dstack-attest" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git#f87c97728ad222a3f3553cf0fb756830f7634eb6" dependencies = [ "anyhow", "cc-eventlog", @@ -1687,7 +1719,7 @@ dependencies = [ [[package]] name = "dstack-types" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git#f87c97728ad222a3f3553cf0fb756830f7634eb6" dependencies = [ "parity-scale-codec", "serde", @@ -2976,7 +3008,7 @@ dependencies = [ [[package]] name = "nested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#ed0a0c5125562b45631a2522d1212b4ece143393" dependencies = [ "rustls", "tokio", @@ -3673,7 +3705,7 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "ra-tls" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git#f87c97728ad222a3f3553cf0fb756830f7634eb6" dependencies = [ "anyhow", "bon", @@ -4480,7 +4512,7 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "size-parser" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git#f87c97728ad222a3f3553cf0fb756830f7634eb6" dependencies = [ "anyhow", "serde", @@ -4671,7 +4703,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tdx-attest" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git#f87c97728ad222a3f3553cf0fb756830f7634eb6" dependencies = [ "anyhow", "cc-eventlog", diff --git a/Cargo.toml b/Cargo.toml index c285277..17b5749 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,9 @@ repository = "https://github.com/flashbots/attested-tls-proxy" keywords = ["attested-TLS", "CVM", "TDX"] [dependencies] -attested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } -nested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } +attested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier" } +nested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier" } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier" } tokio = { version = "1.50.0", features = ["full"] } tokio-rustls = { version = "0.26.4", default-features = false } x509-parser = { version = "0.18.0", features = ["verify"] } @@ -47,7 +47,7 @@ pin-project-lite = "0.2.16" [dev-dependencies] tempfile = "3.23.0" tdx-quote = { version = "0.0.5", features = ["mock"] } -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate", features = ["mock"] } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier", features = ["mock"] } tokio = { version = "1.48.0", features = ["full"] } jsonrpsee = { version = "0.26.0", features = ["server"] } diff --git a/src/http_version.rs b/src/http_version.rs index 901df66..d2f0af2 100644 --- a/src/http_version.rs +++ b/src/http_version.rs @@ -1,6 +1,7 @@ //! HTTP Version support and negotiation use hyper::Response; use hyper_util::rt::TokioIo; +use bytes::Bytes; use std::pin::Pin; use std::task::{Context, Poll}; @@ -55,15 +56,18 @@ impl HttpVersion { } } -type Http1Sender = hyper::client::conn::http1::SendRequest; -type Http2Sender = hyper::client::conn::http2::SendRequest; +type Http1Sender = hyper::client::conn::http1::SendRequest>; +type Http2Sender = hyper::client::conn::http2::SendRequest>; type Http1Connection = - hyper::client::conn::http1::Connection, hyper::body::Incoming>; + hyper::client::conn::http1::Connection< + TokioIo, + http_body_util::Full, + >; type Http2Connection = hyper::client::conn::http2::Connection< TokioIo, - hyper::body::Incoming, + http_body_util::Full, crate::TokioExecutor, >; @@ -88,8 +92,9 @@ impl From for HttpSender { impl HttpSender { pub async fn send_request( &mut self, - request: http::Request, + request: http::Request, ) -> Result, hyper::Error> { + let request = request.map(http_body_util::Full::new); match self { Self::Http1(sender) => sender.send_request(request).await, Self::Http2(sender) => sender.send_request(request).await, diff --git a/src/lib.rs b/src/lib.rs index 88aa200..8e44005 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ mod http_version; #[cfg(test)] mod test_helpers; -use attestation::{AttestationError, AttestationVerifier}; +use attestation::{AttestationError, AttestationExchangeMessage, AttestationVerifier}; use attested_tls::{AttestedCertificateResolver, AttestedCertificateVerifier, AttestedTlsError}; use bytes::Bytes; use http::{HeaderMap, HeaderName, HeaderValue}; @@ -36,6 +36,12 @@ use tracing::{debug, error, warn}; use crate::http_version::{ALPN_H2, ALPN_HTTP11, HttpConnection, HttpSender, HttpVersion}; +/// The header name for giving attestation type +const ATTESTATION_TYPE_HEADER: &str = "X-Flashbots-Attestation-Type"; + +/// The header name for giving measurements +const MEASUREMENT_HEADER: &str = "X-Flashbots-Measurement"; + /// The header name for giving the forwarded for IP static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); @@ -48,7 +54,7 @@ const SERVER_RECONNECT_MAX_BACKOFF_SECS: u64 = 120; const KEEP_ALIVE_INTERVAL: u64 = 30; const KEEP_ALIVE_TIMEOUT: u64 = 10; type RequestWithResponseSender = ( - http::Request, + http::Request, oneshot::Sender>, hyper::Error>>, ); @@ -399,8 +405,22 @@ impl ProxyServer { ) -> Result<(), ProxyError> { debug!("[proxy-server] accepted connection"); + // Get attestation from the remote certificate from the inner session, if present. + let attestation = { + let (_io, server_connection) = tls_stream.get_ref(); + + match server_connection.peer_certificates() { + Some(remote_cert_chain) => Some( + AttestedCertificateVerifier::extract_custom_attestation_from_cert( + remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, + )?, + ), + None => None, + } + }; + let http_version = HttpVersion::from_negotiated_protocol_server(&tls_stream); - Self::serve_tls_stream(tls_stream, http_version, target, client_addr).await + Self::serve_tls_stream(tls_stream, http_version, target, client_addr, attestation).await } async fn handle_inner_connection( @@ -410,8 +430,22 @@ impl ProxyServer { ) -> Result<(), ProxyError> { debug!("[proxy-server] accepted inner-only connection"); + // Get attestation from the remote certificate, if present + let attestation = { + let (_io, server_connection) = tls_stream.get_ref(); + + match server_connection.peer_certificates() { + Some(remote_cert_chain) => Some( + AttestedCertificateVerifier::extract_custom_attestation_from_cert( + remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, + )?, + ), + None => None, + } + }; + let http_version = HttpVersion::from_negotiated_protocol_server(&tls_stream); - Self::serve_tls_stream(tls_stream, http_version, target, client_addr).await + Self::serve_tls_stream(tls_stream, http_version, target, client_addr, attestation).await } async fn serve_tls_stream( @@ -419,10 +453,19 @@ impl ProxyServer { http_version: HttpVersion, target: String, client_addr: SocketAddr, + attestation: Option, ) -> Result<(), ProxyError> where IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static, { + let (remote_attestation_type, measurements) = match attestation { + Some(attestation) => ( + Some(attestation.attestation_type), + attestation.get_measurements()?, + ), + None => (None, None), + }; + // Setup a request handler let service = service_fn(move |mut req| { debug!("[proxy-server] Handling request {req:?}"); @@ -447,6 +490,30 @@ impl ProxyServer { update_header(headers, &X_FORWARDED_FOR, &new_x_forwarded_for); + // If we have measurements, from the remote peer, add them to the request header + let measurements = measurements.clone(); + + if let Some(measurements) = measurements { + match measurements.to_header_format() { + Ok(header_value) => { + headers.insert(MEASUREMENT_HEADER, header_value); + } + Err(e) => { + // This error is highly unlikely - that the measurement values fail to + // encode to JSON or fit in an HTTP header + error!("Failed to encode measurement values: {e}"); + } + } + } + + if let Some(remote_attestation_type) = remote_attestation_type { + update_header( + headers, + ATTESTATION_TYPE_HEADER, + remote_attestation_type.as_str(), + ); + } + let target = target.clone(); async move { match Self::handle_http_request(req, target).await { @@ -635,7 +702,7 @@ impl ProxyClient { // Channel for getting incoming requests from the source client let (requests_tx, mut requests_rx) = mpsc::channel::<( - http::Request, + http::Request, oneshot::Sender< Result>, hyper::Error>, >, @@ -648,7 +715,7 @@ impl ProxyClient { let mut first = true; let mut ready_tx = Some(ready_tx); 'reconnect: loop { - let (mut sender, conn) = + let (mut sender, conn, attestation) = // Connect to the proxy server and provide / verify attestation match Self::setup_connection_with_backoff(&target, &nesting_tls_connector, first) .await @@ -678,6 +745,9 @@ impl ProxyClient { let (conn_done_tx, mut conn_done_rx) = tokio::sync::watch::channel::>(None); + let mut remote_attestation_type = attestation.attestation_type; + let mut measurements = attestation.get_measurements().ok().flatten(); + tokio::spawn(async move { let res = conn.await; let _ = conn_done_tx.send(res.err()); @@ -689,17 +759,69 @@ impl ProxyClient { if let Some((req, response_tx)) = incoming_req_option { debug!("[proxy-client] Read incoming request from source client: {req:?}"); // Attempt to forward it to the proxy server - let (response, should_reconnect) = match sender.send_request(req).await { - Ok(resp) => { + let response = loop { + match sender.send_request(req.clone()).await { + Ok(mut resp) => { debug!("[proxy-client] Read response from proxy-server: {resp:?}"); - (Ok(resp.map(|b| b.boxed())), false) - } - Err(e) => { + // If we have measurements from the proxy-server, inject them into the + // response header + let headers = resp.headers_mut(); + if let Some(measurements) = measurements.clone() { + match measurements.to_header_format() { + Ok(header_value) => { + headers.insert(MEASUREMENT_HEADER, header_value); + } + Err(e) => { + // This error is highly unlikely - that the measurement values fail to + // encode to JSON or fit in an HTTP header + error!("Failed to encode measurement values: {e}"); + } + } + } + + update_header( + headers, + ATTESTATION_TYPE_HEADER, + remote_attestation_type.as_str(), + ); + break Ok(resp.map(|b| b.boxed())); + } + Err(e) => { warn!("Failed to send request to proxy-server: {e}"); - let mut resp = Response::new(full(format!("Request failed: {e}"))); - *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; - - (Ok(resp), true) + match Self::setup_connection_with_backoff( + &target, + &nesting_tls_connector, + false, + ) + .await + { + Ok((new_sender, new_conn, new_attestation)) => { + sender = new_sender; + remote_attestation_type = new_attestation.attestation_type; + measurements = new_attestation.get_measurements().ok().flatten(); + + let (new_conn_done_tx, new_conn_done_rx) = + tokio::sync::watch::channel::>(None); + conn_done_rx = new_conn_done_rx; + + tokio::spawn(async move { + let res = new_conn.await; + let _ = new_conn_done_tx.send(res.err()); + }); + + warn!("Reconnected to proxy-server, retrying request"); + continue; + } + Err(reconnect_err) => { + warn!("Reconnect after request failure failed: {reconnect_err}"); + let mut resp = Response::new(full(format!( + "Request failed: {e}" + ))); + *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; + break Ok(resp); + } + } + } } }; @@ -707,12 +829,6 @@ impl ProxyClient { if response_tx.send(response).is_err() { warn!("Failed to forward response to source client, probably they dropped the connection"); } - - if should_reconnect { - // Leave the inner loop and continue on the reconnect loop - warn!("Reconnecting to proxy-server due to failed request"); - break; - } } else { // The request sender was dropped - so no more incoming requests debug!("Request sender dropped - leaving connection handler loop"); @@ -799,7 +915,7 @@ impl ProxyClient { target: &str, nesting_tls_connector: &NestingTlsConnector, should_bail: bool, - ) -> Result<(HttpSender, HttpConnection), ProxyError> { + ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { let mut delay = Duration::from_secs(1); let max_delay = Duration::from_secs(SERVER_RECONNECT_MAX_BACKOFF_SECS); @@ -828,15 +944,29 @@ impl ProxyClient { async fn setup_connection( nesting_tls_connector: &NestingTlsConnector, target: &str, - ) -> Result<(HttpSender, HttpConnection), ProxyError> { + ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { let outbound_stream = tokio::net::TcpStream::connect(target).await?; let domain = server_name_from_host(target)?; let tls_stream = nesting_tls_connector .connect(domain, outbound_stream) .await?; + debug!("[proxy-client] Connected to proxy server"); + // Get attestation from session + let attestation = { + let (_io, server_connection) = tls_stream.get_ref(); + + let remote_cert_chain = server_connection + .peer_certificates() + .ok_or(ProxyError::NoCertificate)?; + + AttestedCertificateVerifier::extract_custom_attestation_from_cert( + remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, + )? + }; + // The attestation exchange is now complete - setup an HTTP client let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); @@ -848,20 +978,20 @@ impl ProxyClient { .keep_alive_interval(Some(Duration::from_secs(KEEP_ALIVE_INTERVAL))) .keep_alive_timeout(Duration::from_secs(KEEP_ALIVE_TIMEOUT)) .keep_alive_while_idle(true) - .handshake::<_, hyper::body::Incoming>(outbound_io) + .handshake::<_, http_body_util::Full>(outbound_io) .await?; (sender.into(), conn.into()) } HttpVersion::Http1 => { let (sender, conn) = hyper::client::conn::http1::Builder::new() - .handshake::<_, hyper::body::Incoming>(outbound_io) + .handshake::<_, http_body_util::Full>(outbound_io) .await?; (sender.into(), conn.into()) } }; - // Return the HTTP client, as well as remote measurements - Ok((sender, conn)) + // Return the HTTP client, as well as remote attestation + Ok((sender, conn, attestation)) } // Handle a request from the source client to the proxy server @@ -869,6 +999,10 @@ impl ProxyClient { req: hyper::Request, requests_tx: mpsc::Sender, ) -> Result>, ProxyError> { + let (parts, body) = req.into_parts(); + let body = body.collect().await?.to_bytes(); + let req = http::Request::from_parts(parts, body); + let (response_tx, response_rx) = oneshot::channel(); requests_tx.send((req, response_tx)).await?; Ok(response_rx.await??) @@ -1230,7 +1364,7 @@ mod tests { let nesting_tls_connector = NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); - let (sender, conn) = ProxyClient::setup_connection( + let (sender, conn, _attestation) = ProxyClient::setup_connection( &nesting_tls_connector, &format!("localhost:{}", proxy_addr.port()), ) From d45a353b90ffa2e713caa5e0258a97825179d9eb Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 23 Mar 2026 10:37:16 +0100 Subject: [PATCH 22/44] Fix re-connection bug --- src/http_version.rs | 11 ++-- src/lib.rs | 131 +++++++++++++++++++++++++------------------- 2 files changed, 79 insertions(+), 63 deletions(-) diff --git a/src/http_version.rs b/src/http_version.rs index d2f0af2..91948c3 100644 --- a/src/http_version.rs +++ b/src/http_version.rs @@ -1,7 +1,7 @@ //! HTTP Version support and negotiation +use bytes::Bytes; use hyper::Response; use hyper_util::rt::TokioIo; -use bytes::Bytes; use std::pin::Pin; use std::task::{Context, Poll}; @@ -59,11 +59,10 @@ impl HttpVersion { type Http1Sender = hyper::client::conn::http1::SendRequest>; type Http2Sender = hyper::client::conn::http2::SendRequest>; -type Http1Connection = - hyper::client::conn::http1::Connection< - TokioIo, - http_body_util::Full, - >; +type Http1Connection = hyper::client::conn::http1::Connection< + TokioIo, + http_body_util::Full, +>; type Http2Connection = hyper::client::conn::http2::Connection< TokioIo, diff --git a/src/lib.rs b/src/lib.rs index 8e44005..acf25f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -410,11 +410,20 @@ impl ProxyServer { let (_io, server_connection) = tls_stream.get_ref(); match server_connection.peer_certificates() { - Some(remote_cert_chain) => Some( - AttestedCertificateVerifier::extract_custom_attestation_from_cert( - remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, - )?, - ), + Some(remote_cert_chain) => remote_cert_chain + .first() + .and_then(|cert| { + match AttestedCertificateVerifier::extract_custom_attestation_from_cert(cert) + { + Ok(attestation) => Some(attestation), + Err(err) => { + warn!( + "Failed to extract remote attestation from inner-session certificate: {err}" + ); + None + } + } + }), None => None, } }; @@ -435,11 +444,15 @@ impl ProxyServer { let (_io, server_connection) = tls_stream.get_ref(); match server_connection.peer_certificates() { - Some(remote_cert_chain) => Some( - AttestedCertificateVerifier::extract_custom_attestation_from_cert( - remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, - )?, - ), + Some(remote_cert_chain) => remote_cert_chain.first().and_then(|cert| { + match AttestedCertificateVerifier::extract_custom_attestation_from_cert(cert) { + Ok(attestation) => Some(attestation), + Err(err) => { + warn!("Failed to extract remote attestation from certificate: {err}"); + None + } + } + }), None => None, } }; @@ -461,7 +474,13 @@ impl ProxyServer { let (remote_attestation_type, measurements) = match attestation { Some(attestation) => ( Some(attestation.attestation_type), - attestation.get_measurements()?, + match attestation.get_measurements() { + Ok(measurements) => measurements, + Err(err) => { + warn!("Failed to extract measurements from peer attestation: {err}"); + None + } + }, ), None => (None, None), }; @@ -715,7 +734,7 @@ impl ProxyClient { let mut first = true; let mut ready_tx = Some(ready_tx); 'reconnect: loop { - let (mut sender, conn, attestation) = + let (mut sender, conn) = // Connect to the proxy server and provide / verify attestation match Self::setup_connection_with_backoff(&target, &nesting_tls_connector, first) .await @@ -745,9 +764,6 @@ impl ProxyClient { let (conn_done_tx, mut conn_done_rx) = tokio::sync::watch::channel::>(None); - let mut remote_attestation_type = attestation.attestation_type; - let mut measurements = attestation.get_measurements().ok().flatten(); - tokio::spawn(async move { let res = conn.await; let _ = conn_done_tx.send(res.err()); @@ -760,45 +776,60 @@ impl ProxyClient { debug!("[proxy-client] Read incoming request from source client: {req:?}"); // Attempt to forward it to the proxy server let response = loop { - match sender.send_request(req.clone()).await { - Ok(mut resp) => { - debug!("[proxy-client] Read response from proxy-server: {resp:?}"); - // If we have measurements from the proxy-server, inject them into the - // response header - let headers = resp.headers_mut(); - if let Some(measurements) = measurements.clone() { - match measurements.to_header_format() { - Ok(header_value) => { - headers.insert(MEASUREMENT_HEADER, header_value); + let send_result = tokio::select! { + result = sender.send_request(req.clone()) => result, + _ = conn_done_rx.changed() => { + warn!("Connection dropped while request was in flight"); + match Self::setup_connection_with_backoff( + &target, + &nesting_tls_connector, + true, + ) + .await + { + Ok((new_sender, new_conn)) => { + sender = new_sender; + + let (new_conn_done_tx, new_conn_done_rx) = + tokio::sync::watch::channel::>(None); + conn_done_rx = new_conn_done_rx; + + tokio::spawn(async move { + let res = new_conn.await; + let _ = new_conn_done_tx.send(res.err()); + }); + + warn!("Reconnected to proxy-server, retrying request"); + continue; } - Err(e) => { - // This error is highly unlikely - that the measurement values fail to - // encode to JSON or fit in an HTTP header - error!("Failed to encode measurement values: {e}"); + Err(reconnect_err) => { + warn!("Reconnect after in-flight drop failed: {reconnect_err}"); + let mut resp = Response::new(full( + "Request failed: connection to proxy-server dropped", + )); + *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; + break Ok(resp); } } } + }; - update_header( - headers, - ATTESTATION_TYPE_HEADER, - remote_attestation_type.as_str(), - ); + match send_result { + Ok(resp) => { + debug!("[proxy-client] Read response from proxy-server: {resp:?}"); break Ok(resp.map(|b| b.boxed())); } Err(e) => { - warn!("Failed to send request to proxy-server: {e}"); + warn!("Failed to send request to proxy-server: {e}"); match Self::setup_connection_with_backoff( &target, &nesting_tls_connector, - false, + true, ) .await { - Ok((new_sender, new_conn, new_attestation)) => { + Ok((new_sender, new_conn)) => { sender = new_sender; - remote_attestation_type = new_attestation.attestation_type; - measurements = new_attestation.get_measurements().ok().flatten(); let (new_conn_done_tx, new_conn_done_rx) = tokio::sync::watch::channel::>(None); @@ -915,7 +946,7 @@ impl ProxyClient { target: &str, nesting_tls_connector: &NestingTlsConnector, should_bail: bool, - ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { + ) -> Result<(HttpSender, HttpConnection), ProxyError> { let mut delay = Duration::from_secs(1); let max_delay = Duration::from_secs(SERVER_RECONNECT_MAX_BACKOFF_SECS); @@ -944,7 +975,7 @@ impl ProxyClient { async fn setup_connection( nesting_tls_connector: &NestingTlsConnector, target: &str, - ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { + ) -> Result<(HttpSender, HttpConnection), ProxyError> { let outbound_stream = tokio::net::TcpStream::connect(target).await?; let domain = server_name_from_host(target)?; @@ -954,19 +985,6 @@ impl ProxyClient { debug!("[proxy-client] Connected to proxy server"); - // Get attestation from session - let attestation = { - let (_io, server_connection) = tls_stream.get_ref(); - - let remote_cert_chain = server_connection - .peer_certificates() - .ok_or(ProxyError::NoCertificate)?; - - AttestedCertificateVerifier::extract_custom_attestation_from_cert( - remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, - )? - }; - // The attestation exchange is now complete - setup an HTTP client let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); @@ -990,8 +1008,7 @@ impl ProxyClient { } }; - // Return the HTTP client, as well as remote attestation - Ok((sender, conn, attestation)) + Ok((sender, conn)) } // Handle a request from the source client to the proxy server @@ -1364,7 +1381,7 @@ mod tests { let nesting_tls_connector = NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); - let (sender, conn, _attestation) = ProxyClient::setup_connection( + let (sender, conn) = ProxyClient::setup_connection( &nesting_tls_connector, &format!("localhost:{}", proxy_addr.port()), ) From 7ed43a9a9e060f704ca9ab75bc3f4ef435110f26 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 23 Mar 2026 11:30:00 +0100 Subject: [PATCH 23/44] Fully restore measurement header injection --- Cargo.lock | 6 +-- src/lib.rs | 123 +++++++++++++++++++++++++++++++++++++++----- src/test_helpers.rs | 15 +++--- 3 files changed, 121 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45f4bd6..f04e874 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,7 +593,7 @@ dependencies = [ [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#ed0a0c5125562b45631a2522d1212b4ece143393" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#2e4273cd93670e705e789555c00d43ca9c1e4af2" dependencies = [ "anyhow", "az-tdx-vtpm", @@ -670,7 +670,7 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#ed0a0c5125562b45631a2522d1212b4ece143393" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#2e4273cd93670e705e789555c00d43ca9c1e4af2" dependencies = [ "anyhow", "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", @@ -3008,7 +3008,7 @@ dependencies = [ [[package]] name = "nested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#ed0a0c5125562b45631a2522d1212b4ece143393" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#2e4273cd93670e705e789555c00d43ca9c1e4af2" dependencies = [ "rustls", "tokio", diff --git a/src/lib.rs b/src/lib.rs index acf25f4..8d60d37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -734,7 +734,7 @@ impl ProxyClient { let mut first = true; let mut ready_tx = Some(ready_tx); 'reconnect: loop { - let (mut sender, conn) = + let (mut sender, conn, attestation) = // Connect to the proxy server and provide / verify attestation match Self::setup_connection_with_backoff(&target, &nesting_tls_connector, first) .await @@ -764,6 +764,9 @@ impl ProxyClient { let (conn_done_tx, mut conn_done_rx) = tokio::sync::watch::channel::>(None); + let mut remote_attestation_type = attestation.attestation_type; + let mut measurements = attestation.get_measurements().ok().flatten(); + tokio::spawn(async move { let res = conn.await; let _ = conn_done_tx.send(res.err()); @@ -787,8 +790,10 @@ impl ProxyClient { ) .await { - Ok((new_sender, new_conn)) => { + Ok((new_sender, new_conn, new_attestation)) => { sender = new_sender; + remote_attestation_type = new_attestation.attestation_type; + measurements = new_attestation.get_measurements().ok().flatten(); let (new_conn_done_tx, new_conn_done_rx) = tokio::sync::watch::channel::>(None); @@ -815,8 +820,26 @@ impl ProxyClient { }; match send_result { - Ok(resp) => { + Ok(mut resp) => { debug!("[proxy-client] Read response from proxy-server: {resp:?}"); + let headers = resp.headers_mut(); + if let Some(measurements) = measurements.clone() { + match measurements.to_header_format() { + Ok(header_value) => { + headers.insert(MEASUREMENT_HEADER, header_value); + } + Err(e) => { + error!("Failed to encode measurement values: {e}"); + } + } + } + + update_header( + headers, + ATTESTATION_TYPE_HEADER, + remote_attestation_type.as_str(), + ); + break Ok(resp.map(|b| b.boxed())); } Err(e) => { @@ -828,8 +851,10 @@ impl ProxyClient { ) .await { - Ok((new_sender, new_conn)) => { + Ok((new_sender, new_conn, new_attestation)) => { sender = new_sender; + remote_attestation_type = new_attestation.attestation_type; + measurements = new_attestation.get_measurements().ok().flatten(); let (new_conn_done_tx, new_conn_done_rx) = tokio::sync::watch::channel::>(None); @@ -946,7 +971,7 @@ impl ProxyClient { target: &str, nesting_tls_connector: &NestingTlsConnector, should_bail: bool, - ) -> Result<(HttpSender, HttpConnection), ProxyError> { + ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { let mut delay = Duration::from_secs(1); let max_delay = Duration::from_secs(SERVER_RECONNECT_MAX_BACKOFF_SECS); @@ -975,7 +1000,7 @@ impl ProxyClient { async fn setup_connection( nesting_tls_connector: &NestingTlsConnector, target: &str, - ) -> Result<(HttpSender, HttpConnection), ProxyError> { + ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { let outbound_stream = tokio::net::TcpStream::connect(target).await?; let domain = server_name_from_host(target)?; @@ -985,6 +1010,18 @@ impl ProxyClient { debug!("[proxy-client] Connected to proxy server"); + let attestation = { + let (_io, server_connection) = tls_stream.get_ref(); + + let remote_cert_chain = server_connection + .peer_certificates() + .ok_or(ProxyError::NoCertificate)?; + + AttestedCertificateVerifier::extract_custom_attestation_from_cert( + remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, + )? + }; + // The attestation exchange is now complete - setup an HTTP client let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); @@ -1008,7 +1045,7 @@ impl ProxyClient { } }; - Ok((sender, conn)) + Ok((sender, conn, attestation)) } // Handle a request from the source client to the proxy server @@ -1207,6 +1244,7 @@ where #[cfg(test)] mod tests { use attestation::{AttestationType, measurements::MeasurementPolicy}; + use std::collections::HashMap; use tokio_rustls::TlsConnector; use super::*; @@ -1215,6 +1253,43 @@ mod tests { generate_tls_config_with_client_auth, init_tracing, }; + fn expected_mock_measurements() -> HashMap { + let zero_measurement = "0".repeat(96); + HashMap::from([ + ("0".to_string(), zero_measurement.clone()), + ("1".to_string(), zero_measurement.clone()), + ("2".to_string(), zero_measurement.clone()), + ("3".to_string(), zero_measurement.clone()), + ("4".to_string(), zero_measurement), + ]) + } + + fn assert_mock_measurements(body: &str) { + let parsed: HashMap = serde_json::from_str(body).unwrap(); + assert_eq!(parsed, expected_mock_measurements()); + } + + fn assert_mock_measurements_header(headers: &http::HeaderMap) { + let body = headers + .get(MEASUREMENT_HEADER) + .and_then(|v| v.to_str().ok()) + .unwrap(); + assert_mock_measurements(body); + } + + fn assert_attestation_type_header(headers: &http::HeaderMap, expected: &str) { + assert_eq!( + headers + .get(ATTESTATION_TYPE_HEADER) + .and_then(|v| v.to_str().ok()), + Some(expected) + ); + } + + fn assert_no_measurements_header(headers: &http::HeaderMap) { + assert!(headers.get(MEASUREMENT_HEADER).is_none()); + } + #[test] fn proxy_alpn_protocols_prefer_http2() { let mut protocols = Vec::new(); @@ -1381,7 +1456,7 @@ mod tests { let nesting_tls_connector = NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); - let (sender, conn) = ProxyClient::setup_connection( + let (sender, conn, _attestation) = ProxyClient::setup_connection( &nesting_tls_connector, &format!("localhost:{}", proxy_addr.port()), ) @@ -1445,6 +1520,9 @@ mod tests { .await .unwrap(); + assert_attestation_type_header(res.headers(), "dcap-tdx"); + assert_mock_measurements_header(res.headers()); + let res_body = res.text().await.unwrap(); assert_eq!(res_body, "No measurements"); } @@ -1513,8 +1591,11 @@ mod tests { .await .unwrap(); + assert_attestation_type_header(res.headers(), "none"); + assert_no_measurements_header(res.headers()); + let res_body = res.text().await.unwrap(); - assert_eq!(res_body, "No measurements"); + assert_mock_measurements(&res_body); } // Server has no attestation, client has mock DCAP but no client auth @@ -1574,7 +1655,11 @@ mod tests { .await .unwrap(); - let _res_body = res.text().await.unwrap(); + assert_attestation_type_header(res.headers(), "none"); + assert_no_measurements_header(res.headers()); + + let res_body = res.text().await.unwrap(); + assert_eq!(res_body, "No measurements"); } // Server has mock DCAP, client has mock DCAP and client auth @@ -1641,12 +1726,16 @@ mod tests { let res = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); - assert_eq!(res.text().await.unwrap(), "No measurements"); + assert_attestation_type_header(res.headers(), "dcap-tdx"); + assert_mock_measurements_header(res.headers()); + assert_mock_measurements(&res.text().await.unwrap()); let res = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); - assert_eq!(res.text().await.unwrap(), "No measurements"); + assert_attestation_type_header(res.headers(), "dcap-tdx"); + assert_mock_measurements_header(res.headers()); + assert_mock_measurements(&res.text().await.unwrap()); } // Server has mock DCAP, client no attestation - just get the server certificate @@ -1874,9 +1963,11 @@ mod tests { proxy_client.accept().await.unwrap(); }); - let _initial_response = reqwest::get(format!("http://{}", proxy_client_addr)) + let initial_response = reqwest::get(format!("http://{}", proxy_client_addr)) .await .unwrap(); + assert_attestation_type_header(initial_response.headers(), "dcap-tdx"); + assert_mock_measurements_header(initial_response.headers()); // Now break the connection connection_breaker_tx.send(()).unwrap(); @@ -1886,6 +1977,9 @@ mod tests { .await .unwrap(); + assert_attestation_type_header(res.headers(), "dcap-tdx"); + assert_mock_measurements_header(res.headers()); + let res_body = res.text().await.unwrap(); assert_eq!(res_body, "No measurements"); } @@ -1945,6 +2039,9 @@ mod tests { .await .unwrap(); + assert_attestation_type_header(res.headers(), "dcap-tdx"); + assert_mock_measurements_header(res.headers()); + let res_body = res.text().await.unwrap(); assert_eq!(res_body, "No measurements"); } diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 431c5f8..b8509c0 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -12,6 +12,8 @@ use tokio_rustls::rustls::{ }; use tracing_subscriber::{EnvFilter, fmt}; +use crate::MEASUREMENT_HEADER; + static INIT: Once = Once::new(); /// Helper to generate a self-signed certificate for testing with a DNS subject name @@ -127,13 +129,12 @@ pub async fn example_http_service() -> SocketAddr { addr } -async fn get_handler(_headers: http::HeaderMap) -> impl IntoResponse { - // headers - // .get(MEASUREMENT_HEADER) - // .and_then(|v| v.to_str().ok()) - // .unwrap_or("No measurements") - // .to_string() - "No measurements".to_string() +async fn get_handler(headers: http::HeaderMap) -> impl IntoResponse { + headers + .get(MEASUREMENT_HEADER) + .and_then(|v| v.to_str().ok()) + .unwrap_or("No measurements") + .to_string() } pub fn init_tracing() { From a0478373522ec99bd95a39d6d901dd1c167988f8 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 23 Mar 2026 12:44:34 +0100 Subject: [PATCH 24/44] Use the same reconnect behavior as before --- src/http_version.rs | 16 +-- src/lib.rs | 268 +++++++++++++++++++++++++++----------------- 2 files changed, 170 insertions(+), 114 deletions(-) diff --git a/src/http_version.rs b/src/http_version.rs index 91948c3..901df66 100644 --- a/src/http_version.rs +++ b/src/http_version.rs @@ -1,5 +1,4 @@ //! HTTP Version support and negotiation -use bytes::Bytes; use hyper::Response; use hyper_util::rt::TokioIo; use std::pin::Pin; @@ -56,17 +55,15 @@ impl HttpVersion { } } -type Http1Sender = hyper::client::conn::http1::SendRequest>; -type Http2Sender = hyper::client::conn::http2::SendRequest>; +type Http1Sender = hyper::client::conn::http1::SendRequest; +type Http2Sender = hyper::client::conn::http2::SendRequest; -type Http1Connection = hyper::client::conn::http1::Connection< - TokioIo, - http_body_util::Full, ->; +type Http1Connection = + hyper::client::conn::http1::Connection, hyper::body::Incoming>; type Http2Connection = hyper::client::conn::http2::Connection< TokioIo, - http_body_util::Full, + hyper::body::Incoming, crate::TokioExecutor, >; @@ -91,9 +88,8 @@ impl From for HttpSender { impl HttpSender { pub async fn send_request( &mut self, - request: http::Request, + request: http::Request, ) -> Result, hyper::Error> { - let request = request.map(http_body_util::Full::new); match self { Self::Http1(sender) => sender.send_request(request).await, Self::Http2(sender) => sender.send_request(request).await, diff --git a/src/lib.rs b/src/lib.rs index 8d60d37..e7e012a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ const SERVER_RECONNECT_MAX_BACKOFF_SECS: u64 = 120; const KEEP_ALIVE_INTERVAL: u64 = 30; const KEEP_ALIVE_TIMEOUT: u64 = 10; type RequestWithResponseSender = ( - http::Request, + http::Request, oneshot::Sender>, hyper::Error>>, ); @@ -721,7 +721,7 @@ impl ProxyClient { // Channel for getting incoming requests from the source client let (requests_tx, mut requests_rx) = mpsc::channel::<( - http::Request, + http::Request, oneshot::Sender< Result>, hyper::Error>, >, @@ -764,8 +764,8 @@ impl ProxyClient { let (conn_done_tx, mut conn_done_rx) = tokio::sync::watch::channel::>(None); - let mut remote_attestation_type = attestation.attestation_type; - let mut measurements = attestation.get_measurements().ok().flatten(); + let remote_attestation_type = attestation.attestation_type; + let measurements = attestation.get_measurements().ok().flatten(); tokio::spawn(async move { let res = conn.await; @@ -778,106 +778,35 @@ impl ProxyClient { if let Some((req, response_tx)) = incoming_req_option { debug!("[proxy-client] Read incoming request from source client: {req:?}"); // Attempt to forward it to the proxy server - let response = loop { - let send_result = tokio::select! { - result = sender.send_request(req.clone()) => result, - _ = conn_done_rx.changed() => { - warn!("Connection dropped while request was in flight"); - match Self::setup_connection_with_backoff( - &target, - &nesting_tls_connector, - true, - ) - .await - { - Ok((new_sender, new_conn, new_attestation)) => { - sender = new_sender; - remote_attestation_type = new_attestation.attestation_type; - measurements = new_attestation.get_measurements().ok().flatten(); - - let (new_conn_done_tx, new_conn_done_rx) = - tokio::sync::watch::channel::>(None); - conn_done_rx = new_conn_done_rx; - - tokio::spawn(async move { - let res = new_conn.await; - let _ = new_conn_done_tx.send(res.err()); - }); - - warn!("Reconnected to proxy-server, retrying request"); - continue; + let (response, should_reconnect) = match sender.send_request(req).await { + Ok(mut resp) => { + debug!("[proxy-client] Read response from proxy-server: {resp:?}"); + let headers = resp.headers_mut(); + if let Some(measurements) = measurements.clone() { + match measurements.to_header_format() { + Ok(header_value) => { + headers.insert(MEASUREMENT_HEADER, header_value); } - Err(reconnect_err) => { - warn!("Reconnect after in-flight drop failed: {reconnect_err}"); - let mut resp = Response::new(full( - "Request failed: connection to proxy-server dropped", - )); - *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; - break Ok(resp); + Err(e) => { + error!("Failed to encode measurement values: {e}"); } } } - }; - - match send_result { - Ok(mut resp) => { - debug!("[proxy-client] Read response from proxy-server: {resp:?}"); - let headers = resp.headers_mut(); - if let Some(measurements) = measurements.clone() { - match measurements.to_header_format() { - Ok(header_value) => { - headers.insert(MEASUREMENT_HEADER, header_value); - } - Err(e) => { - error!("Failed to encode measurement values: {e}"); - } - } - } - update_header( - headers, - ATTESTATION_TYPE_HEADER, - remote_attestation_type.as_str(), - ); + update_header( + headers, + ATTESTATION_TYPE_HEADER, + remote_attestation_type.as_str(), + ); - break Ok(resp.map(|b| b.boxed())); - } - Err(e) => { - warn!("Failed to send request to proxy-server: {e}"); - match Self::setup_connection_with_backoff( - &target, - &nesting_tls_connector, - true, - ) - .await - { - Ok((new_sender, new_conn, new_attestation)) => { - sender = new_sender; - remote_attestation_type = new_attestation.attestation_type; - measurements = new_attestation.get_measurements().ok().flatten(); - - let (new_conn_done_tx, new_conn_done_rx) = - tokio::sync::watch::channel::>(None); - conn_done_rx = new_conn_done_rx; - - tokio::spawn(async move { - let res = new_conn.await; - let _ = new_conn_done_tx.send(res.err()); - }); - - warn!("Reconnected to proxy-server, retrying request"); - continue; - } - Err(reconnect_err) => { - warn!("Reconnect after request failure failed: {reconnect_err}"); - let mut resp = Response::new(full(format!( - "Request failed: {e}" - ))); - *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; - break Ok(resp); - } - } - } + (Ok(resp.map(|b| b.boxed())), false) + } + Err(e) => { + warn!("Failed to send request to proxy-server: {e}"); + let mut resp = Response::new(full(format!("Request failed: {e}"))); + *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; + + (Ok(resp), true) } }; @@ -885,6 +814,12 @@ impl ProxyClient { if response_tx.send(response).is_err() { warn!("Failed to forward response to source client, probably they dropped the connection"); } + + if should_reconnect { + // Leave the inner loop and continue on the reconnect loop + warn!("Reconnecting to proxy-server due to failed request"); + break; + } } else { // The request sender was dropped - so no more incoming requests debug!("Request sender dropped - leaving connection handler loop"); @@ -1033,13 +968,13 @@ impl ProxyClient { .keep_alive_interval(Some(Duration::from_secs(KEEP_ALIVE_INTERVAL))) .keep_alive_timeout(Duration::from_secs(KEEP_ALIVE_TIMEOUT)) .keep_alive_while_idle(true) - .handshake::<_, http_body_util::Full>(outbound_io) + .handshake::<_, hyper::body::Incoming>(outbound_io) .await?; (sender.into(), conn.into()) } HttpVersion::Http1 => { let (sender, conn) = hyper::client::conn::http1::Builder::new() - .handshake::<_, http_body_util::Full>(outbound_io) + .handshake::<_, hyper::body::Incoming>(outbound_io) .await?; (sender.into(), conn.into()) } @@ -1053,10 +988,6 @@ impl ProxyClient { req: hyper::Request, requests_tx: mpsc::Sender, ) -> Result>, ProxyError> { - let (parts, body) = req.into_parts(); - let body = body.collect().await?.to_bytes(); - let req = http::Request::from_parts(parts, body); - let (response_tx, response_rx) = oneshot::channel(); requests_tx.send((req, response_tx)).await?; Ok(response_rx.await??) @@ -1245,6 +1176,10 @@ where mod tests { use attestation::{AttestationType, measurements::MeasurementPolicy}; use std::collections::HashMap; + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }; use tokio_rustls::TlsConnector; use super::*; @@ -1932,6 +1867,7 @@ mod tests { // This is used to trigger a dropped connection to the proxy server let (connection_breaker_tx, connection_breaker_rx) = oneshot::channel(); + let (reconnected_tx, reconnected_rx) = oneshot::channel(); tokio::spawn(async move { let connection_handle = proxy_server.accept().await.unwrap(); @@ -1943,6 +1879,7 @@ mod tests { // Now accept another connection proxy_server.accept().await.unwrap(); + let _ = reconnected_tx.send(()); }); let proxy_client = ProxyClient::new_with_tls_config( @@ -1971,6 +1908,7 @@ mod tests { // Now break the connection connection_breaker_tx.send(()).unwrap(); + reconnected_rx.await.unwrap(); // Make another request let res = reqwest::get(format!("http://{}", proxy_client_addr)) @@ -1984,6 +1922,128 @@ mod tests { assert_eq!(res_body, "No measurements"); } + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_does_not_retry_failed_request() { + init_tracing(); + + let request_count = Arc::new(AtomicUsize::new(0)); + let request_seen = Arc::new(tokio::sync::Notify::new()); + let (release_tx, release_rx) = tokio::sync::watch::channel(false); + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let target_addr = listener.local_addr().unwrap(); + + let app = axum::Router::new().route( + "/", + axum::routing::get({ + let request_count = request_count.clone(); + let request_seen = request_seen.clone(); + let release_rx = release_rx.clone(); + + move || { + let request_count = request_count.clone(); + let request_seen = request_seen.clone(); + let mut release_rx = release_rx.clone(); + + async move { + request_count.fetch_add(1, Ordering::SeqCst); + request_seen.notify_waiters(); + + if !*release_rx.borrow() { + release_rx.changed().await.unwrap(); + } + + "ok" + } + } + }), + ); + + tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); + + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: certificate_identity_from_chain(&cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + false, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + let (connection_breaker_tx, connection_breaker_rx) = oneshot::channel(); + let (reconnected_tx, reconnected_rx) = oneshot::channel(); + + tokio::spawn(async move { + let connection_handle = proxy_server.accept().await.unwrap(); + connection_breaker_rx.await.unwrap(); + connection_handle.abort(); + proxy_server.accept().await.unwrap(); + let _ = reconnected_tx.send(()); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0".to_string(), + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + proxy_client.accept().await.unwrap(); + }); + + let request_url = format!("http://{}", proxy_client_addr); + let failed_request = tokio::spawn(async move { reqwest::get(request_url).await.unwrap() }); + + loop { + if request_count.load(Ordering::SeqCst) > 0 { + break; + } + + request_seen.notified().await; + } + + connection_breaker_tx.send(()).unwrap(); + release_tx.send(true).unwrap(); + + let failed_response = failed_request.await.unwrap(); + assert_eq!(failed_response.status(), hyper::StatusCode::BAD_GATEWAY); + assert_eq!(request_count.load(Ordering::SeqCst), 1); + + reconnected_rx.await.unwrap(); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await + .unwrap(); + + assert_attestation_type_header(res.headers(), "dcap-tdx"); + assert_mock_measurements_header(res.headers()); + assert_eq!(res.text().await.unwrap(), "ok"); + assert_eq!(request_count.load(Ordering::SeqCst), 2); + } + // Use HTTP 1.1 #[tokio::test(flavor = "multi_thread")] async fn http_proxy_with_http1() { From c1ed5f038665cdb20c306d2ad9401598a97f1bae Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 23 Mar 2026 15:13:41 +0100 Subject: [PATCH 25/44] Strip measurements headers to avoid them being spoofed --- src/lib.rs | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e7e012a..25f0e19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -509,6 +509,10 @@ impl ProxyServer { update_header(headers, &X_FORWARDED_FOR, &new_x_forwarded_for); + // Strip any caller-provided attestation metadata before injecting authenticated values. + headers.remove(ATTESTATION_TYPE_HEADER); + headers.remove(MEASUREMENT_HEADER); + // If we have measurements, from the remote peer, add them to the request header let measurements = measurements.clone(); @@ -782,6 +786,8 @@ impl ProxyClient { Ok(mut resp) => { debug!("[proxy-client] Read response from proxy-server: {resp:?}"); let headers = resp.headers_mut(); + headers.remove(MEASUREMENT_HEADER); + if let Some(measurements) = measurements.clone() { match measurements.to_header_format() { Ok(header_value) => { @@ -1225,6 +1231,56 @@ mod tests { assert!(headers.get(MEASUREMENT_HEADER).is_none()); } + /// Test service that echoes attestation-related request headers as JSON. + async fn request_header_echo_service() -> SocketAddr { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + let app = axum::Router::new().route( + "/", + axum::routing::get(|headers: http::HeaderMap| async move { + axum::Json(serde_json::json!({ + "measurement": headers + .get(MEASUREMENT_HEADER) + .and_then(|v| v.to_str().ok()), + "attestation_type": headers + .get(ATTESTATION_TYPE_HEADER) + .and_then(|v| v.to_str().ok()), + })) + }), + ); + + tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + + addr + } + + /// Test service that deliberately returns a spoofed measurement header. + async fn spoofed_response_measurement_service() -> SocketAddr { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + let app = axum::Router::new().route( + "/", + axum::routing::get(|| async move { + let mut response = http::Response::new("ok".to_string()); + response.headers_mut().insert( + MEASUREMENT_HEADER, + HeaderValue::from_static("{\"spoofed\":\"value\"}"), + ); + response + }), + ); + + tokio::spawn(async move { + axum::serve(listener, app).await.unwrap(); + }); + + addr + } + #[test] fn proxy_alpn_protocols_prefer_http2() { let mut protocols = Vec::new(); @@ -1597,6 +1653,68 @@ mod tests { assert_eq!(res_body, "No measurements"); } + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_strips_spoofed_request_attestation_headers() { + let target_addr = request_header_echo_service().await; + + let (server_cert_chain, server_private_key) = + generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = + generate_tls_config(server_cert_chain.clone(), server_private_key); + + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + false, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::Client::new() + .get(format!("http://{}", proxy_client_addr)) + .header(MEASUREMENT_HEADER, "{\"spoofed\":\"request\"}") + .header(ATTESTATION_TYPE_HEADER, "dcap-tdx") + .send() + .await + .unwrap(); + + let echoed: serde_json::Value = res.json().await.unwrap(); + assert!(echoed["measurement"].is_null()); + assert!(echoed["attestation_type"].is_null()); + } + // Server has mock DCAP, client has mock DCAP and client auth #[tokio::test(flavor = "multi_thread")] async fn http_proxy_mutual_attestation() { @@ -2044,6 +2162,64 @@ mod tests { assert_eq!(request_count.load(Ordering::SeqCst), 2); } + #[tokio::test(flavor = "multi_thread")] + async fn http_proxy_strips_spoofed_response_measurement_header() { + let target_addr = spoofed_response_measurement_service().await; + + let (server_cert_chain, server_private_key) = + generate_certificate_chain_for_host("localhost"); + let (server_config, client_config) = + generate_tls_config(server_cert_chain.clone(), server_private_key); + + let proxy_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + tls: OuterTlsMode::Preconfigured { + server_config, + certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + false, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await + .unwrap(); + + assert_attestation_type_header(res.headers(), "none"); + assert_no_measurements_header(res.headers()); + assert_eq!(res.text().await.unwrap(), "ok"); + } + // Use HTTP 1.1 #[tokio::test(flavor = "multi_thread")] async fn http_proxy_with_http1() { From 0a68ec3ace22b6e50f00e1dad20b79c4312afd17 Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 26 Mar 2026 09:25:25 +0100 Subject: [PATCH 26/44] Add mock feature --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index c285277..9d6d341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,9 @@ default = [] # Adds support for Microsoft Azure attestation generation and verification azure = ["attestation/azure"] +# Adds support for mock attestations for testing. Do not enable in production. +mock = ["attestation/mock"] + [package.metadata.deb] maintainer = "Flashbots Team " depends = "$auto" From 43f2fd3052d701d0b643be17f0a6ea5ad2dffa15 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 1 Apr 2026 09:22:03 +0200 Subject: [PATCH 27/44] Bump attested-tls --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a169090..205e55b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,13 +561,13 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#a96ec2d9096f491e652624c53d3df2b1526ef9f2" dependencies = [ "anyhow", "az-tdx-vtpm", "base64 0.22.1", "configfs-tsm", - "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override)", + "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", "hex", "http", "num-bigint", @@ -638,7 +638,7 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#a96ec2d9096f491e652624c53d3df2b1526ef9f2" dependencies = [ "anyhow", "attestation", @@ -1072,7 +1072,7 @@ dependencies = [ [[package]] name = "cc-eventlog" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "digest 0.10.7", @@ -1439,7 +1439,7 @@ dependencies = [ [[package]] name = "dcap-qvl" version = "0.3.12" -source = "git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override#e38818f0b7b600ceadad1ec3efd9e681bcbdc1e5" +source = "git+https://github.com/flashbots/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1#f1dcc65371e941a7b83e3234833d23a1fb232ab1" dependencies = [ "anyhow", "asn1_der", @@ -1661,7 +1661,7 @@ dependencies = [ [[package]] name = "dstack-attest" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "cc-eventlog", @@ -1687,7 +1687,7 @@ dependencies = [ [[package]] name = "dstack-types" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "parity-scale-codec", "serde", @@ -2976,7 +2976,7 @@ dependencies = [ [[package]] name = "nested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#5c109dba74d4f9de58b4b846f480599752dfb1f9" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#a96ec2d9096f491e652624c53d3df2b1526ef9f2" dependencies = [ "rustls", "tokio", @@ -3673,7 +3673,7 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "ra-tls" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "bon", @@ -4480,7 +4480,7 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "size-parser" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "serde", @@ -4671,7 +4671,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tdx-attest" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#4f602dddc0542cd34da031c90ac0b3a560f316ed" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "cc-eventlog", From 7cd7273b4b0a18afbf88799decf64d586649d644 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 1 Apr 2026 09:25:20 +0200 Subject: [PATCH 28/44] Fix field name following updating attested-tls --- src/lib.rs | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 88aa200..0f1d15a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1644,7 +1644,7 @@ mod tests { let attestation_verifier = AttestationVerifier { measurement_policy, pccs_url: None, - log_dcap_quote: false, + dump_dcap_quotes: false, override_azure_outdated_tcb: false, }; diff --git a/src/main.rs b/src/main.rs index a80a54b..dcb23d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -214,7 +214,7 @@ async fn main() -> anyhow::Result<()> { let attestation_verifier = AttestationVerifier { measurement_policy, pccs_url: cli.pccs_url, - log_dcap_quote: cli.log_dcap_quote, + dump_dcap_quotes: cli.log_dcap_quote, override_azure_outdated_tcb: cli.override_azure_outdated_tcb, }; From cd831e8f5e6c7def285f5401657c30f99589ab63 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 1 Apr 2026 09:32:46 +0200 Subject: [PATCH 29/44] Fix field name following updating attested-tls --- attested-tls/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/attested-tls/src/lib.rs b/attested-tls/src/lib.rs index 9b1eafa..bffc774 100644 --- a/attested-tls/src/lib.rs +++ b/attested-tls/src/lib.rs @@ -193,7 +193,7 @@ impl AttestedTlsServer { let remote_attestation_type = remote_attestation_message.attestation_type; // If we expect an attestaion from the client, verify it and get measurements - let measurements = if self.attestation_verifier.has_remote_attestion() { + let measurements = if self.attestation_verifier.has_remote_attestation() { let remote_input_data = compute_report_input(remote_cert_chain.as_deref(), exporter)?; self.attestation_verifier @@ -745,7 +745,7 @@ mod tests { let attestation_verifier = AttestationVerifier { measurement_policy, pccs_url: None, - log_dcap_quote: false, + dump_dcap_quotes: false, override_azure_outdated_tcb: false, }; From 93f8a542ed71993a17177602505fa2d4b6d692b0 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 1 Apr 2026 10:06:09 +0200 Subject: [PATCH 30/44] Fix field name following updating attested-tls --- attestation-provider-server/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attestation-provider-server/src/main.rs b/attestation-provider-server/src/main.rs index b7c5c13..4a91487 100644 --- a/attestation-provider-server/src/main.rs +++ b/attestation-provider-server/src/main.rs @@ -97,7 +97,7 @@ async fn main() -> anyhow::Result<()> { let attestation_verifier = AttestationVerifier { measurement_policy, pccs_url: None, - log_dcap_quote: cli.log_dcap_quote, + dump_dcap_quotes: cli.log_dcap_quote, override_azure_outdated_tcb: false, }; From 1e409971c31e5ddb7fee2eba29bc73dc540c0bd9 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 1 Apr 2026 10:31:21 +0200 Subject: [PATCH 31/44] Fmt --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9544d95..b45c682 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1710,7 +1710,8 @@ mod tests { .await .unwrap(); - let echoed: serde_json::Value = serde_json::from_slice(&res.bytes().await.unwrap()).unwrap(); + let echoed: serde_json::Value = + serde_json::from_slice(&res.bytes().await.unwrap()).unwrap(); assert!(echoed["measurement"].is_null()); assert!(echoed["attestation_type"].is_null()); } From 312ba833c5ec0d1f227376be0bca24e5f91b5ff8 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 1 Apr 2026 10:58:32 +0200 Subject: [PATCH 32/44] Update field name following merged dep --- Cargo.lock | 56 ++++++++++------------------------------------------- src/lib.rs | 2 +- src/main.rs | 2 +- 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e9d5ed..efca042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,13 +593,13 @@ dependencies = [ [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#2e4273cd93670e705e789555c00d43ca9c1e4af2" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#f977f11b3a9275fb29430a2d84a8672ded4bc73b" dependencies = [ "anyhow", "az-tdx-vtpm", "base64 0.22.1", "configfs-tsm", - "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override)", + "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", "hex", "http", "num-bigint", @@ -670,7 +670,7 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#2e4273cd93670e705e789555c00d43ca9c1e4af2" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#f977f11b3a9275fb29430a2d84a8672ded4bc73b" dependencies = [ "anyhow", "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", @@ -1104,7 +1104,7 @@ dependencies = [ [[package]] name = "cc-eventlog" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#01c26b7878890f765b32b2bde5b4a0086e55fb24" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "digest 0.10.7", @@ -1468,42 +1468,6 @@ dependencies = [ "x509-cert", ] -[[package]] -name = "dcap-qvl" -version = "0.3.12" -source = "git+https://github.com/flashbots/dcap-qvl.git?branch=peg%2Fazure-outdated-tcp-override#e38818f0b7b600ceadad1ec3efd9e681bcbdc1e5" -dependencies = [ - "anyhow", - "asn1_der", - "base64 0.22.1", - "borsh", - "byteorder", - "chrono", - "const-oid", - "dcap-qvl-webpki", - "der", - "derive_more 2.1.1", - "futures", - "hex", - "log", - "p256", - "parity-scale-codec", - "pem", - "reqwest", - "ring", - "rustls-pki-types", - "scale-info", - "serde", - "serde-human-bytes", - "serde_json", - "sha2", - "signature", - "tracing", - "urlencoding", - "wasm-bindgen-futures", - "x509-cert", -] - [[package]] name = "dcap-qvl" version = "0.3.12" @@ -1729,7 +1693,7 @@ dependencies = [ [[package]] name = "dstack-attest" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#01c26b7878890f765b32b2bde5b4a0086e55fb24" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "cc-eventlog", @@ -1755,7 +1719,7 @@ dependencies = [ [[package]] name = "dstack-types" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#01c26b7878890f765b32b2bde5b4a0086e55fb24" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "parity-scale-codec", "serde", @@ -3044,7 +3008,7 @@ dependencies = [ [[package]] name = "nested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#2e4273cd93670e705e789555c00d43ca9c1e4af2" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#f977f11b3a9275fb29430a2d84a8672ded4bc73b" dependencies = [ "rustls", "tokio", @@ -3741,7 +3705,7 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "ra-tls" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#01c26b7878890f765b32b2bde5b4a0086e55fb24" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "bon", @@ -4548,7 +4512,7 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "size-parser" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#01c26b7878890f765b32b2bde5b4a0086e55fb24" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "serde", @@ -4739,7 +4703,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tdx-attest" version = "0.5.8" -source = "git+https://github.com/Dstack-TEE/dstack.git#01c26b7878890f765b32b2bde5b4a0086e55fb24" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", "cc-eventlog", diff --git a/src/lib.rs b/src/lib.rs index b45c682..3cecfd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1938,7 +1938,7 @@ mod tests { let attestation_verifier = AttestationVerifier { measurement_policy, pccs_url: None, - log_dcap_quote: false, + dump_dcap_quotes: false, override_azure_outdated_tcb: false, }; diff --git a/src/main.rs b/src/main.rs index a80a54b..dcb23d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -214,7 +214,7 @@ async fn main() -> anyhow::Result<()> { let attestation_verifier = AttestationVerifier { measurement_policy, pccs_url: cli.pccs_url, - log_dcap_quote: cli.log_dcap_quote, + dump_dcap_quotes: cli.log_dcap_quote, override_azure_outdated_tcb: cli.override_azure_outdated_tcb, }; From 7c262f7c87cd946f3034e42a9a3eb58b0989d6e3 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 09:13:43 +0200 Subject: [PATCH 33/44] Update branch of attested-tls following merging paired PR --- Cargo.lock | 72 +++++++++++++++++++++++++++++++++++++++++++++++------ Cargo.toml | 9 ++++--- src/lib.rs | 11 +++++--- src/main.rs | 4 ++- 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efca042..041e588 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,19 +561,20 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#a96ec2d9096f491e652624c53d3df2b1526ef9f2" +source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" dependencies = [ "anyhow", "az-tdx-vtpm", "base64 0.22.1", "configfs-tsm", - "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", + "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", "hex", "http", "num-bigint", "once_cell", "openssl", "parity-scale-codec", + "pccs", "pem-rfc7468", "rand_core 0.6.4", "reqwest", @@ -593,7 +594,7 @@ dependencies = [ [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#f977f11b3a9275fb29430a2d84a8672ded4bc73b" +source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#a96ec2d9096f491e652624c53d3df2b1526ef9f2" dependencies = [ "anyhow", "az-tdx-vtpm", @@ -670,10 +671,10 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#f977f11b3a9275fb29430a2d84a8672ded4bc73b" +source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" dependencies = [ "anyhow", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=main)", "ra-tls", "rcgen 0.14.7", "rustls", @@ -691,8 +692,8 @@ name = "attested-tls-proxy" version = "1.1.1" dependencies = [ "anyhow", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", - "attested-tls 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier)", + "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=main)", + "attested-tls 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=main)", "axum", "bytes", "clap", @@ -703,6 +704,7 @@ dependencies = [ "jsonrpsee", "nested-tls", "p256", + "pccs", "pem-rfc7468", "pin-project-lite", "pkcs1", @@ -1504,6 +1506,42 @@ dependencies = [ "x509-cert", ] +[[package]] +name = "dcap-qvl" +version = "0.3.12" +source = "git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1#f1dcc65371e941a7b83e3234833d23a1fb232ab1" +dependencies = [ + "anyhow", + "asn1_der", + "base64 0.22.1", + "borsh", + "byteorder", + "chrono", + "const-oid", + "dcap-qvl-webpki", + "der", + "derive_more 2.1.1", + "futures", + "hex", + "log", + "p256", + "parity-scale-codec", + "pem", + "reqwest", + "ring", + "rustls-pki-types", + "scale-info", + "serde", + "serde-human-bytes", + "serde_json", + "sha2", + "signature", + "tracing", + "urlencoding", + "wasm-bindgen-futures", + "x509-cert", +] + [[package]] name = "dcap-qvl-webpki" version = "0.103.4+dcap.1" @@ -3008,7 +3046,7 @@ dependencies = [ [[package]] name = "nested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-expose-cert-verifier#f977f11b3a9275fb29430a2d84a8672ded4bc73b" +source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" dependencies = [ "rustls", "tokio", @@ -3365,6 +3403,24 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pccs" +version = "0.0.1" +source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" +dependencies = [ + "anyhow", + "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", + "hex", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.17", + "time", + "tokio", + "tracing", + "x509-parser 0.18.1", +] + [[package]] name = "pem" version = "3.0.6" diff --git a/Cargo.toml b/Cargo.toml index 3f7e0db..5c4ca20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,10 @@ repository = "https://github.com/flashbots/attested-tls-proxy" keywords = ["attested-TLS", "CVM", "TDX"] [dependencies] -attested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier" } -nested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier" } -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier" } +attested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "main" } +nested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "main" } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "main" } +pccs = { git = "https://github.com/flashbots/attested-tls", branch = "main" } tokio = { version = "1.50.0", features = ["full"] } tokio-rustls = { version = "0.26.4", default-features = false } x509-parser = { version = "0.18.0", features = ["verify"] } @@ -47,7 +48,7 @@ pin-project-lite = "0.2.16" [dev-dependencies] tempfile = "3.23.0" tdx-quote = { version = "0.0.5", features = ["mock"] } -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-expose-cert-verifier", features = ["mock"] } +attestation = { git = "https://github.com/flashbots/attested-tls", branch = "main", features = ["mock"] } tokio = { version = "1.48.0", features = ["full"] } jsonrpsee = { version = "0.26.0", features = ["server"] } diff --git a/src/lib.rs b/src/lib.rs index 3cecfd2..19d6e8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1109,10 +1109,14 @@ async fn build_attested_cert_resolver( attestation_generator: AttestationGenerator, certificate_name: String, ) -> Result { - Ok( - AttestedCertificateResolver::new(attestation_generator, None, certificate_name, vec![]) - .await?, + Ok(AttestedCertificateResolver::new( + attestation_generator, + None, + certificate_name, + vec![], + Duration::from_secs(30 * 60), ) + .await?) } async fn build_inner_server_config( @@ -1940,6 +1944,7 @@ mod tests { pccs_url: None, dump_dcap_quotes: false, override_azure_outdated_tcb: false, + internal_pccs: None, }; let proxy_client_result = ProxyClient::new_with_tls_config( diff --git a/src/main.rs b/src/main.rs index dcb23d5..de9d6ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, ensure}; use attestation::{AttestationType, AttestationVerifier, measurements::MeasurementPolicy}; use clap::{Parser, Subcommand}; +use pccs::Pccs; use std::{fs::File, net::SocketAddr, path::PathBuf}; use tokio::io::AsyncWriteExt; use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; @@ -28,7 +29,7 @@ struct Cli { /// If no measurements file is specified, a single attestion type to allow #[arg(long, global = true)] allowed_remote_attestation_type: Option, - /// The URL of a PCCS to use when verifying DCAP attestations. Defaults to Intel PCS. + /// The URL of a PCCS to use when verifying DCAP attestations. Defaults to an internal PCCS. #[arg(long, global = true)] pccs_url: Option, /// Log debug messages @@ -216,6 +217,7 @@ async fn main() -> anyhow::Result<()> { pccs_url: cli.pccs_url, dump_dcap_quotes: cli.log_dcap_quote, override_azure_outdated_tcb: cli.override_azure_outdated_tcb, + internal_pccs: Some(Pccs::new(None)), }; match cli.command { From f51266546a4d80616c30e99fad2992385e240b79 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 09:45:52 +0200 Subject: [PATCH 34/44] Use aws lc as default crypto provider --- Cargo.lock | 51 +++++++++++++++++++++++++ Cargo.toml | 2 +- attestation-provider-server/src/main.rs | 1 + src/lib.rs | 2 +- src/main.rs | 2 + 5 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 041e588..df7488f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -744,6 +744,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.8.6" @@ -1100,6 +1122,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -1182,6 +1206,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "codicon" version = "3.0.0" @@ -2079,6 +2112,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -2705,6 +2744,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.85" @@ -4189,6 +4238,7 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "brotli", "brotli-decompressor", "once_cell", @@ -4224,6 +4274,7 @@ version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", diff --git a/Cargo.toml b/Cargo.toml index 5c4ca20..e8cdf9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ nested-tls = { git = "https://github.com/flashbots/attested-tls", branch = "main attestation = { git = "https://github.com/flashbots/attested-tls", branch = "main" } pccs = { git = "https://github.com/flashbots/attested-tls", branch = "main" } tokio = { version = "1.50.0", features = ["full"] } -tokio-rustls = { version = "0.26.4", default-features = false } +tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws_lc_rs"] } x509-parser = { version = "0.18.0", features = ["verify"] } thiserror = "2.0.17" clap = { version = "4.5.51", features = ["derive", "env"] } diff --git a/attestation-provider-server/src/main.rs b/attestation-provider-server/src/main.rs index 4a91487..661ca2f 100644 --- a/attestation-provider-server/src/main.rs +++ b/attestation-provider-server/src/main.rs @@ -99,6 +99,7 @@ async fn main() -> anyhow::Result<()> { pccs_url: None, dump_dcap_quotes: cli.log_dcap_quote, override_azure_outdated_tcb: false, + internal_pccs: None, }; let attestation_message = diff --git a/src/lib.rs b/src/lib.rs index 19d6e8f..dd9c17f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1363,7 +1363,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn inner_only_listener_negotiates_http2_by_default() { - let _ = rustls::crypto::ring::default_provider().install_default(); + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let target_addr = example_http_service().await; let proxy_server = ProxyServer::new( diff --git a/src/main.rs b/src/main.rs index de9d6ba..f4be61c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -160,6 +160,8 @@ enum CliCommand { #[tokio::main] async fn main() -> anyhow::Result<()> { + let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default(); + let cli = Cli::parse(); ensure!( From 735e368c1e350b87c8d6d3d340cbf2de3f10f964 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 10:01:29 +0200 Subject: [PATCH 35/44] Use aws lc as default crypto provider --- attested-tls/Cargo.toml | 2 +- attested-tls/src/test_helpers.rs | 8 ++++++++ src/test_helpers.rs | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/attested-tls/Cargo.toml b/attested-tls/Cargo.toml index d40e7b6..60f74c0 100644 --- a/attested-tls/Cargo.toml +++ b/attested-tls/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["attested-TLS", "CVM", "TDX"] [dependencies] tokio = { version = "1.48.0", features = ["full"] } -tokio-rustls = { version = "0.26.4", default-features = false } +tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws_lc_rs"] } sha2 = "0.10.9" x509-parser = "0.18.0" thiserror = "2.0.17" diff --git a/attested-tls/src/test_helpers.rs b/attested-tls/src/test_helpers.rs index 9b218a3..2ba933d 100644 --- a/attested-tls/src/test_helpers.rs +++ b/attested-tls/src/test_helpers.rs @@ -10,6 +10,10 @@ use crate::SUPPORTED_ALPN_PROTOCOL_VERSIONS; pub use attestation::measurements::mock_dcap_measurements; +fn install_crypto_provider() { + let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default(); +} + /// Helper to generate a self-signed certificate for testing pub fn generate_certificate_chain( ip: IpAddr, @@ -36,6 +40,8 @@ pub fn generate_tls_config( certificate_chain: Vec>, key: PrivateKeyDer<'static>, ) -> (ServerConfig, ClientConfig) { + install_crypto_provider(); + let supported_protocols: Vec<_> = SUPPORTED_ALPN_PROTOCOL_VERSIONS .into_iter() .map(|p| p.to_vec()) @@ -67,6 +73,8 @@ pub fn generate_tls_config_with_client_auth( bob_certificate_chain: Vec>, bob_key: PrivateKeyDer<'static>, ) -> ((ServerConfig, ClientConfig), (ServerConfig, ClientConfig)) { + install_crypto_provider(); + let supported_protocols: Vec<_> = SUPPORTED_ALPN_PROTOCOL_VERSIONS .into_iter() .map(|p| p.to_vec()) diff --git a/src/test_helpers.rs b/src/test_helpers.rs index b8509c0..9448f7f 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -16,10 +16,20 @@ use crate::MEASUREMENT_HEADER; static INIT: Once = Once::new(); +pub fn install_crypto_provider() { + static CRYPTO_PROVIDER_INIT: Once = Once::new(); + + CRYPTO_PROVIDER_INIT.call_once(|| { + let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default(); + }); +} + /// Helper to generate a self-signed certificate for testing with a DNS subject name pub fn generate_certificate_chain_for_host( host: &str, ) -> (Vec>, PrivateKeyDer<'static>) { + install_crypto_provider(); + let mut params = rcgen::CertificateParams::new(vec![host.to_string()]).unwrap(); params .subject_alt_names @@ -44,6 +54,8 @@ pub fn generate_tls_config( certificate_chain: Vec>, key: PrivateKeyDer<'static>, ) -> (ServerConfig, ClientConfig) { + install_crypto_provider(); + let server_config = ServerConfig::builder() .with_no_client_auth() .with_single_cert(certificate_chain.clone(), key) @@ -66,6 +78,8 @@ pub fn generate_tls_config_with_client_auth( bob_certificate_chain: Vec>, bob_key: PrivateKeyDer<'static>, ) -> ((ServerConfig, ClientConfig), (ServerConfig, ClientConfig)) { + install_crypto_provider(); + let (alice_client_verifier, alice_root_store) = client_verifier_from_remote_cert(bob_certificate_chain[0].clone()); @@ -117,6 +131,8 @@ fn client_verifier_from_remote_cert( /// Simple http server used in tests which returns in the response the measurement header from the /// request pub async fn example_http_service() -> SocketAddr { + install_crypto_provider(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); @@ -138,6 +154,8 @@ async fn get_handler(headers: http::HeaderMap) -> impl IntoResponse { } pub fn init_tracing() { + install_crypto_provider(); + INIT.call_once(|| { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); From 19b685c38223e2ff638ec8f609e54a1c7205b4f5 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 10:27:44 +0200 Subject: [PATCH 36/44] Client can use inner TLS session only --- src/attested_get.rs | 1 + src/file_server.rs | 3 + src/http_version.rs | 26 +++ src/lib.rs | 391 ++++++++++++++++++++++++++++++++++++++------ src/main.rs | 72 +++++++- 5 files changed, 432 insertions(+), 61 deletions(-) diff --git a/src/attested_get.rs b/src/attested_get.rs index 7fc40b9..a2a0ad8 100644 --- a/src/attested_get.rs +++ b/src/attested_get.rs @@ -86,6 +86,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), diff --git a/src/file_server.rs b/src/file_server.rs index 4c5c9bb..93ce669 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -13,6 +13,7 @@ pub async fn attested_file_server( outer_cert_and_key: Option, outer_listen_addr: Option, inner_listen_addr: Option, + inner_certificate_name: Option, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, @@ -32,6 +33,7 @@ pub async fn attested_file_server( let server = ProxyServer::new( outer_session, inner_listen_addr, + inner_certificate_name, target_addr.to_string(), attestation_generator, attestation_verifier, @@ -121,6 +123,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), diff --git a/src/http_version.rs b/src/http_version.rs index 901df66..157a6d9 100644 --- a/src/http_version.rs +++ b/src/http_version.rs @@ -9,6 +9,7 @@ pub const ALPN_HTTP11: &[u8] = b"http/1.1"; type ProxyClientTlsStream = tokio_rustls::client::TlsStream>; +type ProxyClientInnerOnlyTlsStream = tokio_rustls::client::TlsStream; /// Supported HTTP versions #[derive(Debug)] @@ -60,12 +61,21 @@ type Http2Sender = hyper::client::conn::http2::SendRequest, hyper::body::Incoming>; +type Http1InnerOnlyConnection = hyper::client::conn::http1::Connection< + TokioIo, + hyper::body::Incoming, +>; type Http2Connection = hyper::client::conn::http2::Connection< TokioIo, hyper::body::Incoming, crate::TokioExecutor, >; +type Http2InnerOnlyConnection = hyper::client::conn::http2::Connection< + TokioIo, + hyper::body::Incoming, + crate::TokioExecutor, +>; /// A protocol version agnostic HTTP sender pub enum HttpSender { @@ -102,7 +112,9 @@ pin_project_lite::pin_project! { #[project = HttpConnectionProj] pub enum HttpConnection { Http1 { #[pin] inner: Http1Connection }, + Http1InnerOnly { #[pin] inner: Http1InnerOnlyConnection }, Http2 { #[pin] inner: Http2Connection }, + Http2InnerOnly { #[pin] inner: Http2InnerOnlyConnection }, } } @@ -118,13 +130,27 @@ impl From for HttpConnection { } } +impl From for HttpConnection { + fn from(inner: Http1InnerOnlyConnection) -> Self { + Self::Http1InnerOnly { inner } + } +} + +impl From for HttpConnection { + fn from(inner: Http2InnerOnlyConnection) -> Self { + Self::Http2InnerOnly { inner } + } +} + impl Future for HttpConnection { type Output = Result<(), hyper::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { HttpConnectionProj::Http1 { inner } => inner.poll(cx), + HttpConnectionProj::Http1InnerOnly { inner } => inner.poll(cx), HttpConnectionProj::Http2 { inner } => inner.poll(cx), + HttpConnectionProj::Http2InnerOnly { inner } => inner.poll(cx), } } } diff --git a/src/lib.rs b/src/lib.rs index dd9c17f..4507349 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,14 +19,18 @@ use http::{HeaderMap, HeaderName, HeaderValue}; use http_body_util::{BodyExt, combinators::BoxBody}; use hyper::{Response, service::service_fn}; use hyper_util::rt::TokioIo; -use nested_tls::server::NestingTlsStream; -use nested_tls::{client::NestingTlsConnector, server::NestingTlsAcceptor}; -use std::{net::SocketAddr, num::TryFromIntError, sync::Arc, time::Duration}; +use nested_tls::{client::NestingTlsConnector, server::NestingTlsAcceptor, server::NestingTlsStream}; +use std::{ + net::SocketAddr, + num::TryFromIntError, + sync::Arc, + time::Duration, +}; use thiserror::Error; use tokio::io::{self, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{mpsc, oneshot}; -use tokio_rustls::TlsAcceptor; +use tokio_rustls::{TlsAcceptor, TlsConnector}; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; use tokio_rustls::rustls::{ self, ClientConfig, RootCertStore, ServerConfig, @@ -61,6 +65,15 @@ type RequestWithResponseSender = ( type OuterProxySession = (Arc, NestingTlsAcceptor); type InnerProxySession = (Arc, TlsAcceptor); +#[derive(Clone)] +enum ProxyTlsConnector { + Nested(NestingTlsConnector), + InnerOnly(TlsConnector), +} + +impl ProxyTlsConnector { +} + /// TLS Credentials pub struct TlsCertAndKey { /// Der-encoded TLS certificate chain @@ -227,6 +240,7 @@ impl ProxyServer { pub async fn new( outer_session: Option>, inner_local: Option, + inner_certificate_name: Option, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, @@ -243,7 +257,8 @@ impl ProxyServer { let certificate_name = outer_session .as_ref() .map(OuterTlsConfig::certificate_name) - .transpose()?; + .transpose()? + .or(inner_certificate_name); let inner_server_config = Arc::new( build_inner_server_config( attestation_generator, @@ -709,13 +724,72 @@ impl ProxyClient { let nesting_tls_connector = NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); - Self::new_with_inner(address, nesting_tls_connector, &target_name).await + Self::new_with_connector( + address, + ProxyTlsConnector::Nested(nesting_tls_connector), + &target_name, + ) + .await + } + + /// Start a proxy client which connects directly to the server's inner attested TLS listener. + pub async fn new_inner_only( + cert_and_key: Option, + address: impl ToSocketAddrs, + server_name: String, + attestation_generator: AttestationGenerator, + attestation_verifier: AttestationVerifier, + ) -> Result { + Self::new_inner_only_with_tls_config( + address, + server_name, + attestation_generator, + attestation_verifier, + cert_and_key.map(|cert_and_key| cert_and_key.cert_chain), + ) + .await } - /// Create a new proxy client with given [AttestedTlsClient] - pub async fn new_with_inner( + /// Create a new inner-only proxy client with given TLS configuration. + pub async fn new_inner_only_with_tls_config( address: impl ToSocketAddrs, - nesting_tls_connector: NestingTlsConnector, + target_name: String, + attestation_generator: AttestationGenerator, + attestation_verifier: AttestationVerifier, + cert_chain: Option>>, + ) -> Result { + let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; + + let mut inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { + let inner_cert_resolver = build_attested_cert_resolver( + attestation_generator, + certificate_identity_from_chain(cert_chain)?, + ) + .await?; + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .dangerous() + .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) + .with_client_cert_resolver(Arc::new(inner_cert_resolver)) + } else { + ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) + .dangerous() + .with_custom_certificate_verifier(Arc::new(attested_cert_verifier)) + .with_no_client_auth() + }; + ensure_proxy_alpn_protocols(&mut inner_client_config.alpn_protocols); + + Self::new_with_connector( + address, + ProxyTlsConnector::InnerOnly(TlsConnector::from(Arc::new(inner_client_config))), + &target_name, + ) + .await + } + + /// Create a new proxy client with a configured TLS connector. + async fn new_with_connector( + address: impl ToSocketAddrs, + tls_connector: ProxyTlsConnector, target_name: &str, ) -> Result { let listener = TcpListener::bind(address).await?; @@ -740,7 +814,7 @@ impl ProxyClient { 'reconnect: loop { let (mut sender, conn, attestation) = // Connect to the proxy server and provide / verify attestation - match Self::setup_connection_with_backoff(&target, &nesting_tls_connector, first) + match Self::setup_connection_with_backoff(&target, &tls_connector, first) .await { Ok(output) => { @@ -910,14 +984,14 @@ impl ProxyClient { // If it fails retry with a backoff (indefinately) async fn setup_connection_with_backoff( target: &str, - nesting_tls_connector: &NestingTlsConnector, + tls_connector: &ProxyTlsConnector, should_bail: bool, ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { let mut delay = Duration::from_secs(1); let max_delay = Duration::from_secs(SERVER_RECONNECT_MAX_BACKOFF_SECS); loop { - match Self::setup_connection(nesting_tls_connector, target).await { + match Self::setup_connection(tls_connector, target).await { Ok(output) => { return Ok(output); } @@ -939,54 +1013,92 @@ impl ProxyClient { /// Connect to the proxy-server, do TLS handshake and remote attestation async fn setup_connection( - nesting_tls_connector: &NestingTlsConnector, + tls_connector: &ProxyTlsConnector, target: &str, ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { let outbound_stream = tokio::net::TcpStream::connect(target).await?; let domain = server_name_from_host(target)?; - let tls_stream = nesting_tls_connector - .connect(domain, outbound_stream) - .await?; - - debug!("[proxy-client] Connected to proxy server"); - - let attestation = { - let (_io, server_connection) = tls_stream.get_ref(); - - let remote_cert_chain = server_connection - .peer_certificates() - .ok_or(ProxyError::NoCertificate)?; - - AttestedCertificateVerifier::extract_custom_attestation_from_cert( - remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, - )? - }; - - // The attestation exchange is now complete - setup an HTTP client - let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); + match tls_connector { + ProxyTlsConnector::Nested(connector) => { + let tls_stream = connector.connect(domain, outbound_stream).await?; + debug!("[proxy-client] Connected to proxy server"); + + let attestation = Self::extract_peer_attestation(&tls_stream)?; + let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); + + let outbound_io = TokioIo::new(tls_stream); + let (sender, conn) = match http_version { + HttpVersion::Http2 => { + let (sender, conn) = + hyper::client::conn::http2::Builder::new(TokioExecutor) + .timer(hyper_util::rt::tokio::TokioTimer::new()) + .keep_alive_interval(Some(Duration::from_secs( + KEEP_ALIVE_INTERVAL, + ))) + .keep_alive_timeout(Duration::from_secs(KEEP_ALIVE_TIMEOUT)) + .keep_alive_while_idle(true) + .handshake::<_, hyper::body::Incoming>(outbound_io) + .await?; + (sender.into(), conn.into()) + } + HttpVersion::Http1 => { + let (sender, conn) = hyper::client::conn::http1::Builder::new() + .handshake::<_, hyper::body::Incoming>(outbound_io) + .await?; + (sender.into(), conn.into()) + } + }; - let outbound_io = TokioIo::new(tls_stream); - let (sender, conn) = match http_version { - HttpVersion::Http2 => { - let (sender, conn) = hyper::client::conn::http2::Builder::new(TokioExecutor) - .timer(hyper_util::rt::tokio::TokioTimer::new()) - .keep_alive_interval(Some(Duration::from_secs(KEEP_ALIVE_INTERVAL))) - .keep_alive_timeout(Duration::from_secs(KEEP_ALIVE_TIMEOUT)) - .keep_alive_while_idle(true) - .handshake::<_, hyper::body::Incoming>(outbound_io) - .await?; - (sender.into(), conn.into()) + Ok((sender, conn, attestation)) } - HttpVersion::Http1 => { - let (sender, conn) = hyper::client::conn::http1::Builder::new() - .handshake::<_, hyper::body::Incoming>(outbound_io) - .await?; - (sender.into(), conn.into()) + ProxyTlsConnector::InnerOnly(connector) => { + let tls_stream = connector.connect(domain, outbound_stream).await?; + debug!("[proxy-client] Connected to proxy server"); + + let attestation = Self::extract_peer_attestation(&tls_stream)?; + let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); + + let outbound_io = TokioIo::new(tls_stream); + let (sender, conn) = match http_version { + HttpVersion::Http2 => { + let (sender, conn) = + hyper::client::conn::http2::Builder::new(TokioExecutor) + .timer(hyper_util::rt::tokio::TokioTimer::new()) + .keep_alive_interval(Some(Duration::from_secs( + KEEP_ALIVE_INTERVAL, + ))) + .keep_alive_timeout(Duration::from_secs(KEEP_ALIVE_TIMEOUT)) + .keep_alive_while_idle(true) + .handshake::<_, hyper::body::Incoming>(outbound_io) + .await?; + (sender.into(), conn.into()) + } + HttpVersion::Http1 => { + let (sender, conn) = hyper::client::conn::http1::Builder::new() + .handshake::<_, hyper::body::Incoming>(outbound_io) + .await?; + (sender.into(), conn.into()) + } + }; + + Ok((sender, conn, attestation)) } - }; + } + } - Ok((sender, conn, attestation)) + fn extract_peer_attestation( + tls_stream: &tokio_rustls::client::TlsStream, + ) -> Result { + let (_io, server_connection) = tls_stream.get_ref(); + let remote_cert_chain = server_connection + .peer_certificates() + .ok_or(ProxyError::NoCertificate)?; + + AttestedCertificateVerifier::extract_custom_attestation_from_cert( + remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, + ) + .map_err(ProxyError::from) } // Handle a request from the source client to the proxy server @@ -1306,6 +1418,7 @@ mod tests { let result = ProxyServer::new( None::>, None::<&str>, + None, "127.0.0.1:1".to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -1332,6 +1445,7 @@ mod tests { tls: OuterTlsMode::CertAndKey(tls_cert_and_key), }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -1348,6 +1462,7 @@ mod tests { let inner_only_server = ProxyServer::new( None::>, Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -1369,6 +1484,7 @@ mod tests { let proxy_server = ProxyServer::new( None::>, Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1408,6 +1524,164 @@ mod tests { tls_stream.shutdown().await.unwrap(); } + #[tokio::test(flavor = "multi_thread")] + async fn inner_only_client_with_server_attestation() { + let target_addr = example_http_service().await; + + let proxy_server = ProxyServer::new( + None::>, + Some("127.0.0.1:0"), + None, + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + false, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.inner_local_addr().unwrap().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_inner_only_with_tls_config( + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await + .unwrap(); + + assert_attestation_type_header(res.headers(), "dcap-tdx"); + assert_mock_measurements_header(res.headers()); + assert_eq!(res.text().await.unwrap(), "No measurements"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn inner_only_client_supports_mutual_attestation() { + let target_addr = example_http_service().await; + let (client_cert_chain, _client_private_key) = + generate_certificate_chain_for_host("localhost"); + + let proxy_server = ProxyServer::new( + None::>, + Some("127.0.0.1:0"), + None, + target_addr.to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + true, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.inner_local_addr().unwrap().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_inner_only_with_tls_config( + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + Some(client_cert_chain), + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await + .unwrap(); + + assert_attestation_type_header(res.headers(), "none"); + assert_no_measurements_header(res.headers()); + assert_mock_measurements(&res.text().await.unwrap()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn inner_only_server_uses_configured_certificate_name() { + use tokio_rustls::rustls::client::ResolvesClientCert; + + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + + let resolver = build_attested_cert_resolver( + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + "custom.inner.name".to_string(), + ) + .await + .unwrap(); + + let certified_key = resolver.resolve(&[], &[]).unwrap(); + let cert_chain = &certified_key.cert; + + assert_eq!( + hostname_from_cert(cert_chain.first().unwrap()).unwrap(), + "custom.inner.name" + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn nested_client_fails_against_inner_only_listener() { + let target_addr = example_http_service().await; + + let proxy_server = ProxyServer::new( + None::>, + Some("127.0.0.1:0"), + None, + target_addr.to_string(), + AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), + AttestationVerifier::expect_none(), + false, + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.inner_local_addr().unwrap().unwrap(); + + tokio::spawn(async move { + proxy_server.accept().await.unwrap(); + }); + + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let (_server_config, client_config) = generate_tls_config(cert_chain, private_key); + + let err = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0", + format!("localhost:{}", proxy_addr.port()), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + None, + ) + .await + .unwrap_err() + .to_string(); + + assert!(!err.is_empty()); + } + #[tokio::test(flavor = "multi_thread")] async fn http_proxy_negotiates_http2_by_default() { let target_addr = example_http_service().await; @@ -1425,6 +1699,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1452,7 +1727,7 @@ mod tests { NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); let (sender, conn, _attestation) = ProxyClient::setup_connection( - &nesting_tls_connector, + &ProxyTlsConnector::Nested(nesting_tls_connector), &format!("localhost:{}", proxy_addr.port()), ) .await @@ -1480,6 +1755,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1551,6 +1827,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), @@ -1612,6 +1889,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), @@ -1675,6 +1953,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), @@ -1749,6 +2028,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::mock(), @@ -1813,6 +2093,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1861,6 +2142,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -1907,6 +2189,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -1979,6 +2262,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -2099,6 +2383,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -2186,6 +2471,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), @@ -2246,6 +2532,7 @@ mod tests { }, }), Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), diff --git a/src/main.rs b/src/main.rs index f4be61c..f9d9a81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,9 @@ enum CliCommand { /// Socket address to listen on #[arg(short, long, default_value = "0.0.0.0:0", env = "LISTEN_ADDR")] listen_addr: SocketAddr, + /// Connect directly to the server's inner attested TLS listener instead of nested TLS + #[arg(long)] + inner_session_only: bool, /// The hostname:port or ip:port of the proxy server (port defaults to 443) target_addr: String, /// Type of attestation to present (dafaults to 'auto' for automatic detection) @@ -84,6 +87,9 @@ enum CliCommand { /// Socket address to listen on for the inner-only attested TLS listener #[arg(long)] inner_listen_addr: Option, + /// DNS name to embed into the inner attested certificate when no outer listener is used + #[arg(long)] + inner_certificate_name: Option, /// The hostname:port or ip:port of the target service to forward traffic to target_addr: String, /// Type of attestation to present (dafaults to 'auto' for automatic detection) @@ -129,6 +135,9 @@ enum CliCommand { /// Socket address to listen on for the inner-only attested TLS listener #[arg(long)] inner_listen_addr: Option, + /// DNS name to embed into the inner attested certificate when no outer listener is used + #[arg(long)] + inner_certificate_name: Option, /// Type of attestation to present (dafaults to none) /// This configures the inner attested TLS listener and does not require outer TLS certs. #[arg(long, env = "SERVER_ATTESTATION_TYPE")] @@ -225,6 +234,7 @@ async fn main() -> anyhow::Result<()> { match cli.command { CliCommand::Client { listen_addr, + inner_session_only, target_addr, client_attestation_type, tls_private_key_path, @@ -256,6 +266,8 @@ async fn main() -> anyhow::Result<()> { None }; + validate_client_args(inner_session_only, tls_ca_certificate.as_ref())?; + let remote_tls_cert = match tls_ca_certificate { Some(remote_cert_filename) => Some( load_certs_pem(remote_cert_filename)? @@ -270,15 +282,26 @@ async fn main() -> anyhow::Result<()> { AttestationGenerator::new_with_detection(client_attestation_type, dev_dummy_dcap) .await?; - let client = ProxyClient::new( - tls_cert_and_chain, - listen_addr, - target_addr, - client_attestation_generator, - attestation_verifier, - remote_tls_cert, - ) - .await?; + let client = if inner_session_only { + ProxyClient::new_inner_only( + tls_cert_and_chain, + listen_addr, + target_addr, + client_attestation_generator, + attestation_verifier, + ) + .await? + } else { + ProxyClient::new( + tls_cert_and_chain, + listen_addr, + target_addr, + client_attestation_generator, + attestation_verifier, + remote_tls_cert, + ) + .await? + }; loop { if let Err(err) = client.accept().await { @@ -289,6 +312,7 @@ async fn main() -> anyhow::Result<()> { CliCommand::Server { outer_listen_addr, inner_listen_addr, + inner_certificate_name, target_addr, tls_private_key_path, tls_certificate_path, @@ -321,6 +345,7 @@ async fn main() -> anyhow::Result<()> { tls: OuterTlsMode::CertAndKey(cert_and_key), }), inner_listen_addr, + inner_certificate_name, target_addr, local_attestation_generator, attestation_verifier, @@ -367,6 +392,7 @@ async fn main() -> anyhow::Result<()> { path_to_serve, outer_listen_addr, inner_listen_addr, + inner_certificate_name, server_attestation_type, tls_private_key_path, tls_certificate_path, @@ -392,6 +418,7 @@ async fn main() -> anyhow::Result<()> { tls_cert_and_chain, outer_listen_addr, inner_listen_addr, + inner_certificate_name, attestation_generator, attestation_verifier, false, @@ -475,6 +502,19 @@ fn validate_listener_args( Ok(()) } +fn validate_client_args( + inner_session_only: bool, + tls_ca_certificate: Option<&PathBuf>, +) -> anyhow::Result<()> { + if inner_session_only && tls_ca_certificate.is_some() { + return Err(anyhow!( + "--tls-ca-certificate cannot be used with --inner-session-only" + )); + } + + Ok(()) +} + /// Load TLS details from storage fn load_tls_cert_and_key( cert_chain: PathBuf, @@ -508,3 +548,17 @@ fn certs_to_pem_string(certs: &[CertificateDer<'_>]) -> Result Date: Wed, 22 Apr 2026 10:53:15 +0200 Subject: [PATCH 37/44] Additional config struct for file server, because clippy says too many args --- src/file_server.rs | 47 ++++++++++++++++++++++++++++++++++++---------- src/main.rs | 13 +++++++------ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/file_server.rs b/src/file_server.rs index 93ce669..ef9f766 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -7,17 +7,44 @@ use std::{net::SocketAddr, path::PathBuf}; use tokio::net::ToSocketAddrs; use tower_http::services::ServeDir; +/// Configuration for serving a local directory over the attested proxy +pub struct AttestedFileServerConfig { + /// Filesystem path to expose over HTTP + pub path_to_serve: PathBuf, + /// TLS certificate and key for the optional outer listener + pub outer_cert_and_key: Option, + /// Bind address for the optional outer nested-TLS listener + pub outer_listen_addr: Option, + /// Bind address for the optional inner attested-TLS listener + pub inner_listen_addr: Option, + /// Certificate name to embed in the inner attested certificate + pub inner_certificate_name: Option, + /// Attestation generator used by the proxy server + pub attestation_generator: AttestationGenerator, + /// Attestation verifier used for the remote peer + pub attestation_verifier: AttestationVerifier, + /// Whether inner TLS should require client authentication + pub client_auth: bool, +} + /// Setup a static file server serving the given directory, and a proxy server targetting it -pub async fn attested_file_server( - path_to_serve: PathBuf, - outer_cert_and_key: Option, - outer_listen_addr: Option, - inner_listen_addr: Option, - inner_certificate_name: Option, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - client_auth: bool, -) -> Result<(), ProxyError> { +pub async fn attested_file_server( + config: AttestedFileServerConfig, +) -> Result<(), ProxyError> +where + A: ToSocketAddrs, +{ + let AttestedFileServerConfig { + path_to_serve, + outer_cert_and_key, + outer_listen_addr, + inner_listen_addr, + inner_certificate_name, + attestation_generator, + attestation_verifier, + client_auth, + } = config; + let target_addr = static_file_server(path_to_serve).await?; let outer_session = match (outer_cert_and_key, outer_listen_addr) { (Some(cert_and_key), Some(listen_addr)) => Some(OuterTlsConfig { diff --git a/src/main.rs b/src/main.rs index f9d9a81..168fa2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,9 @@ use tracing::level_filters::LevelFilter; use attested_tls_proxy::{ AttestationGenerator, OuterTlsConfig, OuterTlsMode, ProxyClient, ProxyServer, TlsCertAndKey, - attested_get::attested_get, file_server::attested_file_server, get_inner_tls_cert, - health_check, normalize_pem::normalize_private_key_pem_to_pkcs8, + attested_get::attested_get, + file_server::{AttestedFileServerConfig, attested_file_server}, + get_inner_tls_cert, health_check, normalize_pem::normalize_private_key_pem_to_pkcs8, }; const GIT_REV: &str = match option_env!("GIT_REV") { @@ -413,16 +414,16 @@ async fn main() -> anyhow::Result<()> { let attestation_generator = AttestationGenerator::new(server_attestation_type, dev_dummy_dcap)?; - attested_file_server( + attested_file_server(AttestedFileServerConfig { path_to_serve, - tls_cert_and_chain, + outer_cert_and_key: tls_cert_and_chain, outer_listen_addr, inner_listen_addr, inner_certificate_name, attestation_generator, attestation_verifier, - false, - ) + client_auth: false, + }) .await?; } CliCommand::AttestedGet { From 8b70e1411f3b28b84ef0c35e9d20555ab43ee647 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 10:56:11 +0200 Subject: [PATCH 38/44] Fmt --- src/file_server.rs | 4 +--- src/lib.rs | 22 +++++++--------------- src/main.rs | 3 ++- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/file_server.rs b/src/file_server.rs index ef9f766..abfef0b 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -28,9 +28,7 @@ pub struct AttestedFileServerConfig { } /// Setup a static file server serving the given directory, and a proxy server targetting it -pub async fn attested_file_server( - config: AttestedFileServerConfig, -) -> Result<(), ProxyError> +pub async fn attested_file_server(config: AttestedFileServerConfig) -> Result<(), ProxyError> where A: ToSocketAddrs, { diff --git a/src/lib.rs b/src/lib.rs index 4507349..ab82b96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,23 +19,20 @@ use http::{HeaderMap, HeaderName, HeaderValue}; use http_body_util::{BodyExt, combinators::BoxBody}; use hyper::{Response, service::service_fn}; use hyper_util::rt::TokioIo; -use nested_tls::{client::NestingTlsConnector, server::NestingTlsAcceptor, server::NestingTlsStream}; -use std::{ - net::SocketAddr, - num::TryFromIntError, - sync::Arc, - time::Duration, +use nested_tls::{ + client::NestingTlsConnector, server::NestingTlsAcceptor, server::NestingTlsStream, }; +use std::{net::SocketAddr, num::TryFromIntError, sync::Arc, time::Duration}; use thiserror::Error; use tokio::io::{self, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::{mpsc, oneshot}; -use tokio_rustls::{TlsAcceptor, TlsConnector}; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; use tokio_rustls::rustls::{ self, ClientConfig, RootCertStore, ServerConfig, pki_types::{CertificateDer, PrivateKeyDer, ServerName}, }; +use tokio_rustls::{TlsAcceptor, TlsConnector}; use tracing::{debug, error, warn}; use crate::http_version::{ALPN_H2, ALPN_HTTP11, HttpConnection, HttpSender, HttpVersion}; @@ -71,8 +68,7 @@ enum ProxyTlsConnector { InnerOnly(TlsConnector), } -impl ProxyTlsConnector { -} +impl ProxyTlsConnector {} /// TLS Credentials pub struct TlsCertAndKey { @@ -1033,9 +1029,7 @@ impl ProxyClient { let (sender, conn) = hyper::client::conn::http2::Builder::new(TokioExecutor) .timer(hyper_util::rt::tokio::TokioTimer::new()) - .keep_alive_interval(Some(Duration::from_secs( - KEEP_ALIVE_INTERVAL, - ))) + .keep_alive_interval(Some(Duration::from_secs(KEEP_ALIVE_INTERVAL))) .keep_alive_timeout(Duration::from_secs(KEEP_ALIVE_TIMEOUT)) .keep_alive_while_idle(true) .handshake::<_, hyper::body::Incoming>(outbound_io) @@ -1065,9 +1059,7 @@ impl ProxyClient { let (sender, conn) = hyper::client::conn::http2::Builder::new(TokioExecutor) .timer(hyper_util::rt::tokio::TokioTimer::new()) - .keep_alive_interval(Some(Duration::from_secs( - KEEP_ALIVE_INTERVAL, - ))) + .keep_alive_interval(Some(Duration::from_secs(KEEP_ALIVE_INTERVAL))) .keep_alive_timeout(Duration::from_secs(KEEP_ALIVE_TIMEOUT)) .keep_alive_while_idle(true) .handshake::<_, hyper::body::Incoming>(outbound_io) diff --git a/src/main.rs b/src/main.rs index 168fa2c..c489bee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,8 @@ use attested_tls_proxy::{ AttestationGenerator, OuterTlsConfig, OuterTlsMode, ProxyClient, ProxyServer, TlsCertAndKey, attested_get::attested_get, file_server::{AttestedFileServerConfig, attested_file_server}, - get_inner_tls_cert, health_check, normalize_pem::normalize_private_key_pem_to_pkcs8, + get_inner_tls_cert, health_check, + normalize_pem::normalize_private_key_pem_to_pkcs8, }; const GIT_REV: &str = match option_env!("GIT_REV") { From 0881820185acb995c47d7b0a9dbc3d1f9ef1a72f Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 11:17:05 +0200 Subject: [PATCH 39/44] Dont allow TLS cert and key to be given on client with inner session only --- src/lib.rs | 28 +++++++++++++++++++++++++++- src/main.rs | 33 ++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ab82b96..2a11b8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -736,12 +736,16 @@ impl ProxyClient { attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, ) -> Result { + if cert_and_key.is_some() { + return Err(ProxyError::InnerOnlyClientAuthUnsupported); + } + Self::new_inner_only_with_tls_config( address, server_name, attestation_generator, attestation_verifier, - cert_and_key.map(|cert_and_key| cert_and_key.cert_chain), + None, ) .await } @@ -1177,6 +1181,8 @@ pub enum ProxyError { MpscSend, #[error("Client auth must be configured on both the inner and outer TLS sessions")] ClientAuthMisconfigured, + #[error("Inner-session-only mode does not support user-supplied TLS client certificates")] + InnerOnlyClientAuthUnsupported, #[error("At least one server listener must be configured")] NoListenersConfigured, } @@ -1563,6 +1569,26 @@ mod tests { assert_eq!(res.text().await.unwrap(), "No measurements"); } + #[tokio::test(flavor = "multi_thread")] + async fn inner_only_client_rejects_user_supplied_tls_client_cert() { + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let err = ProxyClient::new_inner_only( + Some(TlsCertAndKey { + cert_chain, + key: private_key, + }), + "127.0.0.1:0", + "localhost:443".to_string(), + AttestationGenerator::with_no_attestation(), + AttestationVerifier::expect_none(), + ) + .await + .unwrap_err() + .to_string(); + + assert!(err.contains("Inner-session-only mode")); + } + #[tokio::test(flavor = "multi_thread")] async fn inner_only_client_supports_mutual_attestation() { let target_addr = example_http_service().await; diff --git a/src/main.rs b/src/main.rs index c489bee..f02ab0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,10 +64,10 @@ enum CliCommand { /// If other than None, a TLS key and certicate must also be given #[arg(long, env = "CLIENT_ATTESTATION_TYPE")] client_attestation_type: Option, - /// The path to a PEM encoded private key for client authentication + /// The path to a PEM encoded private key for client authentication in nested-TLS mode #[arg(long, env = "TLS_PRIVATE_KEY_PATH")] tls_private_key_path: Option, - /// The path to a PEM encoded certificate chain for client authentication + /// The path to a PEM encoded certificate chain for client authentication in nested-TLS mode #[arg(long, env = "TLS_CERTIFICATE_PATH")] tls_certificate_path: Option, /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. @@ -254,6 +254,13 @@ async fn main() -> anyhow::Result<()> { health_check::server(listen_addr_healthcheck).await?; } + validate_client_args( + inner_session_only, + tls_private_key_path.as_ref(), + tls_certificate_path.as_ref(), + tls_ca_certificate.as_ref(), + )?; + let tls_cert_and_chain = if let Some(private_key) = tls_private_key_path { Some(load_tls_cert_and_key( tls_certificate_path @@ -268,8 +275,6 @@ async fn main() -> anyhow::Result<()> { None }; - validate_client_args(inner_session_only, tls_ca_certificate.as_ref())?; - let remote_tls_cert = match tls_ca_certificate { Some(remote_cert_filename) => Some( load_certs_pem(remote_cert_filename)? @@ -506,6 +511,8 @@ fn validate_listener_args( fn validate_client_args( inner_session_only: bool, + tls_private_key_path: Option<&PathBuf>, + tls_certificate_path: Option<&PathBuf>, tls_ca_certificate: Option<&PathBuf>, ) -> anyhow::Result<()> { if inner_session_only && tls_ca_certificate.is_some() { @@ -514,6 +521,12 @@ fn validate_client_args( )); } + if inner_session_only && (tls_private_key_path.is_some() || tls_certificate_path.is_some()) { + return Err(anyhow!( + "--tls-private-key-path and --tls-certificate-path are not supported with --inner-session-only" + )); + } + Ok(()) } @@ -558,9 +571,19 @@ mod tests { #[test] fn client_rejects_tls_ca_certificate_in_inner_only_mode() { let cert_path = PathBuf::from("ca.pem"); - let err = validate_client_args(true, Some(&cert_path)) + let err = validate_client_args(true, None, None, Some(&cert_path)) .unwrap_err() .to_string(); assert!(err.contains("--tls-ca-certificate")); } + + #[test] + fn client_rejects_tls_client_auth_in_inner_only_mode() { + let cert_path = PathBuf::from("client.crt"); + let key_path = PathBuf::from("client.key"); + let err = validate_client_args(true, Some(&key_path), Some(&cert_path), None) + .unwrap_err() + .to_string(); + assert!(err.contains("--tls-private-key-path")); + } } From dc90aa8cf9849aaf82676dea358771f3f5385abc Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 11:52:43 +0200 Subject: [PATCH 40/44] Use constant for cert validity duration --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2a11b8f..7b0e3db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,9 @@ static X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip"); /// The longest time in seconds to wait between reconnection attempts const SERVER_RECONNECT_MAX_BACKOFF_SECS: u64 = 120; +/// Validity period for generated attested TLS certificates +const ATTESTED_CERTIFICATE_VALIDITY_SECS: u64 = 30 * 60; + const KEEP_ALIVE_INTERVAL: u64 = 30; const KEEP_ALIVE_TIMEOUT: u64 = 10; type RequestWithResponseSender = ( @@ -1224,7 +1227,7 @@ async fn build_attested_cert_resolver( None, certificate_name, vec![], - Duration::from_secs(30 * 60), + Duration::from_secs(ATTESTED_CERTIFICATE_VALIDITY_SECS), ) .await?) } From dde77bf9e05ffb66e626095898263261801ac749 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 27 Apr 2026 12:35:12 +0200 Subject: [PATCH 41/44] Rm old attested-tls protocol to avoid confusion --- Cargo.lock | 1358 ++---------------------------- Cargo.toml | 2 +- attested-tls/Cargo.toml | 70 -- attested-tls/README.md | 145 ---- attested-tls/src/attested_rpc.rs | 367 -------- attested-tls/src/lib.rs | 833 ------------------ attested-tls/src/test_helpers.rs | 136 --- attested-tls/src/websockets.rs | 176 ---- 8 files changed, 77 insertions(+), 3010 deletions(-) delete mode 100644 attested-tls/Cargo.toml delete mode 100644 attested-tls/README.md delete mode 100644 attested-tls/src/attested_rpc.rs delete mode 100644 attested-tls/src/lib.rs delete mode 100644 attested-tls/src/test_helpers.rs delete mode 100644 attested-tls/src/websockets.rs diff --git a/Cargo.lock b/Cargo.lock index df7488f..cd73d6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,185 +32,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "alloy-json-rpc" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57586581f2008933241d16c3e3f633168b3a5d2738c5c42ea5246ec5e0ef17a" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "http", - "serde", - "serde_json", - "thiserror 2.0.17", - "tracing", -] - -[[package]] -name = "alloy-primitives" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b1483f8c2562bf35f0270b697d5b5fe8170464e935bd855a4c5eaf6f89b354" -dependencies = [ - "alloy-rlp", - "bytes", - "cfg-if", - "const-hex", - "derive_more 2.1.1", - "foldhash", - "hashbrown", - "indexmap", - "itoa", - "k256", - "keccak-asm", - "paste", - "proptest", - "rand 0.9.2", - "rapidhash", - "ruint", - "rustc-hash", - "serde", - "sha3", -] - -[[package]] -name = "alloy-rlp" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" -dependencies = [ - "arrayvec", - "bytes", -] - -[[package]] -name = "alloy-rpc-client" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91577235d341a1bdbee30a463655d08504408a4d51e9f72edbfc5a622829f402" -dependencies = [ - "alloy-json-rpc", - "alloy-primitives", - "alloy-transport", - "alloy-transport-http", - "futures", - "pin-project", - "reqwest", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tower", - "tracing", - "url", - "wasmtimer", -] - -[[package]] -name = "alloy-sol-macro" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4b64c8146291f750c3f391dff2dd40cf896f7e2b253417a31e342aa7265baa" -dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.108", -] - -[[package]] -name = "alloy-sol-macro-expander" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9df903674682f9bae8d43fdea535ab48df2d6a8cb5104ca29c58ada22ef67b3" -dependencies = [ - "alloy-sol-macro-input", - "const-hex", - "heck", - "indexmap", - "proc-macro-error2", - "proc-macro2", - "quote", - "sha3", - "syn 2.0.108", - "syn-solidity", -] - -[[package]] -name = "alloy-sol-macro-input" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737b8a959f527a86e07c44656db237024a32ae9b97d449f788262a547e8aa136" -dependencies = [ - "const-hex", - "dunce", - "heck", - "macro-string", - "proc-macro2", - "quote", - "syn 2.0.108", - "syn-solidity", -] - -[[package]] -name = "alloy-sol-types" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf7effe4ab0a4f52c865959f790036e61a7983f68b13b75d7fbcedf20b753ce" -dependencies = [ - "alloy-primitives", - "alloy-sol-macro", -] - -[[package]] -name = "alloy-transport" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a03bb3f02b9a7ab23dacd1822fa7f69aa5c8eefcdcf57fad085e0b8d76fb4334" -dependencies = [ - "alloy-json-rpc", - "auto_impl", - "base64 0.22.1", - "derive_more 2.1.1", - "futures", - "futures-utils-wasm", - "parking_lot", - "serde", - "serde_json", - "thiserror 2.0.17", - "tokio", - "tower", - "tracing", - "url", - "wasmtimer", -] - -[[package]] -name = "alloy-transport-http" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce599598ef8ebe067f3627509358d9faaa1ef94f77f834a7783cd44209ef55c" -dependencies = [ - "alloy-json-rpc", - "alloy-transport", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "itertools 0.14.0", - "opentelemetry", - "opentelemetry-http", - "reqwest", - "serde_json", - "tower", - "tracing", - "tracing-opentelemetry", - "url", -] - [[package]] name = "anstream" version = "0.6.21" @@ -267,195 +88,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "ark-ff" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" -dependencies = [ - "ark-ff-asm 0.3.0", - "ark-ff-macros 0.3.0", - "ark-serialize 0.3.0", - "ark-std 0.3.0", - "derivative", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.3.3", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" -dependencies = [ - "ark-ff-asm 0.4.2", - "ark-ff-macros 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "derivative", - "digest 0.10.7", - "itertools 0.10.5", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.4.1", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" -dependencies = [ - "ark-ff-asm 0.5.0", - "ark-ff-macros 0.5.0", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "arrayvec", - "digest 0.10.7", - "educe", - "itertools 0.13.0", - "num-bigint", - "num-traits", - "paste", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-asm" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-asm" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" -dependencies = [ - "quote", - "syn 2.0.108", -] - -[[package]] -name = "ark-ff-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" -dependencies = [ - "num-bigint", - "num-traits", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 2.0.108", -] - -[[package]] -name = "ark-serialize" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" -dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", -] - -[[package]] -name = "ark-serialize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" -dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint", -] - -[[package]] -name = "ark-serialize" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" -dependencies = [ - "ark-std 0.5.0", - "arrayvec", - "digest 0.10.7", - "num-bigint", -] - -[[package]] -name = "ark-std" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "ark-std" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "ark-std" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - [[package]] name = "arrayref" version = "0.3.9" @@ -508,7 +140,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "synstructure", ] @@ -520,7 +152,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "synstructure", ] @@ -532,7 +164,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -549,7 +181,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -591,38 +223,6 @@ dependencies = [ "x509-parser 0.18.1", ] -[[package]] -name = "attestation" -version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate#a96ec2d9096f491e652624c53d3df2b1526ef9f2" -dependencies = [ - "anyhow", - "az-tdx-vtpm", - "base64 0.22.1", - "configfs-tsm", - "dcap-qvl 0.3.12 (git+https://github.com/flashbots/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", - "hex", - "http", - "num-bigint", - "once_cell", - "openssl", - "parity-scale-codec", - "pem-rfc7468", - "rand_core 0.6.4", - "reqwest", - "rustls-webpki", - "serde", - "serde_json", - "tdx-quote", - "thiserror 2.0.17", - "time", - "tokio", - "tokio-rustls", - "tracing", - "tss-esapi", - "x509-parser 0.18.1", -] - [[package]] name = "attestation-provider-server" version = "0.1.0" @@ -639,42 +239,13 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "attested-tls" -version = "0.0.1" -dependencies = [ - "alloy-rpc-client", - "alloy-transport-http", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=peg%2Fattested-tls-crate)", - "bytes", - "futures-util", - "http", - "http-body-util", - "hyper", - "hyper-util", - "parity-scale-codec", - "rcgen 0.14.7", - "serde_json", - "sha2", - "tempfile", - "thiserror 2.0.17", - "tokio", - "tokio-rustls", - "tokio-tungstenite", - "tower-service", - "tracing", - "url", - "webpki-roots", - "x509-parser 0.18.1", -] - [[package]] name = "attested-tls" version = "0.0.1" source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" dependencies = [ "anyhow", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=main)", + "attestation", "ra-tls", "rcgen 0.14.7", "rustls", @@ -692,8 +263,8 @@ name = "attested-tls-proxy" version = "1.1.1" dependencies = [ "anyhow", - "attestation 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=main)", - "attested-tls 0.0.1 (git+https://github.com/flashbots/attested-tls?branch=main)", + "attestation", + "attested-tls", "axum", "bytes", "clap", @@ -727,17 +298,6 @@ dependencies = [ "x509-parser 0.18.1", ] -[[package]] -name = "auto_impl" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.108", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -922,21 +482,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - [[package]] name = "bitfield" version = "0.14.0" @@ -960,7 +505,7 @@ checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -993,7 +538,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] @@ -1041,7 +586,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.108", + "syn", ] [[package]] @@ -1064,7 +609,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1111,9 +656,6 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] [[package]] name = "cc" @@ -1133,7 +675,7 @@ version = "0.5.8" source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" dependencies = [ "anyhow", - "digest 0.10.7", + "digest", "ez-hash", "fs-err", "hex", @@ -1197,7 +739,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1245,18 +787,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "const-hex" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" -dependencies = [ - "cfg-if", - "cpufeatures", - "proptest", - "serde_core", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -1284,45 +814,19 @@ dependencies = [ ] [[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" +name = "constant_time_eq" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "cpufeatures" @@ -1372,12 +876,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1409,9 +907,9 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", + "digest", "fiat-crypto", - "rustc_version 0.4.1", + "rustc_version", "subtle", ] @@ -1423,7 +921,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1446,7 +944,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.108", + "syn", ] [[package]] @@ -1457,7 +955,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1503,42 +1001,6 @@ dependencies = [ "x509-cert", ] -[[package]] -name = "dcap-qvl" -version = "0.3.12" -source = "git+https://github.com/flashbots/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1#f1dcc65371e941a7b83e3234833d23a1fb232ab1" -dependencies = [ - "anyhow", - "asn1_der", - "base64 0.22.1", - "borsh", - "byteorder", - "chrono", - "const-oid", - "dcap-qvl-webpki", - "der", - "derive_more 2.1.1", - "futures", - "hex", - "log", - "p256", - "parity-scale-codec", - "pem", - "reqwest", - "ring", - "rustls-pki-types", - "scale-info", - "serde", - "serde-human-bytes", - "serde_json", - "sha2", - "signature", - "tracing", - "urlencoding", - "wasm-bindgen-futures", - "x509-cert", -] - [[package]] name = "dcap-qvl" version = "0.3.12" @@ -1642,7 +1104,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1654,17 +1116,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "derive_more" version = "1.0.0" @@ -1691,7 +1142,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1703,20 +1154,11 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.1", - "syn 2.0.108", + "rustc_version", + "syn", "unicode-xid", ] -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.7" @@ -1758,7 +1200,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1812,7 +1254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest 0.10.7", + "digest", "elliptic-curve", "rfc6979", "signature", @@ -1840,24 +1282,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "educe" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.108", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - [[package]] name = "elliptic-curve" version = "0.13.8" @@ -1866,7 +1290,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", "generic-array", "group", @@ -1893,27 +1317,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.108", -] - -[[package]] -name = "enum-ordinalize" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1933,7 +1337,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1961,7 +1365,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1982,7 +1386,7 @@ checksum = "42b3b3adc5fbbc9e21416d5b721b1bccb501a87d7b32ac89f2c7cea229d40772" dependencies = [ "blake2", "blake3", - "digest 0.10.7", + "digest", "md-5", "sha1", "sha2", @@ -1995,28 +1399,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fastrlp" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - -[[package]] -name = "fastrlp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - [[package]] name = "ff" version = "0.13.1" @@ -2039,18 +1421,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] - [[package]] name = "flagset" version = "0.4.7" @@ -2073,12 +1443,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "foreign-types" version = "0.3.2" @@ -2180,7 +1544,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -2213,12 +1577,6 @@ dependencies = [ "slab", ] -[[package]] -name = "futures-utils-wasm" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" - [[package]] name = "generic-array" version = "0.14.9" @@ -2292,10 +1650,6 @@ name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" -dependencies = [ - "foldhash", - "serde", -] [[package]] name = "heck" @@ -2376,7 +1730,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] @@ -2477,22 +1831,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.17" @@ -2512,11 +1850,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -2627,15 +1963,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - [[package]] name = "impl-trait-for-tuples" version = "0.2.3" @@ -2644,7 +1971,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -2655,8 +1982,6 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", - "serde", - "serde_core", ] [[package]] @@ -2711,33 +2036,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.15" @@ -2866,7 +2164,6 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2", ] @@ -2879,16 +2176,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "keccak-asm" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" -dependencies = [ - "digest 0.10.7", - "sha3-asm", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -2953,17 +2240,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "macro-string" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.108", -] - [[package]] name = "matchers" version = "0.2.0" @@ -2996,7 +2272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest", ] [[package]] @@ -3069,29 +2345,12 @@ dependencies = [ "equivalent", "parking_lot", "portable-atomic", - "rustc_version 0.4.1", + "rustc_version", "smallvec", "tagptr", "uuid", ] -[[package]] -name = "native-tls" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nested-tls" version = "0.0.1" @@ -3175,7 +2434,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -3274,15 +2533,9 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - [[package]] name = "openssl-src" version = "300.5.4+3.5.4" @@ -3305,47 +2558,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror 2.0.17", - "tracing", -] - -[[package]] -name = "opentelemetry-http" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" -dependencies = [ - "async-trait", - "bytes", - "http", - "opentelemetry", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" -dependencies = [ - "futures-channel", - "futures-executor", - "futures-util", - "opentelemetry", - "percent-encoding", - "rand 0.9.2", - "thiserror 2.0.17", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -3420,7 +2632,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -3443,15 +2655,9 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pccs" version = "0.0.1" @@ -3495,16 +2701,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "pest" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" -dependencies = [ - "memchr", - "ucd-trie", -] - [[package]] name = "picky-asn1" version = "0.8.0" @@ -3557,7 +2753,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -3636,7 +2832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.108", + "syn", ] [[package]] @@ -3645,49 +2841,16 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "primitive-types" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" -dependencies = [ - "fixed-hash", - "impl-codec", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", + "elliptic-curve", ] [[package]] -name = "proc-macro-error2" -version = "2.0.1" +name = "proc-macro-crate" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.108", + "toml_edit", ] [[package]] @@ -3707,36 +2870,11 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "version_check", "yansi", ] -[[package]] -name = "proptest" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.10.0", - "num-traits", - "rand 0.9.2", - "rand_chacha 0.9.0", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quinn" version = "0.11.9" @@ -3869,7 +3007,6 @@ checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "serde", ] [[package]] @@ -3908,25 +3045,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.4", - "serde", -] - -[[package]] -name = "rand_xorshift" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" -dependencies = [ - "rand_core 0.9.3", -] - -[[package]] -name = "rapidhash" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111325c42c4bafae99e777cd77b40dea9a2b30c69e9d8c74b6eccd7fba4337de" -dependencies = [ - "rustversion", ] [[package]] @@ -4032,11 +3150,9 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4048,7 +3164,6 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls", "tower", "tower-http", @@ -4090,16 +3205,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes", - "rustc-hex", -] - [[package]] name = "rmp" version = "0.8.15" @@ -4132,7 +3237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", - "digest 0.10.7", + "digest", "num-bigint-dig", "num-integer", "num-traits", @@ -4146,68 +3251,19 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ruint" -version = "1.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" -dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", - "ark-ff 0.5.0", - "bytes", - "fastrlp 0.3.1", - "fastrlp 0.4.0", - "num-bigint", - "num-integer", - "num-traits", - "parity-scale-codec", - "primitive-types", - "proptest", - "rand 0.8.5", - "rand 0.9.2", - "rlp", - "ruint-macro", - "serde_core", - "valuable", - "zeroize", -] - -[[package]] -name = "ruint-macro" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" - [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] - [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", + "semver", ] [[package]] @@ -4286,18 +3342,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "rusty-fork" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] - [[package]] name = "ryu" version = "1.0.20" @@ -4326,16 +3370,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.108", -] - -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", + "syn", ] [[package]] @@ -4358,53 +3393,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "3.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -[[package]] -name = "semver-parser" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" -dependencies = [ - "pest", -] - [[package]] name = "serde" version = "1.0.228" @@ -4462,7 +3456,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -4536,7 +3530,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -4547,7 +3541,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -4556,20 +3550,10 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] -[[package]] -name = "sha3-asm" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" -dependencies = [ - "cc", - "cfg-if", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -4600,7 +3584,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.10.7", + "digest", "rand_core 0.6.4", ] @@ -4714,17 +3698,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.108" @@ -4736,18 +3709,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn-solidity" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8658017776544996edc21c8c7cc8bb4f13db60955382f4bac25dc6303b38438" -dependencies = [ - "paste", - "proc-macro2", - "quote", - "syn 2.0.108", -] - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4765,28 +3726,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", + "syn", ] [[package]] @@ -4879,7 +3819,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -4890,7 +3830,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -4983,17 +3923,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", + "syn", ] [[package]] @@ -5018,18 +3948,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "tokio-tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - [[package]] name = "tokio-util" version = "0.7.17" @@ -5150,7 +4068,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -5174,25 +4092,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e" -dependencies = [ - "js-sys", - "opentelemetry", - "opentelemetry_sdk", - "rustversion", - "smallvec", - "thiserror 2.0.17", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", - "web-time", -] - [[package]] name = "tracing-serde" version = "0.2.0" @@ -5263,53 +4162,12 @@ dependencies = [ "target-lexicon", ] -[[package]] -name = "tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.2", - "sha1", - "thiserror 2.0.17", - "utf-8", -] - [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - [[package]] name = "unicase" version = "2.8.1" @@ -5378,12 +4236,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -5442,15 +4294,6 @@ dependencies = [ "nix", ] -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - [[package]] name = "want" version = "0.3.1" @@ -5521,7 +4364,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.108", + "syn", "wasm-bindgen-shared", ] @@ -5534,20 +4377,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasmtimer" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" -dependencies = [ - "futures", - "js-sys", - "parking_lot", - "pin-utils", - "slab", - "wasm-bindgen", -] - [[package]] name = "web-sys" version = "0.3.85" @@ -5583,47 +4412,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -5666,7 +4460,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5706,7 +4500,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -6018,7 +4812,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "synstructure", ] @@ -6039,7 +4833,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -6059,7 +4853,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "synstructure", ] @@ -6080,7 +4874,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -6113,7 +4907,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e8cdf9e..c8317fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "attestation-provider-server", "attested-tls"] +members = [".", "attestation-provider-server"] [package] name = "attested-tls-proxy" diff --git a/attested-tls/Cargo.toml b/attested-tls/Cargo.toml deleted file mode 100644 index 60f74c0..0000000 --- a/attested-tls/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "attested-tls" -version = "0.0.1" -edition = "2024" -license = "MIT" -description = "A remote-attested TLS protocol for secure communication with CVM services" -repository = "https://github.com/flashbots/attested-tls-proxy" -keywords = ["attested-TLS", "CVM", "TDX"] - -[dependencies] -tokio = { version = "1.48.0", features = ["full"] } -tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws_lc_rs"] } -sha2 = "0.10.9" -x509-parser = "0.18.0" -thiserror = "2.0.17" -webpki-roots = "1.0.4" -http = "1.3.1" -serde_json = "1.0.145" -tracing = "0.1.41" -parity-scale-codec = "3.7.5" -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate" } - -# Used for websocket support -tokio-tungstenite = { version = "0.28.0", optional = true } -futures-util = { version = "0.3.31", optional = true } - -# Used for JSON RPC support -alloy-rpc-client = { version = "1.1.3", optional = true } -tower-service = { version = "0.3.3", optional = true } -alloy-transport-http = { version = "1.4.3", features = ["hyper"], optional = true } -url = { version = "2.5.7", optional = true } -hyper = { version = "1.7.0", features = ["client", "http2"], optional = true } -hyper-util = { version = "0.1.17", features = ["tokio"], optional = true } -bytes = { version = "1.11.1", optional = true } -http-body-util = { version = "0.1.3", optional = true } - -# Used by test helpers -rcgen = { version = "0.14.5", optional = true } - -[dev-dependencies] -rcgen = "0.14.5" -tempfile = "3.23.0" -attestation = { git = "https://github.com/flashbots/attested-tls", branch = "peg/attested-tls-crate", features = ["mock"] } - -[features] -default = ["ws", "rpc"] - -# Adds support for Microsoft Azure attestation generation and verification -azure = ["attestation/azure"] - -# Adds websocket support -ws = ["tokio-tungstenite", "futures-util"] - -# Adds JSON RPC support -rpc = [ - "alloy-rpc-client", - "tower-service", - "alloy-transport-http", - "url", - "hyper", - "hyper-util", - "bytes", - "http-body-util", - "futures-util", -] - -# Exposes helper functions for testing - do not enable in production as this allows dangerous configuration -test-helpers = ["rcgen", "attestation/mock"] - -mock = ["attestation/mock"] diff --git a/attested-tls/README.md b/attested-tls/README.md deleted file mode 100644 index 03a50b0..0000000 --- a/attested-tls/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# attested-tls - -This is a remote-attested TLS protocol and library which uses a post-handshake attestation exchange. - -It is designed to provide a secure channel for communicating with confidential virtual machine based services. - -A normal TLS 1.3 handshake takes place, followed by an attestation exchange sent as normal application data. If the attestation was successful the session is used for normal application traffic. - -This means normal CA-signed TLS certificates can be used, and there is nothing special about the TLS implementation, or any special handshake message extensions or certificate extensions. - -The only special TLS configuration is that the protocol name is specified in ALPN protocol negotiation. - -It uses session binding through exported key material from the TLS session. This means the attestation is guaranteed to be fresh, and is authenticated with ephemeral secrets unique to the session. - -Attestation may be provided by either the server, or the client, or both. - -## Protocol Specification - -A TLS 1.3 handshake is made between server and client. The protocol name `flashbots-ratls/1` is included in ALPN. Future versions of the protocol may add additional protocol names which increment the number given after the slash, but backwards compatibility will be provided through also specifying `flashbots-ratls/1`. - -### Attestation Exchange - -Immediately after the TLS handshake, an attestation exchange is made. The server first provides an attestation message (even if it has the `none` attestation type). The client verifies, if verification is successful it also provides an attestation message and otherwise closes the connection. If the server cannot verify the client's attestation, it closes the connection. - -Attestation exchange messages are formatted as follows: -- A 4 byte length prefix - a big endian encoded unsigned 32 bit integer -- A SCALE (Simple Concatenated Aggregate Little-Endian) encoded [struct](./src/attestation/mod.rs) with the following fields: - - `attestation_type` - a string with one of the attestation types (described above) including `none`. - - `attestation` - the actual attestation data. In the case of DCAP this is a binary quote report. In the case of `none` this is an empty byte array. - -SCALE is used by parity/substrate and was chosen because it is simple and actually matches the formatting used in TDX quotes. So it was already used as a dependency (via the [`dcap-qvl`](https://docs.rs/dcap-qvl) crate). - -### Attestation Generation and Verification - -Attestation input takes the form of a 64 byte array. - -The first 32 bytes are the SHA256 hash of the encoded public key from the TLS leaf certificate of the party providing the attestation, DER encoded exactly as given in the certificate. - -The remaining 32 bytes are exported key material ([RFC5705](https://www.rfc-editor.org/rfc/rfc5705)) from the TLS session. This must have the exporter label `EXPORTER-Channel-Binding` and no context data. - -In the case of attestation types `dcap-tdx`, `gcp-tdx`, and `qemu-tdx`, a standard DCAP attestation is generated using the `configfs-tsm` linux filesystem interface. This means that this binary must be run with access to `/sys/kernel/config/tsm/report` which on many systems requires sudo. - -When verifying DCAP attestations, the Intel PCS is used to retrieve collateral unless a PCCS url is provided via a command line argument. If expired TCB collateral is provided, the quote will fail to verify. - -### Attestation Types - -These are the attestation type names used in the measurements file. - -- `none` - No attestation provided -- `gcp-tdx` - DCAP TDX on Google Cloud Platform -- `azure-tdx` - TDX on Azure, with vTPM attestation -- `qemu-tdx` - TDX on Qemu (no cloud platform) -- `dcap-tdx` - DCAP TDX (platform not specified) - -Local attestation types can be automatically detected. - -## Measurements File - -Accepted measurements for the remote party can be specified in a JSON file containing an array of objects, each of which specifies an accepted attestation type and set of measurements. - -This aims to match the formatting used by `cvm-reverse-proxy`. - -These objects have the following fields: -- `measurement_id` - a name used to describe the entry. For example the name and version of the CVM OS image that these measurements correspond to. -- `attestation_type` - a string containing one of the attestation types (confidential computing platforms) described below. -- `measurements` - an object with fields referring to the five measurement registers. Field names are the same as for the measurement headers (see below). - -Each measurement register entry supports two mutually exclusive fields: -- `expected_any` - **(recommended)** an array of hex-encoded measurement values. The attestation is accepted if the actual measurement matches **any** value in the list (OR semantics). -- `expected` - **(deprecated)** a single hex-encoded measurement value. Retained for backwards compatibility but `expected_any` should be preferred. - -Example using `expected_any` (recommended): - -```JSON -[ - { - "measurement_id": "dcap-tdx-example", - "attestation_type": "dcap-tdx", - "measurements": { - "0": { - "expected_any": [ - "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323" - ] - }, - "1": { - "expected_any": [ - "da6e07866635cb34a9ffcdc26ec6622f289e625c42c39b320f29cdf1dc84390b4f89dd0b073be52ac38ca7b0a0f375bb" - ] - }, - "2": { - "expected_any": [ - "a7157e7c5f932e9babac9209d4527ec9ed837b8e335a931517677fa746db51ee56062e3324e266e3f39ec26a516f4f71" - ] - }, - "3": { - "expected_any": [ - "e63560e50830e22fbc9b06cdce8afe784bf111e4251256cf104050f1347cd4ad9f30da408475066575145da0b098a124" - ] - }, - "4": { - "expected_any": [ - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ] - } - } - } -] -``` - -The `expected_any` field is useful when multiple measurement values should be accepted for a register (e.g., for different versions of the firmware): - -```JSON -{ - "0": { - "expected_any": [ - "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323", - "abc123def456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" - ] - } -} -``` - -
-Legacy format using deprecated expected field - -The `expected` field is deprecated but still supported for backwards compatibility: - -```JSON -[ - { - "measurement_id": "dcap-tdx-example", - "attestation_type": "dcap-tdx", - "measurements": { - "0": { - "expected": "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323" - } - } - } -] -``` - -
- -The only mandatory field is `attestation_type`. If an attestation type is specified, but no measurements, *any* measurements will be accepted for this attestation type. The measurements can still be checked up-stream by the source client or target service using header injection described below. But it is then up to these external programs to reject unacceptable measurements. - diff --git a/attested-tls/src/attested_rpc.rs b/attested-tls/src/attested_rpc.rs deleted file mode 100644 index 0e19c6d..0000000 --- a/attested-tls/src/attested_rpc.rs +++ /dev/null @@ -1,367 +0,0 @@ -//! Provides an attested JSON RPC client based on [alloy_rpc_client::RpcClient] -use alloy_rpc_client::RpcClient; -use alloy_transport_http::{Http, HyperClient}; -use hyper::{Request, Response, client::conn}; -use hyper_util::rt::TokioIo; -use std::{ - future::Future, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; -use thiserror::Error; -use tower_service::Service; - -use crate::{ - AttestedTlsClient, AttestedTlsError, - attestation::{AttestationType, measurements::MultiMeasurements}, -}; - -/// Supported HTTP versions for RPC connection bootstrapping -pub enum HttpVersion { - Http1, - Http2, -} - -/// An attested TLS client which can create RpcClients for attested connections -pub struct AttestedRpcClient { - /// The underlying attested TLS client - pub inner: AttestedTlsClient, - pub http_version: HttpVersion, -} - -impl AttestedRpcClient { - /// Start an RPC client based on HTTP1.1 - pub fn new_http1(inner: AttestedTlsClient) -> Self { - Self { - inner, - http_version: HttpVersion::Http1, - } - } - - /// Start an RPC client based on HTTP2 - pub fn new_http2(inner: AttestedTlsClient) -> Self { - Self { - inner, - http_version: HttpVersion::Http2, - } - } - - /// Connect to an attested RPC server - /// - /// This could be a regular JSON RPC server behind an attested TLS proxy. - pub async fn connect( - &self, - server: &str, - is_local: bool, - ) -> Result<(RpcClient, Option, AttestationType), AttestedRpcError> { - let (stream, measurements, attestation_type) = self.inner.connect_tcp(server).await?; - let io = TokioIo::new(stream); - - let rpc_client = match self.http_version { - HttpVersion::Http1 => { - let (sender, conn) = conn::http1::handshake(io).await?; - tokio::spawn(async move { - if let Err(e) = conn.await { - tracing::error!("AttestedRpcClient connection error: {e}"); - } - }); - let url = url::Url::parse(&format!("http://{server}"))?; - Self::make_attested_http1_rpc_client(url, sender, is_local).await? - } - HttpVersion::Http2 => { - let (sender, conn) = conn::http2::handshake(TokioExecutor, io).await?; - tokio::spawn(async move { - if let Err(e) = conn.await { - tracing::error!("AttestedRpcClient connection error: {e}"); - } - }); - let url = url::Url::parse(&format!("http://{server}"))?; - Self::make_attested_http2_rpc_client(url, sender, is_local).await? - } - }; - - Ok((rpc_client, measurements, attestation_type)) - } - - async fn make_attested_http1_rpc_client( - rpc_url: url::Url, - sender: hyper::client::conn::http1::SendRequest>, - is_local: bool, - ) -> Result { - let service = Http1ClientConnectionService::new(sender); - let hyper_transport = - HyperClient::, _>::with_service(service); - let http = Http::with_client(hyper_transport, rpc_url); - Ok(RpcClient::new(http, is_local)) - } - - async fn make_attested_http2_rpc_client( - rpc_url: url::Url, - sender: hyper::client::conn::http2::SendRequest>, - is_local: bool, - ) -> Result { - let service = Http2ClientConnectionService { sender }; - let hyper_transport = - HyperClient::, _>::with_service(service); - let http = Http::with_client(hyper_transport, rpc_url); - Ok(RpcClient::new(http, is_local)) - } -} - -#[derive(Debug, Clone)] -struct Http1ClientConnectionService { - sender: Arc< - tokio::sync::Mutex< - hyper::client::conn::http1::SendRequest>, - >, - >, -} - -impl Http1ClientConnectionService { - fn new( - sender: hyper::client::conn::http1::SendRequest>, - ) -> Self { - Self { - sender: tokio::sync::Mutex::new(sender).into(), - } - } -} - -impl Service>> for Http1ClientConnectionService { - type Response = Response; - type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Request>) -> Self::Future { - let sender = self.sender.clone(); - Box::pin(async move { - let mut sender = sender.lock().await; - futures_util::future::poll_fn(|cx| sender.poll_ready(cx)).await?; - sender.send_request(req).await - }) - } -} - -#[derive(Debug, Clone)] -struct Http2ClientConnectionService { - sender: hyper::client::conn::http2::SendRequest>, -} - -impl Service>> for Http2ClientConnectionService { - type Response = Response; - type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.sender.poll_ready(cx) - } - - fn call(&mut self, req: Request>) -> Self::Future { - let mut sender = self.sender.clone(); - Box::pin(async move { sender.send_request(req).await }) - } -} - -#[derive(Error, Debug)] -pub enum AttestedRpcError { - #[error("Attested TLS: {0}")] - Rustls(#[from] AttestedTlsError), - #[error("IO: {0}")] - Io(#[from] std::io::Error), - #[error("HTTP: {0}")] - Hyper(#[from] hyper::Error), - #[error("Cannot parse URL: {0}")] - Url(#[from] url::ParseError), -} - -#[derive(Clone)] -struct TokioExecutor; - -impl hyper::rt::Executor for TokioExecutor -where - F: Future + Send + 'static, - F::Output: Send + 'static, -{ - fn execute(&self, fut: F) { - tokio::task::spawn(fut); - } -} - -#[cfg(test)] -mod tests { - use std::{convert::Infallible, sync::Arc}; - - use bytes::Bytes; - use http_body_util::{BodyExt, Full}; - use hyper::service::service_fn; - use hyper::{Request, Response, StatusCode}; - use hyper_util::rt::TokioIo; - use serde_json::{Value, json}; - use tokio::net::TcpListener; - - use super::AttestedRpcClient; - - use crate::{ - AttestedTlsClient, AttestedTlsServer, - attestation::{AttestationGenerator, AttestationType, AttestationVerifier}, - test_helpers::{generate_certificate_chain, generate_tls_config}, - }; - - async fn simple_json_rpc_service( - req: Request, - ) -> Result>, Infallible> { - let body = req.into_body().collect().await.unwrap().to_bytes(); - let id = serde_json::from_slice::(&body) - .ok() - .and_then(|v| v.get("id").cloned()) - .unwrap_or(Value::Null); - - let response_body = json!({ - "jsonrpc": "2.0", - "result": "0x1", - "id": id, - }) - .to_string(); - - Ok(Response::builder() - .status(StatusCode::OK) - .header("content-type", "application/json") - .body(Full::new(Bytes::from(response_body))) - .unwrap()) - } - - async fn serve_json_rpc_connection( - server: Arc, - tcp_stream: tokio::net::TcpStream, - ) { - let (tls_stream, _measurements, _attestation_type) = - server.handle_connection(tcp_stream).await.unwrap(); - let io = TokioIo::new(tls_stream); - let service = service_fn(simple_json_rpc_service); - - hyper::server::conn::http2::Builder::new(hyper_util::rt::tokio::TokioExecutor::new()) - .serve_connection(io, service) - .await - .unwrap(); - } - - #[tokio::test] - async fn server_attestation_rpc_client() { - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - let server = Arc::new(server); - - tokio::spawn(async move { - let (tcp_stream, _) = listener.accept().await.unwrap(); - serve_json_rpc_connection(server, tcp_stream).await; - }); - - let client = AttestedTlsClient::new_with_tls_config( - client_config, - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .unwrap(); - - let attested_rpc_client = AttestedRpcClient::new_http2(client); - - let (rpc_client, _measurements, _attestation_type) = attested_rpc_client - .connect(&server_addr.to_string(), true) - .await - .unwrap(); - - let response: String = rpc_client.request("eth_chainId", ()).await.unwrap(); - assert_eq!(response, "0x1"); - } - - #[tokio::test] - async fn server_attestation_rpc_client_drops_connection() { - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - let server = Arc::new(server); - - let (connection_breaker_tx, connection_breaker_rx) = tokio::sync::oneshot::channel(); - let (connection_closed_tx, connection_closed_rx) = tokio::sync::oneshot::channel(); - - tokio::spawn(async move { - let (tcp_stream_1, _) = listener.accept().await.unwrap(); - let first_conn_handle = - tokio::spawn(serve_json_rpc_connection(server.clone(), tcp_stream_1)); - - connection_breaker_rx.await.unwrap(); - first_conn_handle.abort(); - let _ = first_conn_handle.await; - let _ = connection_closed_tx.send(()); - - let (tcp_stream_2, _) = listener.accept().await.unwrap(); - serve_json_rpc_connection(server, tcp_stream_2).await; - }); - - let client = AttestedTlsClient::new_with_tls_config( - client_config, - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .unwrap(); - - let attested_rpc_client = AttestedRpcClient::new_http2(client); - - let (rpc_client, _measurements, _attestation_type) = attested_rpc_client - .connect(&server_addr.to_string(), true) - .await - .unwrap(); - - let response: String = rpc_client.request("eth_chainId", ()).await.unwrap(); - assert_eq!(response, "0x1"); - - connection_breaker_tx.send(()).unwrap(); - connection_closed_rx.await.unwrap(); - - let err = rpc_client - .request::<(), String>("eth_chainId", ()) - .await - .unwrap_err(); - let err_msg = err.to_string(); - assert!( - err_msg.contains("connection error") || err_msg.contains("operation was canceled"), - "unexpected error: {err_msg}" - ); - - let (rpc_client, _measurements, _attestation_type) = attested_rpc_client - .connect(&server_addr.to_string(), true) - .await - .unwrap(); - - let response: String = rpc_client.request("eth_chainId", ()).await.unwrap(); - assert_eq!(response, "0x1"); - } -} diff --git a/attested-tls/src/lib.rs b/attested-tls/src/lib.rs deleted file mode 100644 index bffc774..0000000 --- a/attested-tls/src/lib.rs +++ /dev/null @@ -1,833 +0,0 @@ -//! Attested TLS protocol server and client -#[cfg(feature = "ws")] -pub mod websockets; - -#[cfg(feature = "rpc")] -pub mod attested_rpc; - -#[cfg(any(test, feature = "test-helpers"))] -pub mod test_helpers; - -pub use attestation; - -use attestation::{ - AttestationError, AttestationExchangeMessage, AttestationGenerator, AttestationType, - AttestationVerifier, measurements::MultiMeasurements, -}; -use parity_scale_codec::{Decode, Encode}; -use sha2::{Digest, Sha256}; -use thiserror::Error; -use tokio_rustls::rustls::{ - self, - server::{VerifierBuilderError, WebPkiClientVerifier}, -}; -use x509_parser::parse_x509_certificate; - -use std::num::TryFromIntError; -use std::sync::Arc; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use tokio_rustls::rustls::RootCertStore; -use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; -use tokio_rustls::{ - TlsAcceptor, TlsConnector, - rustls::{ClientConfig, ServerConfig}, -}; - -/// This makes it possible to add breaking protocol changes and provide backwards compatibility. -/// When adding more supported versions, note that ordering is important. ALPN will pick the first -/// protocol which both parties support - so newer supported versions should come first. -pub const SUPPORTED_ALPN_PROTOCOL_VERSIONS: [&[u8]; 1] = [b"flashbots-ratls/1"]; - -/// The label used when exporting key material from a TLS session -pub(crate) const EXPORTER_LABEL: &[u8; 24] = b"EXPORTER-Channel-Binding"; -/// Maximum allowed attestation frame length in bytes (64 KiB) -const MAX_ATTESTATION_LEN_BYTES: usize = 64 * 1024; - -/// TLS Credentials -pub struct TlsCertAndKey { - /// Der-encoded TLS certificate chain - pub cert_chain: Vec>, - /// Der-encoded TLS private key - pub key: PrivateKeyDer<'static>, -} - -/// A TLS server which makes an attestation exchange following the TLS handshake -#[derive(Clone)] -pub struct AttestedTlsServer { - /// Quote generation type to use (including none) - attestation_generator: AttestationGenerator, - /// Verifier for remote attestation (including none) - attestation_verifier: AttestationVerifier, - /// The TLS certificate chain - cert_chain: Vec>, - /// For accepting TLS connections - acceptor: TlsAcceptor, -} - -impl std::fmt::Debug for AttestedTlsServer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AttestedTlsServer") - .field("attestation_generator", &self.attestation_generator) - .field("attestation_verifier", &self.attestation_verifier) - .field("cert_chain", &self.cert_chain) - .finish() - } -} - -impl AttestedTlsServer { - pub fn new( - cert_and_key: TlsCertAndKey, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - client_auth: bool, - ) -> Result { - let server_config = if client_auth { - let root_store = - RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?; - - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_client_cert_verifier(verifier) - .with_single_cert(cert_and_key.cert_chain.clone(), cert_and_key.key)? - } else { - ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_no_client_auth() - .with_single_cert(cert_and_key.cert_chain.clone(), cert_and_key.key)? - }; - - Self::new_with_tls_config( - cert_and_key.cert_chain, - server_config, - attestation_generator, - attestation_verifier, - ) - } - - /// Start with preconfigured TLS - /// - /// This allows dangerous configuration - pub fn new_with_tls_config( - cert_chain: Vec>, - mut server_config: ServerConfig, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - ) -> Result { - #[cfg(feature = "mock")] - tracing::warn!("AttestedTlsServer instantiated in MOCK mode - do NOT use in production"); - // Ensure protocol version compatibility - server_config.alpn_protocols = map_alpn_protocols(server_config.alpn_protocols); - - let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config)); - - Ok(Self { - attestation_generator, - attestation_verifier, - acceptor, - cert_chain, - }) - } - - /// Handle an incoming connection from an [AttestedTlsClient] - /// - /// This is transport agnostic and will work with any asynchronous stream - pub async fn handle_connection( - &self, - inbound: IO, - ) -> Result< - ( - tokio_rustls::server::TlsStream, - Option, - AttestationType, - ), - AttestedTlsError, - > - where - IO: AsyncRead + AsyncWrite + Unpin, - { - tracing::debug!("attested-tls-server accepted connection"); - - // Do TLS handshake - let mut tls_stream = self.acceptor.accept(inbound).await?; - let (_io, connection) = tls_stream.get_ref(); - - // Ensure TLS 1.3 - if connection.protocol_version() != Some(rustls::ProtocolVersion::TLSv1_3) { - return Err(AttestedTlsError::NotTls13); - } - - // Ensure that we agreed a protocol - let _negotiated_protocol = connection - .alpn_protocol() - .ok_or(AttestedTlsError::AlpnFailed)?; - - // Compute an exporter unique to the session - let mut exporter = [0u8; 32]; - connection.export_keying_material( - &mut exporter, - EXPORTER_LABEL, - None, // context - )?; - - let input_data = compute_report_input(Some(&self.cert_chain), exporter)?; - - // Get the TLS certficate chain of the client, if there is one - let remote_cert_chain = connection.peer_certificates().map(|c| c.to_owned()); - - // If we are in a CVM, generate an attestation - let attestation = self - .attestation_generator - .generate_attestation(input_data) - .await? - .encode(); - - // Write our attestation to the channel, with length prefix - let attestation_length_prefix = checked_length_prefix(&attestation)?; - tls_stream.write_all(&attestation_length_prefix).await?; - tls_stream.write_all(&attestation).await?; - - // Now read a length-prefixed attestation from the remote peer - // In the case of no client attestation this will be zero bytes - let buf = read_length_prefixed_attestation(&mut tls_stream).await?; - - let remote_attestation_message = AttestationExchangeMessage::decode(&mut &buf[..])?; - let remote_attestation_type = remote_attestation_message.attestation_type; - - // If we expect an attestaion from the client, verify it and get measurements - let measurements = if self.attestation_verifier.has_remote_attestation() { - let remote_input_data = compute_report_input(remote_cert_chain.as_deref(), exporter)?; - - self.attestation_verifier - .verify_attestation(remote_attestation_message, remote_input_data) - .await? - } else { - None - }; - - Ok((tls_stream, measurements, remote_attestation_type)) - } - - /// Handle an incoming connection from an [AttestedTlsClient] - /// - /// This is transport agnostic and will work with any asynchronous stream - /// - /// This is the same as handle_connection except it returns only the stream and not the - /// attestation details, in order to provide a similar API to [TlsAcceptor::accept] - pub async fn accept( - &self, - inbound: IO, - ) -> Result, AttestedTlsError> - where - IO: AsyncRead + AsyncWrite + Unpin, - { - let (stream, _measurements, _attestation_type) = self.handle_connection(inbound).await?; - Ok(stream) - } -} - -/// A proxy client which forwards http traffic to a proxy-server -#[derive(Clone)] -pub struct AttestedTlsClient { - /// The connector for making TLS connections with out configuration - connector: TlsConnector, - /// Quote generation type to use (including none) - attestation_generator: AttestationGenerator, - /// Verifier for remote attestation (including none) - attestation_verifier: AttestationVerifier, - /// The certificate chain for client auth - cert_chain: Option>>, -} - -impl std::fmt::Debug for AttestedTlsClient { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AttestedTlsClient") - .field("attestation_verifier", &self.attestation_verifier) - .field("attestation_generator", &self.attestation_generator) - .field("cert_chain", &self.cert_chain) - .finish() - } -} - -impl AttestedTlsClient { - /// Start with optional TLS client auth - pub fn new( - cert_and_key: Option, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - remote_certificate: Option>, - ) -> Result { - // If a remote CA cert was given, use it as the root store, otherwise use webpki_roots - let root_store = match remote_certificate { - Some(remote_certificate) => { - let mut root_store = RootCertStore::empty(); - root_store.add(remote_certificate)?; - root_store - } - None => RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()), - }; - - // Setup TLS client configuration, with or without client auth - let client_config = if let Some(ref cert_and_key) = cert_and_key { - ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_root_certificates(root_store) - .with_client_auth_cert( - cert_and_key.cert_chain.clone(), - cert_and_key.key.clone_key(), - )? - } else { - ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) - .with_root_certificates(root_store) - .with_no_client_auth() - }; - - Self::new_with_tls_config( - client_config, - attestation_generator, - attestation_verifier, - cert_and_key.map(|c| c.cert_chain), - ) - } - - /// Create a new proxy client with given TLS configuration - /// - /// This allows dangerous configuration but is used in tests - pub fn new_with_tls_config( - mut client_config: ClientConfig, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - cert_chain: Option>>, - ) -> Result { - #[cfg(feature = "mock")] - tracing::warn!("AttestedTlsClient instantiated in MOCK mode - do NOT use in production"); - if client_config.client_auth_cert_resolver.has_certs() && cert_chain.is_none() { - return Err(AttestedTlsError::ClientAuthWithoutClientCert); - } - // Ensure protocol version compatibility - client_config.alpn_protocols = map_alpn_protocols(client_config.alpn_protocols); - - let connector = TlsConnector::from(Arc::new(client_config)); - - Ok(Self { - connector, - attestation_generator, - attestation_verifier, - cert_chain, - }) - } - - /// Given a connection to an attested TLS server, do a TLS handshake and attestation exchange, and return the TLS - /// stream together with measurement details - /// - /// This is transport agnostic and will work with any asynchronous stream - pub async fn connect( - &self, - target: &str, - outbound: IO, - ) -> Result< - ( - tokio_rustls::client::TlsStream, - Option, - AttestationType, - ), - AttestedTlsError, - > - where - IO: AsyncRead + AsyncWrite + Unpin, - { - // Make a TLS handshake with the given connection - let mut tls_stream = self - .connector - .connect(server_name_from_host(target)?, outbound) - .await?; - - let (_io, server_connection) = tls_stream.get_ref(); - - // Ensure TLS 1.3 - if server_connection.protocol_version() != Some(rustls::ProtocolVersion::TLSv1_3) { - return Err(AttestedTlsError::NotTls13); - } - - // Ensure that we agreed a protocol - let _negotiated_protocol = server_connection - .alpn_protocol() - .ok_or(AttestedTlsError::AlpnFailed)?; - - // Compute an exporter unique to the channel - let mut exporter = [0u8; 32]; - server_connection.export_keying_material( - &mut exporter, - EXPORTER_LABEL, - None, // context - )?; - - // Get the TLS certificate chain of the server - let remote_cert_chain = server_connection - .peer_certificates() - .ok_or(AttestedTlsError::NoCertificate)? - .to_owned(); - - let remote_input_data = compute_report_input(Some(&remote_cert_chain), exporter)?; - - // Read a length prefixed attestation from the proxy-server - let buf = read_length_prefixed_attestation(&mut tls_stream).await?; - - let remote_attestation_message = AttestationExchangeMessage::decode(&mut &buf[..])?; - let remote_attestation_type = remote_attestation_message.attestation_type; - - // Verify the remote attestation against our accepted measurements - let measurements = self - .attestation_verifier - .verify_attestation(remote_attestation_message, remote_input_data) - .await?; - - // If we are in a CVM, provide an attestation - let attestation = if self.attestation_generator.attestation_type != AttestationType::None { - let local_input_data = compute_report_input(self.cert_chain.as_deref(), exporter)?; - self.attestation_generator - .generate_attestation(local_input_data) - .await? - .encode() - } else { - AttestationExchangeMessage::without_attestation().encode() - }; - - // Send our attestation (or zero bytes) prefixed with length - let attestation_length_prefix = checked_length_prefix(&attestation)?; - tls_stream.write_all(&attestation_length_prefix).await?; - tls_stream.write_all(&attestation).await?; - - Ok((tls_stream, measurements, remote_attestation_type)) - } - - /// Make a TCP connection, do a TLS handshake and attestation exchange, and return the TLS - /// stream together with measurement details - pub async fn connect_tcp( - &self, - target: &str, - ) -> Result< - ( - tokio_rustls::client::TlsStream, - Option, - AttestationType, - ), - AttestedTlsError, - > { - let out = tokio::net::TcpStream::connect(&target).await?; - self.connect(target, out).await - } - - /// Connect to an attested TLS server using TCP, retrieve the remote TLS certificate and return it - pub async fn get_tls_cert( - &self, - server_name: &str, - ) -> Result<(Vec>, Option), AttestedTlsError> { - let (mut tls_stream, measurements, _attestation_type) = - self.connect_tcp(server_name).await?; - - let (_io, server_connection) = tls_stream.get_ref(); - - let remote_cert_chain = server_connection - .peer_certificates() - .ok_or(AttestedTlsError::NoCertificate)? - .to_owned(); - - tls_stream.shutdown().await?; - - Ok((remote_cert_chain, measurements)) - } -} - -/// A client which just gets the attested remote certificate, with no client authentication -pub async fn get_tls_cert( - server_name: String, - attestation_verifier: AttestationVerifier, - remote_certificate: Option>, -) -> Result<(Vec>, Option), AttestedTlsError> { - tracing::debug!("Getting remote TLS cert"); - let attested_tls_client = AttestedTlsClient::new( - None, - AttestationGenerator::with_no_attestation(), - attestation_verifier, - remote_certificate, - )?; - attested_tls_client - .get_tls_cert(&host_to_host_with_port(&server_name)) - .await -} - -/// Retrieve a remote TLS certificate using a preconfigured TLS client config -pub async fn get_tls_cert_with_config( - server_name: &str, - attestation_verifier: AttestationVerifier, - client_config: ClientConfig, -) -> Result<(Vec>, Option), AttestedTlsError> { - let attested_tls_client = AttestedTlsClient::new_with_tls_config( - client_config, - AttestationGenerator::with_no_attestation(), - attestation_verifier, - None, - )?; - attested_tls_client - .get_tls_cert(&host_to_host_with_port(server_name)) - .await -} - -/// Given a certificate chain and an exporter (session key material), build the quote input value -/// SHA256(pki) || exporter -pub fn compute_report_input( - cert_chain: Option<&[CertificateDer<'_>]>, - exporter: [u8; 32], -) -> Result<[u8; 64], AttestationError> { - let mut quote_input = [0u8; 64]; - if let Some(cert_chain) = cert_chain { - let pki_hash = get_pki_hash_from_certificate_chain(cert_chain)?; - quote_input[..32].copy_from_slice(&pki_hash); - } - quote_input[32..].copy_from_slice(&exporter); - Ok(quote_input) -} - -/// Given a certificate chain, get the [Sha256] hash of the public key of the leaf certificate -fn get_pki_hash_from_certificate_chain( - cert_chain: &[CertificateDer<'_>], -) -> Result<[u8; 32], AttestationError> { - let leaf_certificate = cert_chain.first().ok_or(AttestationError::NoCertificate)?; - let (_, cert) = parse_x509_certificate(leaf_certificate.as_ref())?; - let public_key = &cert.tbs_certificate.subject_pki; - let key_bytes = public_key.subject_public_key.as_ref(); - - let mut hasher = Sha256::new(); - hasher.update(key_bytes); - Ok(hasher.finalize().into()) -} - -/// An error when running an attested TLS client or server -#[derive(Error, Debug)] -pub enum AttestedTlsError { - #[error("Failed to get server ceritifcate")] - NoCertificate, - #[error("TLS: {0}")] - Rustls(#[from] tokio_rustls::rustls::Error), - #[error("Verifier builder: {0}")] - VerifierBuilder(#[from] VerifierBuilderError), - #[error("IO: {0}")] - Io(#[from] std::io::Error), - #[error("Attestation: {0}")] - Attestation(#[from] AttestationError), - #[error("Integer conversion: {0}")] - IntConversion(#[from] TryFromIntError), - #[error("Bad host name: {0}")] - BadDnsName(#[from] tokio_rustls::rustls::pki_types::InvalidDnsNameError), - #[error("Serialization: {0}")] - Serialization(#[from] parity_scale_codec::Error), - #[error("Protocol negotiation failed - remote peer does not support this protocol")] - AlpnFailed, - #[error("Client authentication is enabled but a client ceritifcate was not given")] - ClientAuthWithoutClientCert, - #[error("No cryptography provider available - this implies a bad build")] - NoCryptoProvider, - #[error("Only TLS 1.3 is supported")] - NotTls13, - #[error("Attestation length {length} exceeds maximum {max}")] - AttestationTooLarge { length: usize, max: usize }, -} - -/// Given a byte array, encode its length as a 4 byte big endian u32 -fn length_prefix(input: &[u8]) -> [u8; 4] { - let len = input.len() as u32; - len.to_be_bytes() -} - -/// Encode a byte array length as a 4-byte big endian u32 after enforcing the max frame size. -fn checked_length_prefix(input: &[u8]) -> Result<[u8; 4], AttestedTlsError> { - validate_attestation_length(input.len())?; - Ok(length_prefix(input)) -} - -fn validate_attestation_length(length: usize) -> Result<(), AttestedTlsError> { - if length > MAX_ATTESTATION_LEN_BYTES { - return Err(AttestedTlsError::AttestationTooLarge { - length, - max: MAX_ATTESTATION_LEN_BYTES, - }); - } - Ok(()) -} - -async fn read_length_prefixed_attestation(io: &mut IO) -> Result, AttestedTlsError> -where - IO: AsyncRead + Unpin, -{ - let mut length_bytes = [0; 4]; - io.read_exact(&mut length_bytes).await?; - let length = u32::from_be_bytes(length_bytes) as usize; - validate_attestation_length(length)?; - - let mut buf = vec![0; length]; - io.read_exact(&mut buf).await?; - Ok(buf) -} - -/// Given a hostname with or without port number, create a TLS [ServerName] with just the host part -pub(crate) fn server_name_from_host( - host: &str, -) -> Result, tokio_rustls::rustls::pki_types::InvalidDnsNameError> { - // If host contains ':', try to split off the port. - let host_part = host.rsplit_once(':').map(|(h, _)| h).unwrap_or(host); - - // If the host is an IPv6 literal in brackets like "[::1]:443", - // remove the brackets for SNI (SNI allows bare IPv6 too). - let host_part = host_part.trim_matches(|c| c == '[' || c == ']'); - - ServerName::try_from(host_part.to_string()) -} - -/// If no port was provided, default to 443 -fn host_to_host_with_port(host: &str) -> String { - if host.contains(':') { - host.to_string() - } else { - format!("{host}:443") - } -} - -/// Ensure protocol compatibility with the other party by adding 'flashbots-ratls/' to the -/// protocol names of all supported protocols -fn map_alpn_protocols(existing_protocols: Vec>) -> Vec> { - let mut mapped_protocols = Vec::new(); - for alpn in SUPPORTED_ALPN_PROTOCOL_VERSIONS { - let alpn = alpn.to_vec(); - let new_p: Vec<_> = existing_protocols - .clone() - .into_iter() - .map(|p| { - let mut updated_protocol_name = alpn.clone(); - updated_protocol_name.extend(b"+"); - updated_protocol_name.extend(p); - updated_protocol_name - }) - .collect(); - - mapped_protocols.extend(new_p); - } - - // For the case that no existing protocols are specified by the remote party: - for alpn in SUPPORTED_ALPN_PROTOCOL_VERSIONS { - mapped_protocols.push(alpn.to_vec()); - } - - mapped_protocols -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::test_helpers::{generate_certificate_chain, generate_tls_config}; - use attestation::measurements::MeasurementPolicy; - use tokio::net::TcpListener; - - #[tokio::test] - async fn server_attestation() { - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - - tokio::spawn(async move { - let (tcp_stream, _) = listener.accept().await.unwrap(); - let (_stream, _measurements, _attestation_type) = - server.handle_connection(tcp_stream).await.unwrap(); - }); - - let client = AttestedTlsClient::new_with_tls_config( - client_config, - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .unwrap(); - - let (_stream, _measurements, _attestation_type) = - client.connect_tcp(&server_addr.to_string()).await.unwrap(); - } - - // Negative test - server does not provide attestation but client requires it - // Server has no attestation, client has no attestation and no client auth - #[tokio::test] - async fn fails_on_no_attestation_when_expected() { - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - AttestationGenerator::with_no_attestation(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - - tokio::spawn(async move { - let (tcp_stream, _) = listener.accept().await.unwrap(); - let (_stream, _measurements, _attestation_type) = - server.handle_connection(tcp_stream).await.unwrap(); - }); - - let client = AttestedTlsClient::new_with_tls_config( - client_config, - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .unwrap(); - - let client_result = client.connect_tcp(&server_addr.to_string()).await; - - assert!(matches!( - client_result.unwrap_err(), - AttestedTlsError::Attestation(AttestationError::AttestationTypeNotAccepted) - )); - } - - // Negative test - server does not provide attestation but client requires it - // Server has no attestaion, client has no attestation and no client auth - #[tokio::test] - async fn fails_on_bad_measurements() { - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let server_addr = listener.local_addr().unwrap(); - - tokio::spawn(async move { - let (tcp_stream, _) = listener.accept().await.unwrap(); - let (_stream, _measurements, _attestation_type) = - server.handle_connection(tcp_stream).await.unwrap(); - }); - - let measurement_policy = MeasurementPolicy::from_json_bytes( - br#" - [{ - "measurement_id": "test", - "attestation_type": "dcap-tdx", - "measurements": { - "0": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - "1": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - "2": { "expected": "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" }, - "3": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - "4": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } - } - }] - "# - .to_vec(), - ) - .unwrap(); - - let attestation_verifier = AttestationVerifier { - measurement_policy, - pccs_url: None, - dump_dcap_quotes: false, - override_azure_outdated_tcb: false, - }; - - let client = AttestedTlsClient::new_with_tls_config( - client_config, - AttestationGenerator::with_no_attestation(), - attestation_verifier, - None, - ) - .unwrap(); - - let client_result = client.connect_tcp(&server_addr.to_string()).await; - - assert!(matches!( - client_result.unwrap_err(), - AttestedTlsError::Attestation(AttestationError::MeasurementsNotAccepted) - )); - } - - #[test] - fn accepts_attestation_length_at_cap() { - assert!(validate_attestation_length(MAX_ATTESTATION_LEN_BYTES).is_ok()); - } - - #[test] - fn rejects_attestation_length_over_cap() { - let err = validate_attestation_length(MAX_ATTESTATION_LEN_BYTES + 1).unwrap_err(); - assert!(matches!( - err, - AttestedTlsError::AttestationTooLarge { - length, - max: MAX_ATTESTATION_LEN_BYTES - } if length == MAX_ATTESTATION_LEN_BYTES + 1 - )); - } - - #[test] - fn rejects_oversized_outgoing_attestation_frame() { - let oversized = vec![0u8; MAX_ATTESTATION_LEN_BYTES + 1]; - let err = checked_length_prefix(&oversized).unwrap_err(); - assert!(matches!( - err, - AttestedTlsError::AttestationTooLarge { - length, - max: MAX_ATTESTATION_LEN_BYTES - } if length == MAX_ATTESTATION_LEN_BYTES + 1 - )); - } - - #[tokio::test] - async fn read_length_prefixed_attestation_accepts_length_at_cap() { - let (mut tx, mut rx) = tokio::io::duplex(MAX_ATTESTATION_LEN_BYTES + 16); - let payload = vec![7u8; MAX_ATTESTATION_LEN_BYTES]; - - tokio::spawn(async move { - tx.write_all(&(MAX_ATTESTATION_LEN_BYTES as u32).to_be_bytes()) - .await - .unwrap(); - tx.write_all(&payload).await.unwrap(); - }); - - let buf = read_length_prefixed_attestation(&mut rx).await.unwrap(); - assert_eq!(buf.len(), MAX_ATTESTATION_LEN_BYTES); - } - - #[tokio::test] - async fn read_length_prefixed_attestation_rejects_length_over_cap() { - let (mut tx, mut rx) = tokio::io::duplex(16); - - tokio::spawn(async move { - tx.write_all(&((MAX_ATTESTATION_LEN_BYTES as u32) + 1).to_be_bytes()) - .await - .unwrap(); - }); - - let err = read_length_prefixed_attestation(&mut rx).await.unwrap_err(); - assert!(matches!( - err, - AttestedTlsError::AttestationTooLarge { - length, - max: MAX_ATTESTATION_LEN_BYTES - } if length == MAX_ATTESTATION_LEN_BYTES + 1 - )); - } -} diff --git a/attested-tls/src/test_helpers.rs b/attested-tls/src/test_helpers.rs deleted file mode 100644 index 2ba933d..0000000 --- a/attested-tls/src/test_helpers.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Helper functions used in tests -use std::{net::IpAddr, sync::Arc}; -use tokio_rustls::rustls::{ - ClientConfig, RootCertStore, ServerConfig, - pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}, - server::{WebPkiClientVerifier, danger::ClientCertVerifier}, -}; - -use crate::SUPPORTED_ALPN_PROTOCOL_VERSIONS; - -pub use attestation::measurements::mock_dcap_measurements; - -fn install_crypto_provider() { - let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default(); -} - -/// Helper to generate a self-signed certificate for testing -pub fn generate_certificate_chain( - ip: IpAddr, -) -> (Vec>, PrivateKeyDer<'static>) { - let mut params = rcgen::CertificateParams::new(vec![]).unwrap(); - params.subject_alt_names.push(rcgen::SanType::IpAddress(ip)); - params - .distinguished_name - .push(rcgen::DnType::CommonName, ip.to_string()); - - let keypair = rcgen::KeyPair::generate().unwrap(); - let cert = params.self_signed(&keypair).unwrap(); - - let certs = vec![CertificateDer::from(cert)]; - let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(keypair.serialize_der())); - (certs, key) -} - -/// Helper to generate TLS configuration for testing -/// -/// For the server: A given self-signed certificate -/// For the client: A root certificate store with the server's certificate -pub fn generate_tls_config( - certificate_chain: Vec>, - key: PrivateKeyDer<'static>, -) -> (ServerConfig, ClientConfig) { - install_crypto_provider(); - - let supported_protocols: Vec<_> = SUPPORTED_ALPN_PROTOCOL_VERSIONS - .into_iter() - .map(|p| p.to_vec()) - .collect(); - - let mut server_config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(certificate_chain.clone(), key) - .expect("Failed to create rustls server config"); - - server_config.alpn_protocols = supported_protocols.clone(); - - let mut root_store = RootCertStore::empty(); - root_store.add(certificate_chain[0].clone()).unwrap(); - - let mut client_config = ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); - - client_config.alpn_protocols = supported_protocols; - - (server_config, client_config) -} - -/// Helper to generate a mutual TLS configuration with client authentification for testing -pub fn generate_tls_config_with_client_auth( - alice_certificate_chain: Vec>, - alice_key: PrivateKeyDer<'static>, - bob_certificate_chain: Vec>, - bob_key: PrivateKeyDer<'static>, -) -> ((ServerConfig, ClientConfig), (ServerConfig, ClientConfig)) { - install_crypto_provider(); - - let supported_protocols: Vec<_> = SUPPORTED_ALPN_PROTOCOL_VERSIONS - .into_iter() - .map(|p| p.to_vec()) - .collect(); - - let (alice_client_verifier, alice_root_store) = - client_verifier_from_remote_cert(bob_certificate_chain[0].clone()); - - let mut alice_server_config = ServerConfig::builder() - .with_client_cert_verifier(alice_client_verifier) - .with_single_cert(alice_certificate_chain.clone(), alice_key.clone_key()) - .expect("Failed to create rustls server config"); - - alice_server_config.alpn_protocols = supported_protocols.clone(); - - let mut alice_client_config = ClientConfig::builder() - .with_root_certificates(alice_root_store) - .with_client_auth_cert(alice_certificate_chain.clone(), alice_key) - .unwrap(); - - alice_client_config.alpn_protocols = supported_protocols.clone(); - - let (bob_client_verifier, bob_root_store) = - client_verifier_from_remote_cert(alice_certificate_chain[0].clone()); - - let mut bob_server_config = ServerConfig::builder() - .with_client_cert_verifier(bob_client_verifier) - .with_single_cert(bob_certificate_chain.clone(), bob_key.clone_key()) - .expect("Failed to create rustls server config"); - - bob_server_config.alpn_protocols = supported_protocols.clone(); - - let mut bob_client_config = ClientConfig::builder() - .with_root_certificates(bob_root_store) - .with_client_auth_cert(bob_certificate_chain, bob_key) - .unwrap(); - - bob_client_config.alpn_protocols = supported_protocols; - ( - (alice_server_config, alice_client_config), - (bob_server_config, bob_client_config), - ) -} - -/// Given a TLS certificate, return a [WebPkiClientVerifier] and [RootCertStore] which will accept -/// that certificate -fn client_verifier_from_remote_cert( - cert: CertificateDer<'static>, -) -> (Arc, RootCertStore) { - let mut root_store = RootCertStore::empty(); - root_store.add(cert).unwrap(); - - ( - WebPkiClientVerifier::builder(Arc::new(root_store.clone())) - .build() - .unwrap(), - root_store, - ) -} diff --git a/attested-tls/src/websockets.rs b/attested-tls/src/websockets.rs deleted file mode 100644 index 7669787..0000000 --- a/attested-tls/src/websockets.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! An attested Websocket server and client -use std::{net::SocketAddr, sync::Arc}; -use thiserror::Error; -use tokio::net::{TcpListener, ToSocketAddrs}; -use tokio_tungstenite::{WebSocketStream, tungstenite::protocol::WebSocketConfig}; - -use crate::{ - AttestedTlsClient, AttestedTlsError, AttestedTlsServer, - attestation::{AttestationType, measurements::MultiMeasurements}, -}; - -/// Websocket message type re-exported for convenience -pub use tokio_tungstenite::tungstenite::protocol::Message; - -/// An attested Websocket server -pub struct AttestedWsServer { - /// The underlying attested TLS server - pub inner: AttestedTlsServer, - /// Optional websocket configuration - pub websocket_config: Option, - listener: Arc, -} - -impl AttestedWsServer { - pub async fn new( - addr: impl ToSocketAddrs, - inner: AttestedTlsServer, - websocket_config: Option, - ) -> Result { - let listener = TcpListener::bind(addr).await?; - - Ok(Self { - listener: listener.into(), - inner, - websocket_config, - }) - } - - /// Accept a Websocket connection - pub async fn accept( - &self, - ) -> Result< - ( - WebSocketStream>, - Option, - AttestationType, - ), - AttestedWsError, - > { - let (tcp_stream, _addr) = self.listener.accept().await?; - - let (stream, measurements, attestation_type) = - self.inner.handle_connection(tcp_stream).await?; - Ok(( - tokio_tungstenite::accept_async_with_config(stream, self.websocket_config).await?, - measurements, - attestation_type, - )) - } - - /// Helper to get the socket address of the underlying TCP listener - pub fn local_addr(&self) -> std::io::Result { - self.listener.local_addr() - } -} - -/// An attested Websocket client -pub struct AttestedWsClient { - /// The underlying attested TLS client - pub inner: AttestedTlsClient, - /// Optional websocket configuration - pub websocket_config: Option, -} - -impl AttestedWsClient { - /// Make a Websocket connection - pub async fn connect( - &self, - server: &str, - ) -> Result< - ( - WebSocketStream>, - Option, - AttestationType, - ), - AttestedWsError, - > { - let (stream, measurements, attestation_type) = self.inner.connect_tcp(server).await?; - let (ws_connection, _response) = tokio_tungstenite::client_async_with_config( - format!("wss://{server}"), - stream, - self.websocket_config, - ) - .await?; - - Ok((ws_connection, measurements, attestation_type)) - } -} - -impl From for AttestedWsClient { - fn from(inner: AttestedTlsClient) -> Self { - Self { - inner, - websocket_config: None, - } - } -} - -#[derive(Error, Debug)] -pub enum AttestedWsError { - #[error("Attested TLS: {0}")] - Rustls(#[from] AttestedTlsError), - #[error("Websockets: {0}")] - Tungstenite(#[from] tokio_tungstenite::tungstenite::Error), - #[error("IO: {0}")] - Io(#[from] std::io::Error), -} - -#[cfg(test)] -mod tests { - use futures_util::{StreamExt, sink::SinkExt}; - use tokio_tungstenite::tungstenite::protocol::Message; - - use super::*; - use crate::{ - attestation::{AttestationGenerator, AttestationType, AttestationVerifier}, - test_helpers::{generate_certificate_chain, generate_tls_config}, - }; - - #[tokio::test] - async fn server_attestation_websocket() { - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - - let server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), - AttestationVerifier::expect_none(), - ) - .unwrap(); - - let ws_server = AttestedWsServer::new("127.0.0.1:0", server, None) - .await - .unwrap(); - - let server_addr = ws_server.local_addr().unwrap(); - - tokio::spawn(async move { - let (mut ws_connection, _measurements, _attestation_type) = - ws_server.accept().await.unwrap(); - - ws_connection - .send(Message::Text("foo".into())) - .await - .unwrap(); - }); - - let client = AttestedTlsClient::new_with_tls_config( - client_config, - AttestationGenerator::with_no_attestation(), - AttestationVerifier::mock(), - None, - ) - .unwrap(); - - let ws_client: AttestedWsClient = client.into(); - - let (mut ws_connection, _measurements, _attestation_type) = - ws_client.connect(&server_addr.to_string()).await.unwrap(); - - let message = ws_connection.next().await.unwrap().unwrap(); - - assert_eq!(message.to_text().unwrap(), "foo"); - } -} From 3e9b8306f6fd2caa3b5c7a3fca54db476ed95749 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 27 Apr 2026 12:39:47 +0200 Subject: [PATCH 42/44] Bump attested-tls --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd73d6c..5336d40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,7 +193,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" +source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" dependencies = [ "anyhow", "az-tdx-vtpm", @@ -242,7 +242,7 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" +source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" dependencies = [ "anyhow", "attestation", @@ -2354,7 +2354,7 @@ dependencies = [ [[package]] name = "nested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" +source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" dependencies = [ "rustls", "tokio", @@ -2512,9 +2512,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -2547,9 +2547,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -2661,7 +2661,7 @@ dependencies = [ [[package]] name = "pccs" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#9fb3002d82918b85f780f37b5545c4e112e0e772" +source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" dependencies = [ "anyhow", "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", From f6b97b3e824f08295cbdc9503b0aab8221e5ccec Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 27 Apr 2026 13:11:28 +0200 Subject: [PATCH 43/44] Update readme and docker-compose for updated protocol --- README.md | 242 +++++++++++++++++++++++++-------------------- docker-compose.yml | 4 +- 2 files changed, 138 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 91e9b7b..6fcd6e1 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,124 @@ - # `attested-tls-proxy` This is a reverse HTTP proxy allowing a normal HTTP client to communicate with a normal HTTP server over a remote-attested TLS channel, by tunneling requests through a proxy-client and proxy-server which handle attestation generation and verification. -This is designed to be an alternative to [`cvm-reverse-proxy`](https://github.com/flashbots/cvm-reverse-proxy). Unlike `cvm-reverse-proxy` this uses post-handshake remote-attested TLS, meaning regular CA-signed TLS certificates can be used. +This is designed to be an alternative to [`cvm-reverse-proxy`](https://github.com/flashbots/cvm-reverse-proxy). Unlike `cvm-reverse-proxy`, this can use a regular PKI certificate on an outer TLS session while carrying attestation on an inner attested TLS session. + +The protocol primitives now live in the external [`flashbots/attested-tls`](https://github.com/flashbots/attested-tls) repository: -Details of the remote-attested TLS protocol are in [attested-tls/README.md](attested-tls/README.md). This is provided as a separate crate for other uses than HTTP proxying. +- [`attested-tls`](https://github.com/flashbots/attested-tls/tree/main/crates/attested-tls) for attested TLS certificate handling +- [`nested-tls`](https://github.com/flashbots/attested-tls/tree/main/crates/nested-tls) for the optional outer TLS session +- [`attestation`](https://github.com/flashbots/attested-tls/tree/main/crates/attestation) for attestation generation, verification, and measurement policies -The proxy-client, on starting, immediately connects to the proxy-server and an attestation-verification exchange is made. This attested-TLS channel is then re-used for all requests from that proxy-client instance. +It has four main subcommands: -It has three subcommands: -- `attested-tls-proxy server` - run a proxy server, which accepts TLS connections from a proxy client, sends an attestation and then forwards traffic to a target CVM service. -- `attested-tls-proxy client` - run a proxy client, which accepts connections from elsewhere, connects to and verifies the attestation from the proxy server, and then forwards traffic to it over TLS. -- `attested-tls-proxy get-tls-cert` - connects to a proxy-server, verify the attestation, and if successful write the server's PEM-encoded TLS certificate chain to standard out. This can be used to make subsequent connections to services using this certificate over regular TLS. +- `attested-tls-proxy server` - run a proxy server that exposes an inner attested TLS listener and optionally an outer nested-TLS listener, then forwards traffic to a target service. +- `attested-tls-proxy client` - run a proxy client that accepts local HTTP connections and forwards them to the proxy server over nested TLS or directly to the inner attested TLS listener. +- `attested-tls-proxy get-tls-cert` - connect to a proxy server, verify the remote attestation, and write the inner PEM-encoded TLS certificate chain to standard output. +- `attested-tls-proxy attested-get` - perform a single GET request through the attested channel and print the response body. -### How it works +## How It Works This works as follows: -1. The source HTTP client (eg: curl or a web browser) makes an HTTP request to a proxy-client instance running locally. -2. The proxy-client forwards the request to a proxy-server instance over a remote-attested TLS channel. -3. The proxy-server forwards the request to the target service over regular HTTP. -4. The response from the target service is sent back to the source client, via the proxy-server and proxy-client. -One or both of the proxy-client and proxy-server may be running in a confidential environment and provide attestations which will be verified by the remote party. Verification is configured by a measurements file, and attestation generation is configured by specifying an attestation type when starting the proxy client or server. +1. A source HTTP client such as `curl` or a web browser makes an HTTP request to a local proxy-client instance. +2. The proxy-client connects to the proxy-server over either: + - nested TLS: outer PKI TLS plus inner attested TLS, or + - inner-only mode: direct connection to the inner attested TLS listener with `--inner-session-only`. +3. The proxy server verifies the client attestation when configured to require it, extracts remote attestation from the peer certificate on the inner session, and forwards the HTTP request to the target service. +4. The target service response is returned through the proxy server and proxy client to the source client. -### Measurements File +One or both of the proxy client and proxy server may run in a confidential environment and provide attestations which are verified by the remote party. Verification is configured by a measurements file or by allowing a single remote attestation type. -Accepted measurements for the remote party can be specified in a JSON file containing an array of objects, each of which specifies an accepted attestation type and set of measurements. +## Measurements File + +Accepted measurements for the remote party can be specified in a JSON file containing an array of policy entries. Each entry specifies an accepted attestation type and a set of measurements. This aims to match the formatting used by `cvm-reverse-proxy`. -Details and examples of the measurements file format are [in the attested-tls documentation](attested-tls/README.md#measurements-file). +The canonical format is documented in the upstream [`attestation` crate README](https://github.com/flashbots/attested-tls/tree/main/crates/attestation#measurements-file). That document is the source of truth for: + +- current attestation type names +- preferred measurement field names +- legacy field names still accepted for compatibility +- `expected_any` versus legacy `expected` -If a measurements file is not provided, a single allowed attestation type **must** be specified using the `--allowed-remote-attestation-type` option. This may be `none` for cases where the remote party is not running in a CVM, but that must be explicitly specified. +If a measurements file is not provided, a single allowed attestation type **must** be specified using `--allowed-remote-attestation-type`. This may be `none` when the remote party is not running in a CVM, but it must be stated explicitly. -### Measurement Headers +## Measurement Headers -When attestation is validated successfully, the following headers are injected into the HTTP request / response making them available to the source client and/or target service. +When attestation is validated successfully, the following headers are injected into the HTTP request or response, making them available to the source client and target service. These aim to match the header formatting used by `cvm-reverse-proxy`. Header name: `X-Flashbots-Measurement` Header value: + ```json { "0": "48 byte MRTD value encoded as hex", "1": "48 byte RTMR0 value encoded as hex", "2": "48 byte RTMR1 value encoded as hex", "3": "48 byte RTMR2 value encoded as hex", - "4": "48 byte RTMR3 value encoded as hex", + "4": "48 byte RTMR3 value encoded as hex" } ``` Header name: `X-Flashbots-Attestation-Type` -Header value: an attestation type given as a string as described below. - -### Attestation Types - -These are the attestation type names used in the HTTP headers, and the measurements file, and when specifying a local attestation type with the `--client-attestation-type` or `--server-attestation-type` command line options. +Header value: an attestation type string such as `none`, `gcp-tdx`, `azure-tdx`, `qemu-tdx`, or `dcap-tdx`. -- `auto` - detect attestation type (used only when specifying the local attestation type as a command-line argument) -- `none` - No attestation provided -- `gcp-tdx` - DCAP TDX on Google Cloud Platform -- `azure-tdx` - TDX on Azure, with vTPM attestation -- `qemu-tdx` - TDX on Qemu (no cloud platform) -- `dcap-tdx` - DCAP TDX (platform not specified) +## Connection Model -## Protocol Specification +Proxy-client to proxy-server connections use TLS 1.3. The server can expose two different listeners: -A proxy-client client will immediately attempt to connect to the given proxy-server. +- `--inner-listen-addr` exposes the inner attested TLS listener. +- `--outer-listen-addr` exposes an optional outer nested-TLS listener that wraps the inner session with a regular PKI TLS session. -Proxy-client to proxy-server connections use TLS 1.3. +At least one of these listeners must be configured. If TLS certificate and key files are provided, they apply only to the outer listener, and `--outer-listen-addr` is required. -The protocol name `flashbots-ratls/1` must be given in the TLS configuration for ALPN protocol negotiation during the TLS handshake. Future versions of this protocol will use incrementing version numbers, eg: `flashbots-ratls/2`. +When the server runs without an outer listener, the inner attested certificate still needs a DNS identity. In that case, use `--inner-certificate-name` to control the certificate name embedded into the inner attested certificate. If an outer certificate is present, the server derives that identity from the outer certificate instead. -Immediately after the TLS handshake, an attestation exchange is made. Details of how this works are in the [attested-tls protocol spepcification](attested-tls/README.md#protocol-specification). +On the client side: -Following a successful attestation exchange, the client can make HTTP requests, and the server will forward them to the target service. +- default mode connects to the server's outer listener and verifies the outer PKI certificate before entering the inner attested TLS session +- `--inner-session-only` connects directly to the inner attested TLS listener -As described above, the server will inject measurement data into the request headers before forwarding them to the target service, and the client will inject measurement data into the response headers before forwarding them to the source client. +In both modes, attestation is taken from the peer certificate on the inner TLS session, then enforced against the configured measurement policy. - +## Dependencies and Feature Flags -## Dependencies and feature flags +The `azure` feature for Microsoft Azure attestation requires [tpm2](https://tpm2-software.github.io) to be installed. On Debian-based systems this is provided by [`libtss2-dev`](https://packages.debian.org/trixie/libtss2-dev), and on nix by `tpm2-tss`. This dependency is currently not packaged for MacOS, so it is not currently possible to compile or run with the `azure` feature on MacOS. -The `azure` feature, for Microsoft Azure attestation requires [tpm2](https://tpm2-software.github.io) to be installed. On Debian-based systems this is provided by [`libtss2-dev`](https://packages.debian.org/trixie/libtss2-dev), and on nix `tpm2-tss`. This dependency is currently not packaged for MacOS, meaning currently it is not possible to compile or run with the `azure` feature on MacOS. +This feature is disabled by default. Without it, verification of Azure attestations is not possible and Azure attestations will be rejected with an error. -This feature is disabled by default. Note that without this feature, verification of azure attestations is not possible and azure attestations will be rejected with an error. +## Trying It Out Locally -## Trying it out locally (without CVM attestation) +This example uses nested TLS on the outer session and `none` for attestation on both sides. -This might help give an understanding of how it works. - -1. Run the helper script to generate a mock certifcate authority and a TLS certificate for localhost signed by it. +1. Generate a local certificate authority and a TLS certificate for `localhost`. This requires `openssl` to be installed. -``` +```bash ./scripts/generate-cert.sh localhost 127.0.0.1 ``` -2. Start a http server to try this out with, on 127.0.01:8000 +2. Start a local HTTP server on `127.0.0.1:8000`. This requires `python3` to be installed. -``` +```bash python3 -m http.server 8000 ``` -3. Start a proxy-server: +3. Start the proxy server with both an inner and outer listener. -``` +```bash cargo run -- server \ - --listen-addr 127.0.0.1:7000 \ + --outer-listen-addr 127.0.0.1:7000 \ + --inner-listen-addr 127.0.0.1:7001 \ --server-attestation-type none \ --allowed-remote-attestation-type none \ --tls-private-key-path server.key \ @@ -122,12 +126,11 @@ cargo run -- server \ 127.0.0.1:8000 ``` -The final positional argument is the target address - in this case the python server we started in step 3. -Note that you must specify that you accept 'none' as the remote attestation type. +The final positional argument is the target address, in this case the Python server from step 2. -4. Start a proxy-client: +4. Start a proxy client that connects through the outer nested-TLS listener. -``` +```bash cargo run -- client \ --listen-addr 127.0.0.1:6000 \ --client-attestation-type none \ @@ -136,37 +139,60 @@ cargo run -- client \ localhost:7000 ``` -The final positional argument is the hostname and port of the proxy-server. -Note that we specified a CA root of trust. If you use a standard certificate authority you do not need this argument. +The final positional argument is the hostname and port of the proxy server's outer listener. `--tls-ca-certificate` is only used in nested-TLS mode. -5. Make a HTTP request to the proxy-client: +5. Make an HTTP request to the proxy client. -``` -curl 127.0.0.1:6000/README.md +```bash +curl http://127.0.0.1:6000/README.md ``` -Assuming you started the python http server in the directory of this repository, this should print the contents of this README. +Assuming you started the Python HTTP server in the repository root, this should print this README. -Since we just wanted to make a single GET request here, we can make this process simpler but using the `attested-get` command: +For a single request, `attested-get` is simpler: -``` +```bash cargo run -- attested-get \ - --url-path README.md + --url-path README.md \ --tls-ca-certificate ca.crt \ --allowed-remote-attestation-type none \ localhost:7000 ``` -This should also print the README file. This should work even if the proxy-client from step 5 is not running. +This should also print the README file. + +### Inner-Only Example + +If you want to connect directly to the inner attested TLS listener instead of nested TLS: + +```bash +cargo run -- server \ + --inner-listen-addr 127.0.0.1:7001 \ + --inner-certificate-name localhost \ + --server-attestation-type none \ + --allowed-remote-attestation-type none \ + 127.0.0.1:8000 +``` + +```bash +cargo run -- client \ + --listen-addr 127.0.0.1:6000 \ + --inner-session-only \ + --client-attestation-type none \ + --allowed-remote-attestation-type none \ + localhost:7001 +``` + +In inner-only mode the client does not accept `--tls-ca-certificate`, `--tls-private-key-path`, or `--tls-certificate-path`. -## CLI differences from `cvm-reverse-proxy` +## CLI Differences from `cvm-reverse-proxy` -This aims to have a similar command line interface to `cvm-reverse-proxy` but there are some differences: +This aims to have a similar command line interface to `cvm-reverse-proxy`, but there are some differences: - The measurements file path is specified with `--measurements-file` rather than `--server-measurements` or `--client-measurements`. - If no measurements file is specified, `--allowed-remote-attestation-type` must be given. -- `--log-dcap-quote` logs all attestation data (not only DCAP), but [currently] only remote attestation data, not locally-generated data. - +- The server splits listener configuration into `--inner-listen-addr` and optional `--outer-listen-addr`. +- `--log-dcap-quote` logs remote DCAP quotes into `quotes/`. ## Docker @@ -175,60 +201,64 @@ This aims to have a similar command line interface to `cvm-reverse-proxy` but th ```bash docker build -t attested-tls-proxy . -# With custom features (e.g., without azure/TPM): +# With custom features, for example without Azure/TPM support: docker build --build-arg FEATURES="" -t attested-tls-proxy . ``` -**Note for Apple Silicon (M1-M4) Mac users:** When building on ARM Macs, the Docker build will automatically compile without Azure/TPM features (`--no-default-features`) because the TPM libraries cannot be cross-compiled. For production builds with full Azure support, use an x86_64 system. +**Note for Apple Silicon (M1-M4) Mac users:** When building on ARM Macs, the Docker build automatically compiles without Azure/TPM features (`--no-default-features`) because the TPM libraries cannot be cross-compiled. For production builds with full Azure support, use an x86_64 system. ### Running -The same image supports all subcommands (server, client, get-tls-cert, etc.): +The same image supports all subcommands: ```bash # Show help docker run --rm attested-tls-proxy --help -# Run as server +# Run as server in nested-TLS mode docker run --rm attested-tls-proxy server \ - --listen-addr 0.0.0.0:443 \ - --target-addr 127.0.0.1:8080 \ + --outer-listen-addr 0.0.0.0:443 \ + --inner-listen-addr 0.0.0.0:7443 \ --tls-private-key-path /path/to/key.pem \ --tls-certificate-path /path/to/cert.pem \ - --allowed-remote-attestation-type none + --allowed-remote-attestation-type none \ + 127.0.0.1:8080 # Run as client docker run --rm attested-tls-proxy client \ --listen-addr 0.0.0.0:8080 \ - target-server:443 \ - --allowed-remote-attestation-type none + --allowed-remote-attestation-type none \ + target-server:443 ``` ### Testing with Docker Compose -A `docker-compose.yml` is provided to test the full proxy chain: - -1. **Generate test certificates:** - ```bash - mkdir -p certs && cd certs - ../scripts/generate-cert.sh proxy-server 127.0.0.1 - # Convert key to PKCS#8 format (required by the proxy) - openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in server.key -out server.pkcs8.key - mv server.pkcs8.key server.key - ``` - -2. **Start all services:** - ```bash - docker compose up --build - ``` - -3. **Test the proxy:** - ```bash - # Test via proxy-client (HTTP) - curl http://localhost:8080 - # Should return the nginx welcome page - - # Test TLS directly to proxy-server - openssl s_client -connect localhost:8443 -CAfile certs/ca.crt -servername proxy-server - # Should show "Verify return code: 0 (ok)" - ``` +A `docker-compose.yml` is provided to test the full proxy chain in nested-TLS mode. + +1. Generate test certificates: + +```bash +mkdir -p certs && cd certs +../scripts/generate-cert.sh proxy-server 127.0.0.1 +# Convert key to PKCS#8 format, required by the proxy +openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in server.key -out server.pkcs8.key +mv server.pkcs8.key server.key +``` + +2. Start all services: + +```bash +docker compose up --build +``` + +3. Test the proxy: + +```bash +# HTTP through proxy-client +curl http://localhost:8080 + +# Outer TLS directly to proxy-server +openssl s_client -connect localhost:8443 -CAfile certs/ca.crt -servername proxy-server +``` + +The `openssl s_client` command should show `Verify return code: 0 (ok)`. diff --git a/docker-compose.yml b/docker-compose.yml index 7913884..ce976a1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,8 @@ services: build: . command: - server - - --listen-addr=0.0.0.0:8443 + - --outer-listen-addr=0.0.0.0:8443 + - --inner-listen-addr=0.0.0.0:7443 - --tls-private-key-path=/certs/server.key - --tls-certificate-path=/certs/server.crt - --allowed-remote-attestation-type=none @@ -50,4 +51,3 @@ networks: ipam: config: - subnet: 172.28.0.0/16 - From af35ce3bce7a069604d1958d6a3dfffa7ee2f31c Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 8 May 2026 09:39:39 +0200 Subject: [PATCH 44/44] Update crates from attested-tls --- Cargo.lock | 37 +++++++++++++----- Cargo.toml | 3 +- attestation-provider-server/src/lib.rs | 3 +- attestation-provider-server/src/main.rs | 2 +- scripts/ca.crt | 30 ++++++++++++++ scripts/ca.key | 52 +++++++++++++++++++++++++ scripts/ca.srl | 1 + scripts/server.crt | 25 ++++++++++++ scripts/server.csr | 15 +++++++ scripts/server.key | 28 +++++++++++++ src/lib.rs | 43 ++++++++++++-------- src/main.rs | 6 +-- 12 files changed, 210 insertions(+), 35 deletions(-) create mode 100644 scripts/ca.crt create mode 100644 scripts/ca.key create mode 100644 scripts/ca.srl create mode 100644 scripts/server.crt create mode 100644 scripts/server.csr create mode 100644 scripts/server.key diff --git a/Cargo.lock b/Cargo.lock index 5336d40..84aae56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,7 +193,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" +source = "git+https://github.com/flashbots/attested-tls?branch=main#dab9db727b1436c0b9f066562ff625535f9c2234" dependencies = [ "anyhow", "az-tdx-vtpm", @@ -220,6 +220,7 @@ dependencies = [ "tokio-rustls", "tracing", "tss-esapi", + "ureq", "x509-parser 0.18.1", ] @@ -242,7 +243,7 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" +source = "git+https://github.com/flashbots/attested-tls?branch=main#dab9db727b1436c0b9f066562ff625535f9c2234" dependencies = [ "anyhow", "attestation", @@ -254,7 +255,8 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tracing", - "x509-parser 0.18.1", + "webpki-roots 1.0.7", + "x509-parser 0.16.0", "yasna 0.5.2", ] @@ -294,7 +296,8 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", - "webpki-roots", + "webpki-roots 1.0.7", + "x509-parser 0.16.0", "x509-parser 0.18.1", ] @@ -1828,7 +1831,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.7", ] [[package]] @@ -2354,7 +2357,7 @@ dependencies = [ [[package]] name = "nested-tls" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" +source = "git+https://github.com/flashbots/attested-tls?branch=main#dab9db727b1436c0b9f066562ff625535f9c2234" dependencies = [ "rustls", "tokio", @@ -2661,7 +2664,7 @@ dependencies = [ [[package]] name = "pccs" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=main#be7c094f080a1bc36e3daed9e1bbb7278d1cf7c1" +source = "git+https://github.com/flashbots/attested-tls?branch=main#dab9db727b1436c0b9f066562ff625535f9c2234" dependencies = [ "anyhow", "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", @@ -3172,7 +3175,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 1.0.7", ] [[package]] @@ -3297,6 +3300,7 @@ dependencies = [ "aws-lc-rs", "brotli", "brotli-decompressor", + "log", "once_cell", "ring", "rustls-pki-types", @@ -4211,11 +4215,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ "base64 0.22.1", + "flate2", "log", "once_cell", + "rustls", + "rustls-pki-types", "serde", "serde_json", "url", + "webpki-roots 0.26.11", ] [[package]] @@ -4399,9 +4407,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.7", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index c8317fa..7144059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ pccs = { git = "https://github.com/flashbots/attested-tls", branch = "main" } tokio = { version = "1.50.0", features = ["full"] } tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws_lc_rs"] } x509-parser = { version = "0.18.0", features = ["verify"] } +x509-parser-016 = { package = "x509-parser", version = "0.16", features = ["verify"] } thiserror = "2.0.17" clap = { version = "4.5.51", features = ["derive", "env"] } rustls-pemfile = "2.2.0" @@ -33,7 +34,7 @@ serde = "1.0.228" reqwest = { version = "0.12.24", default-features = false, features = [ "rustls-tls-webpki-roots-no-provider", ] } -webpki-roots = "1.0.4" +webpki-roots = "1.0.7" tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["env-filter", "json"] } axum = "0.8.6" diff --git a/attestation-provider-server/src/lib.rs b/attestation-provider-server/src/lib.rs index d3dc976..a7134c1 100644 --- a/attestation-provider-server/src/lib.rs +++ b/attestation-provider-server/src/lib.rs @@ -44,8 +44,7 @@ async fn get_attest( let attestation = shared_state .attestation_generator - .generate_attestation(input_data) - .await? + .generate_attestation(input_data)? .encode(); Ok((StatusCode::OK, attestation)) diff --git a/attestation-provider-server/src/main.rs b/attestation-provider-server/src/main.rs index 661ca2f..276d43c 100644 --- a/attestation-provider-server/src/main.rs +++ b/attestation-provider-server/src/main.rs @@ -78,7 +78,7 @@ async fn main() -> anyhow::Result<()> { server_attestation_type, } => { let attestation_generator = - AttestationGenerator::new_with_detection(server_attestation_type, None).await?; + AttestationGenerator::new_with_detection(server_attestation_type, None)?; let listener = TcpListener::bind(listen_addr).await?; diff --git a/scripts/ca.crt b/scripts/ca.crt new file mode 100644 index 0000000..dc41826 --- /dev/null +++ b/scripts/ca.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFHjCCAwagAwIBAgIUasHwibWCoGWFr7ZRyXg2VKoHFnMwDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKTXkgVGVzdCBDQTAeFw0yNjAzMTkwODI1MjJaFw0zNjAz +MTYwODI1MjJaMBUxEzARBgNVBAMMCk15IFRlc3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDHufLMLSQs0fSnCRpYuKmdGSi/EkpZcPxO4gFiI6bn +8hmXiSdyJrhtq8KSLxzgzNiGBbZorjjRhzXAYGF+/kB5XMbp8bdd8EfuxWoFWz7u +sjka5B+38vbd3U+u9gSbBQLln3qQnf0SFadr1BNTHI/Mc725x1z3eO2V5EmJfZcJ +Ea4W+PkXPUULk/RKdcQjb5vJ/oEESDeUZHyqPqpj817d5vpNnMDT2s7JiRkQOtiR +Wj2gqt9A5FHXg3djeD32ZV6W2b1dmscGMOrmEWobSp9B02abmTwbUne93zh+a63f +n5iwIx13WruSCh2tplOVfodHE+LCLQFy7nTmjNnyOwD4MSxymq8h/VaLYW/eGmTH +uPbUUJrAOWpijYlvSPKiQE3gnDiIqWpSZPlN/vA92TcN+KaRfJYMxa8aw0u9eizJ +ScEsGmXnpKJv8mS7kg8ZilWYLVQZfGSuWRLfo/l8SfMUzmVWfrQ7ua63yvnvmc69 +pGgtUuqeztPtNG+JvmQ7tFo6ohZvEaLfSXRvcN1sOapx0wso0QQ+wK+a+wK69Neo +iMCRmN11v7xnbhGoTfN2NdaBwcUYjabx91e8BQgknkJ/iC4igqIJ87LJ/iAAF2of +sh5LmM0OnL5lADvWvCHkVoerBhcR+8zDMzwroabyBm0gabFj4Tpb+ZqlnarxxvoP +sQIDAQABo2YwZDAdBgNVHQ4EFgQU7Itauy5liUe7QQ1AaWAACvFcVP4wHwYDVR0j +BBgwFoAU7Itauy5liUe7QQ1AaWAACvFcVP4wEgYDVR0TAQH/BAgwBgEB/wIBADAO +BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHS1Dh4p1iD56DVomOuS +uiYxxEzZJSwdvDVHEDQxIGZZX8fMHM+fLxSPP4fnokFWTELRb9glfZU0ejiIbjlX +YHIO/RS+t89nTW226Y0CMq6kcAAbg4S9OL0qap8RNOwU9jnLDuOFiDkCamaP6PcJ +x1HAT6wevHLdpkQCOoi40g9RVWPQq1sa15Up1KNoQdfVdC32AUgUYlbwG2pGeRJ5 +Tou5aLbRzKZEM/055UFO87h9nG2SJEOXXTu0vz7a5hKpJFdj7ATvs7ltwMBsUDml +r5QVgdXVnL5RUawLS94UYMu/cjmtD2tAvssrjLZJ/G554B7dAFr7LEnPJi8mnnHB +k8yGVype1UpdxBmqzjzpYigUi5eMUZyNAG+XTQW767lZzG3JMqBsh4B9XI8+T82O +mrvXzRlNoJ/EVzQZtH38NbMT1Uw1u1/F6fcGvhoUMvEn8zHStJNopTcEU0o5X2Rk +/bK8zmnjhYoCm7BjIZePNUdAv8/N+mOaDc712/JhcO7/+VlSEQ5kt2uxRBd3mAss +SJQwFQiqPbgRK6ch2VYNq5u3QJgPlB+SPaPRBYC4BvFT8nEa4sXQXJb9XCDOxogC +WLPLWQc8yV0qVW8UzesLQhqIBPILMo/tklPU/DbEcJ9z4AaLJfVjlUsqHQ3GxROM +wDL3nF6j2pvnyL2Qma1SagDT +-----END CERTIFICATE----- diff --git a/scripts/ca.key b/scripts/ca.key new file mode 100644 index 0000000..ae9bb9c --- /dev/null +++ b/scripts/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDHufLMLSQs0fSn +CRpYuKmdGSi/EkpZcPxO4gFiI6bn8hmXiSdyJrhtq8KSLxzgzNiGBbZorjjRhzXA +YGF+/kB5XMbp8bdd8EfuxWoFWz7usjka5B+38vbd3U+u9gSbBQLln3qQnf0SFadr +1BNTHI/Mc725x1z3eO2V5EmJfZcJEa4W+PkXPUULk/RKdcQjb5vJ/oEESDeUZHyq +Pqpj817d5vpNnMDT2s7JiRkQOtiRWj2gqt9A5FHXg3djeD32ZV6W2b1dmscGMOrm +EWobSp9B02abmTwbUne93zh+a63fn5iwIx13WruSCh2tplOVfodHE+LCLQFy7nTm +jNnyOwD4MSxymq8h/VaLYW/eGmTHuPbUUJrAOWpijYlvSPKiQE3gnDiIqWpSZPlN +/vA92TcN+KaRfJYMxa8aw0u9eizJScEsGmXnpKJv8mS7kg8ZilWYLVQZfGSuWRLf +o/l8SfMUzmVWfrQ7ua63yvnvmc69pGgtUuqeztPtNG+JvmQ7tFo6ohZvEaLfSXRv +cN1sOapx0wso0QQ+wK+a+wK69NeoiMCRmN11v7xnbhGoTfN2NdaBwcUYjabx91e8 +BQgknkJ/iC4igqIJ87LJ/iAAF2ofsh5LmM0OnL5lADvWvCHkVoerBhcR+8zDMzwr +oabyBm0gabFj4Tpb+ZqlnarxxvoPsQIDAQABAoICACj9qm2qNBr4yk//58beu3zx +HXI9pEHAFSBxdRw5ufcvsn3t2cktVju3/Tp4beJbWHMFOB865pFmQStm/IuOThg6 +aN65y8r9Vh4UqUJJLFzb+ilhOXtM48q8Ma70chSIzkPnW8XTjw2HoTFZuM+ddIvf +E0jOOG/YA1b4n/kWbAmh9ctkNOdGnWWXa5NeoS8uqFgoIj2JPtL3XrioHNtfpxA+ +6A9GxTKV3UvyldDFaw5F6ZEQkLpmQb/MDRQ+qOTpgCTQsnNEIWXNY2BtoYvRYGcn +po73v6TR45518kfL/McBH9rCkjkdR5Lc/aNWmB/99HCrjaYRi5MViquIq/BxmKx6 +uhsSwOIldu1lzOuDyEu5u/8BDXGnVfkRzmsAsdmC5bIF64KW76zmPKBxpTvSfLgI +9DMK0iU3rR3uK/QXhzstpNl1+pyltNBZAkAMeNdgz+JBC7MN6WHRvEq/5AQkGOOl +a89XZrbG3dl6d7js9KrvIdwfn3+5HbK1qlj4WFgh3kOgPEw7HriT/NXWx9e23ILd +OQ89CjncxGPZf/Hntbs0RoQbTiRjZFSNZULH6zKYno9uffUsQzRPbPdZy4Fi7c/m +VRG/WP919wFbCAzvLcMXu6Ke8B6JIV3fEeu1giwf/+1mfof+Znb9AFRuH13/fQoE +mCCeuRMopWXlovPE4h17AoIBAQDpaUke+1g79I0MQtFWeubb73GzvX/lmgbcAf3E +UE+pXqeINGPrBtc4EVsPbwguZ4to4N6ExBerVXzzehoHEmL3QJZpqIvv219s755A +ouHtBjEaEhtd2lTmf9WNxjQq0rJWJqqKTg67wljqF86iSVruleO94y+9fyJjO+X7 +rlYcKPqnRuGZWQLCupL8dHTefQUpn9Km/jXkwqfB77PN8N7laHV0XZSxIUdUdccH +YDj8p6Xjo4GhMXXCsoZnT7vyYLtKqsoDpd5Wqc3yD2HWzcQRJEXLLZM9uke4pUuZ +fbPUarWGKdbxvt7xSjceFi9YQ1kpIANI6gQYqkceRTzfydtXAoIBAQDbDiBXNTQE +pKOoB2z2ZF0NL99yl/CJas+Mzj1H08Z5rVyU4uSkdgDOV2Hc3kfIPnOUXTzoUuww +geDSE5T5voFyQgWnX8pzoRVqhjbf85mcd73FT17q8aBQUqRyx7LRKIQ6V3T0nfA1 +kpsI8YTPLenFO3xwH0jqE+JmwB4qgljaUGqJdnEhtZ7/unbHkfZ+7RlXawrUpLGH +BnOmBY33rb0V+We8CwYc+LXiK4krb1L2VkjDOJP1uu8uFVN7p4rbANJejfR2I8Cr +SpC4g09npKkaCiVsu3EIGGmrNZ0pIkAXwdKTe/A7e6y2DtHRcRtRjwNuZV6ZcUz8 +2+EqlKmIvZA3AoIBAQCiQIsQ212xh8UIseX9PMAAQQmEDYW7oH+GLmguMDnAHclN +uEWA2id/lSj8qI1CXL4fMLneBsYBVxLd2ZIAOnrLL32fgweLu64w9rzGZ7OvZW96 +lP2rFxuPg2t6+z3GSuKnWoeQTsSVJntSdywvydhJI67FkAjerGLGIpwzgTPgrWfY +IkCurZ5qqPGGRpn7E2MkVTJE3U+vbtTYznzp+renNx72vhqfqud/wcOR0AStNQjv +Hfj9iXk314vwgw++ZBmtxN1a/dF54t4Dl0fG3xCdbd6KZ2sELNqwc08DKst4LJl0 +4t7E+47UomeAJaHRtDdljBhcdjhUwQFg/HZdhl9PAoIBACWlbNtv8kul+9ZByHEj +2HYOtykbgaQsLhU10IRKmf6JU7Lmt4KoIQVVMSVjMhTw3q4tw+zta1f+yiNwaBbv +rZF0VUJpjQKIOYUAIeglFk40qfGB7X8VHYcQXOC4/ztA6lCYfSoZBpI+atSDpV5x +to5F5eHjDoXj+vHLrKODmT+TuGt9Zn3zl6q9YfJ0XFd96fADHm5jC3t4GWH77GUX +f7qKDVDc2CUcfS2sa7uCO1DIjWK3k34UrUxPL/S9W3i90Gmdndi71j3Z57s+HeRj +AQjPm5TvNcK9Z1OcODE8iKVMkyLzltqyK4FyVI7ZEGfY2y8azsIyHZgLNcw6llRA +t8sCggEATgusL2btvhWur/oHIfG+p43CXNkQNFIpHWyVZBSMlUpokQ4DZ98XRKEW +PHd2ywi2MTFo6K08YwXW6rnogmPSRWAsgria79NtbKTaYkVef6mqABV+lAySRNn0 +p6WSsH3liz/xcQebi87S5tiFkhYF/ahRJzrKErghFOmCwcW/cRQOKZycUQeEfXVh +NSQXZ6cWMS7VnriGfyyAiVHzhXmxk1Ogu/v5HCk7g6mOstHjLvbTwkDLvgmQuUd5 +RZVMv3+PtKAUCgrA0k57Rl18WSrSrovR0LOuKyB5prpfFAXGvxnSkbvFmkI3IW2t +z84u+XcCzbUkgUeIDXeUfMiwxZ+w8A== +-----END PRIVATE KEY----- diff --git a/scripts/ca.srl b/scripts/ca.srl new file mode 100644 index 0000000..8260bdb --- /dev/null +++ b/scripts/ca.srl @@ -0,0 +1 @@ +5670929BAEB0868D972E0E317E9624717A9E95D8 diff --git a/scripts/server.crt b/scripts/server.crt new file mode 100644 index 0000000..cb4261e --- /dev/null +++ b/scripts/server.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIERDCCAiygAwIBAgIUVnCSm66who2XLg4xfpYkcXqeldgwDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKTXkgVGVzdCBDQTAeFw0yNjAzMTkwODI1MjJaFw0yNzAz +MTkwODI1MjJaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKbair3A3rtq2ey277w42UtZJvBBRRffN61kSPL61cKk +MbFbqzUs5o5YHQrz6g+d8+PJGnJXqZR2FrF9FyXv3K+vhrgkmzRV8LT7udcg6r1Y +iurL0+y4kxYZBMvP9sqQr1DOqTiRDVMicIi5Jy72f84PBf0jgDa71QBrLDZQ4Xzf +m/16XZK/H/UnCoXplXLlnarvzZxD5RzM3cp/+ddB7A5TFGndDy4tuqBmIgC8/GiV +zQW3ajvGp3FwQxNOPZ+3Hs4M+QT1bGkF72TyJvAT4iixfLXThfn8+sPloORZZNyz +HvA59WCCadERAsXEp677XHTHBzZijqpdTqWPIX1KGT0CAwEAAaOBjDCBiTAJBgNV +HRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAaBgNVHREE +EzARgglsb2NhbGhvc3SHBH8AAAEwHQYDVR0OBBYEFE6xE9yd1cF4yhk7qFT7x88L +7EY2MB8GA1UdIwQYMBaAFOyLWrsuZYlHu0ENQGlgAArxXFT+MA0GCSqGSIb3DQEB +CwUAA4ICAQBQsQENd4tgwxsVrHdxSuNFNshO7GfqiVs4E0kYznZ5WVXX1nu5rmr6 +BS2FVYV0hrlL7euuqQe1Fy9SHIP9oAxEoIpAXYK0zzkx8APHg70uLIJXnZpSmIGf +MsD4MvTd4VTR+hIRXG8RYYW+/DU657qgXhMe8/dizQG4eluLYHBI1fYbFIbxx2pl +HgkZBhdw5DM9Gq/iQ+Lb0szSlZ5sQoUxMAhhT6UxO74Bkmj3WNKr9wr95GFt2F14 +KrprT4l6wPu5SBVe2jB9thYBGE8EROM87ibBjpJcbwTiIRqvVH6xnYPfSTO0C1SA +DL9divHk+OrS1LhG/m4wBUkg2PksIhqDOD4rFyIWoYK/B8fgcv0Feo+oFKGE4j4C +2rmsP7qTfSnzly7pz+tY6rfmimq0DV3TDSEV+8JMIb1ZG0T8+ueP53ru9GrWgr1s +M0qLSKhqQC61q0PVASf30RQ1DF3p5lCShlRzoei3ZynXdhW3mNLWKMks4RX6hWIl +aKbXe3Cf3ATqogjnQVfwySjCIcvken7NPVtU0DgJZR4lmzTuQxiL6O4sbCRXdVyr +sgyJGQwn+vBQIm5L+X++ZcQmzedFbnGge1ApNK4S/+CUc5apRYZRqkjsmAjJ1uwo +yHdzdnlZwaUvg6enzHqXpV1p+XYNnky6ZPDdivtbJlI/5HxdwlnS1Q== +-----END CERTIFICATE----- diff --git a/scripts/server.csr b/scripts/server.csr new file mode 100644 index 0000000..7ed916e --- /dev/null +++ b/scripts/server.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAptqKvcDeu2rZ7LbvvDjZS1km8EFFF983rWRI8vrV +wqQxsVurNSzmjlgdCvPqD53z48kacleplHYWsX0XJe/cr6+GuCSbNFXwtPu51yDq +vViK6svT7LiTFhkEy8/2ypCvUM6pOJENUyJwiLknLvZ/zg8F/SOANrvVAGssNlDh +fN+b/Xpdkr8f9ScKhemVcuWdqu/NnEPlHMzdyn/510HsDlMUad0PLi26oGYiALz8 +aJXNBbdqO8ancXBDE049n7cezgz5BPVsaQXvZPIm8BPiKLF8tdOF+fz6w+Wg5Flk +3LMe8Dn1YIJp0RECxcSnrvtcdMcHNmKOql1OpY8hfUoZPQIDAQABoAAwDQYJKoZI +hvcNAQELBQADggEBAIMkTcdtKfuLdJAusZfiTuNCXLfaIIL8homwUxjS+0uBMNiq +bdzIm5yGc0qpDVAGXIwbzNTnIdB4gLm0BQmjVP6p2fwXqh1jIFhTn1sRGK6NKddq +JiTUII2Bqb1ocX8JJEeaJ9ChfckjOfRtpRgslUVAcQi/mfd3JPAPHM4GGYdsmt+q +YivXv2BWzvhjCu+gkVCvO/Zou6A8We2C626oo7qXkZEWWCU82uFlnJ5UMlGjCy5P +zDEXv+xXG6PjgtaJPz9X6EtJ+5Uk/svw268e3EyxBcSPeSnnij+81iyiud73KLrk +r8O1Pa4chtph2gXFZDeFmqXP5Xct0XriBYMPZdU= +-----END CERTIFICATE REQUEST----- diff --git a/scripts/server.key b/scripts/server.key new file mode 100644 index 0000000..70ef271 --- /dev/null +++ b/scripts/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCm2oq9wN67atns +tu+8ONlLWSbwQUUX3zetZEjy+tXCpDGxW6s1LOaOWB0K8+oPnfPjyRpyV6mUdhax +fRcl79yvr4a4JJs0VfC0+7nXIOq9WIrqy9PsuJMWGQTLz/bKkK9Qzqk4kQ1TInCI +uScu9n/ODwX9I4A2u9UAayw2UOF835v9el2Svx/1JwqF6ZVy5Z2q782cQ+UczN3K +f/nXQewOUxRp3Q8uLbqgZiIAvPxolc0Ft2o7xqdxcEMTTj2ftx7ODPkE9WxpBe9k +8ibwE+IosXy104X5/PrD5aDkWWTcsx7wOfVggmnREQLFxKeu+1x0xwc2Yo6qXU6l +jyF9Shk9AgMBAAECggEALoo2uMbbWZtsPcKpC+8vbPzpB4qtIwN4HD0ku+GLTkdO +NRJEpwhnJr0dHLj8SG7eYGLvpYUeH/LwUYF6u3I0NCzZfvN29xohkdnE9GSSFU5l +MSi5bAXvwPIpjE/tnoLM49VBDi4ncVDh4sECVO8cpvU5zmngkWPC/eLm6h+VRacF +weiwmBQ77CeERgUJJs50bCGyuMpYzpmnn5otxGHqNcFNtyKzPtEVJed2bKhEjhRn +gWQY6TM9WzMN37BGEBFFM0U6p7B7MqEQoYTmqdTe1cnvQpWUJ8ouubqTKk1Jl8CX +ldbb8Gko6Y7O3ZRZerUtJHCyGhRpSSXEO7szOFGPYQKBgQDglWAgFuBs8mcCjvJn +aEXofscy7DBHTfZH4xrWY6YBJ8/ew4a6fhDwrOc4xoCcwTxDq+AFTGxlZ635KgAO +vcxyBhYqioIFWmF4xnyfH1SA/8JsdxMWYo9hiBd1yazqgmQ31z+3sJa4pYFMxx6n +l5lDL8+PhaF7yfwZU4bDCDLN5wKBgQC+McmKN5Ku+NfGy3pFvTaS2oLrp32w8+Mo +ikTMFZ2615ALhLiNWz3InvTvcZl1MATfM1zoY8+ULoAsS7K1xJKnVv+vtjvK8uj7 +ri22qe8Y3C3YKhHKfR+H7OobzJQ5TUgT7vx07kC1RwOMl6SrS0Y0P0h9QNofWLf1 +xo6aAr+TOwKBgEnyhVsrjSvySN67yK8p01LxY4+t21uvHPegnLaYyxE4VaaoxNTl +K5jDpcK5AaIbskcp8bJs7ogZMlyHastvzUCCJoxNXPB8SPnmiSQvwbhT/mWOc5d8 +Tm+zyt260RfKZUtJsa4/E/GqdT+dlMdkEE/iDzrXqktDP6HrMcVIDspjAoGBAKHd +P1d5KkXtE75k53+4o1xhyPADUG4rcdTLr/c8IkukLzwOTP5Ie5Fk7rFlLaPVJSBL +1zMT1tKWrJCGL+aQD/uIh/wjjEmRZ4TiXJdLkmfG4uFIFHIPAKmukCLufesygTgt +uP9O0RU2Bag+Wm9JlcrbXLb0XW42FAGAZVZfJmmHAoGADpOKsUSJIaOGNOLyim9r +T/ZezusLVMkDl+88pvoWsW07EHsno0o+7FzkjMa4Q/fyGe3BNeK8EbZCR82atM/4 +7LUhcyc5KaWUO1xj6Jf2hHrrmBvaHgzZEdCOJQIK0CgB0SXBw0z2/LtRCD9qjTeH +9rZFFnp1xuYQDqlWBg8H4Os= +-----END PRIVATE KEY----- diff --git a/src/lib.rs b/src/lib.rs index 7b0e3db..5c15792 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,7 +199,7 @@ pub async fn get_inner_tls_cert_with_config( let domain = server_name_from_host(&server_name)?; - let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; + let attested_cert_verifier = AttestedCertificateVerifier::try_default(attestation_verifier)?; let inner_client_config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .dangerous() @@ -427,8 +427,7 @@ impl ProxyServer { Some(remote_cert_chain) => remote_cert_chain .first() .and_then(|cert| { - match AttestedCertificateVerifier::extract_custom_attestation_from_cert(cert) - { + match extract_custom_attestation_from_cert(cert) { Ok(attestation) => Some(attestation), Err(err) => { warn!( @@ -459,7 +458,7 @@ impl ProxyServer { match server_connection.peer_certificates() { Some(remote_cert_chain) => remote_cert_chain.first().and_then(|cert| { - match AttestedCertificateVerifier::extract_custom_attestation_from_cert(cert) { + match extract_custom_attestation_from_cert(cert) { Ok(attestation) => Some(attestation), Err(err) => { warn!("Failed to extract remote attestation from certificate: {err}"); @@ -700,7 +699,8 @@ impl ProxyClient { return Err(ProxyError::ClientAuthMisconfigured); } - let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; + let attested_cert_verifier = + AttestedCertificateVerifier::try_default(attestation_verifier)?; let mut inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { let inner_cert_resolver = build_attested_cert_resolver( @@ -761,7 +761,8 @@ impl ProxyClient { attestation_verifier: AttestationVerifier, cert_chain: Option>>, ) -> Result { - let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; + let attested_cert_verifier = + AttestedCertificateVerifier::try_default(attestation_verifier)?; let mut inner_client_config = if let Some(cert_chain) = cert_chain.as_ref() { let inner_cert_resolver = build_attested_cert_resolver( @@ -1094,7 +1095,7 @@ impl ProxyClient { .peer_certificates() .ok_or(ProxyError::NoCertificate)?; - AttestedCertificateVerifier::extract_custom_attestation_from_cert( + extract_custom_attestation_from_cert( remote_cert_chain.first().ok_or(ProxyError::NoCertificate)?, ) .map_err(ProxyError::from) @@ -1218,18 +1219,25 @@ fn certificate_identity_from_chain( hostname_from_cert(cert_chain.first().ok_or(ProxyError::NoCertificate)?) } +fn extract_custom_attestation_from_cert( + cert: &CertificateDer<'_>, +) -> Result { + let (_, cert) = x509_parser_016::parse_x509_certificate(cert.as_ref()) + .map_err(|err| rustls::Error::General(format!("invalid certificate encoding: {err}")))?; + + AttestedCertificateVerifier::extract_custom_attestation_from_cert(&cert) +} + async fn build_attested_cert_resolver( attestation_generator: AttestationGenerator, certificate_name: String, ) -> Result { - Ok(AttestedCertificateResolver::new( - attestation_generator, - None, - certificate_name, - vec![], - Duration::from_secs(ATTESTED_CERTIFICATE_VALIDITY_SECS), + Ok( + AttestedCertificateResolver::build(&certificate_name, attestation_generator) + .with_subject_alt_names(vec![]) + .with_certificate_validity(Duration::from_secs(ATTESTED_CERTIFICATE_VALIDITY_SECS)) + .finish()?, ) - .await?) } async fn build_inner_server_config( @@ -1245,7 +1253,8 @@ async fn build_inner_server_config( .await?; let mut inner_server_config = if client_auth { - let attested_cert_verifier = AttestedCertificateVerifier::new(None, attestation_verifier)?; + let attested_cert_verifier = + AttestedCertificateVerifier::try_default(attestation_verifier)?; ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .with_client_cert_verifier(Arc::new(attested_cert_verifier)) .with_cert_resolver(Arc::new(inner_cert_resolver)) @@ -1501,7 +1510,7 @@ mod tests { }); let attested_cert_verifier = - AttestedCertificateVerifier::new(None, AttestationVerifier::mock()).unwrap(); + AttestedCertificateVerifier::try_default(AttestationVerifier::mock()).unwrap(); let mut client_config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .dangerous() @@ -1736,7 +1745,7 @@ mod tests { }); let attested_cert_verifier = - AttestedCertificateVerifier::new(None, AttestationVerifier::mock()).unwrap(); + AttestedCertificateVerifier::try_default(AttestationVerifier::mock()).unwrap(); let mut inner_client_config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .dangerous() diff --git a/src/main.rs b/src/main.rs index f02ab0a..6f04bd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -286,8 +286,7 @@ async fn main() -> anyhow::Result<()> { }; let client_attestation_generator = - AttestationGenerator::new_with_detection(client_attestation_type, dev_dummy_dcap) - .await?; + AttestationGenerator::new_with_detection(client_attestation_type, dev_dummy_dcap)?; let client = if inner_session_only { ProxyClient::new_inner_only( @@ -341,8 +340,7 @@ async fn main() -> anyhow::Result<()> { )?; let local_attestation_generator = - AttestationGenerator::new_with_detection(server_attestation_type, dev_dummy_dcap) - .await?; + AttestationGenerator::new_with_detection(server_attestation_type, dev_dummy_dcap)?; let server = ProxyServer::new( tls_cert_and_chain