diff --git a/Cargo.lock b/Cargo.lock index 0459228..84aae56 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" @@ -12,182 +18,18 @@ dependencies = [ ] [[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" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf7effe4ab0a4f52c865959f790036e61a7983f68b13b75d7fbcedf20b753ce" -dependencies = [ - "alloy-primitives", - "alloy-sol-macro", -] +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[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" +name = "alloc-stdlib" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce599598ef8ebe067f3627509358d9faaa1ef94f77f834a7783cd44209ef55c" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 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", + "alloc-no-stdlib", ] [[package]] @@ -242,212 +84,45 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[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" +version = "1.0.102" 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", -] +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "ark-std" -version = "0.3.0" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" -dependencies = [ - "num-traits", - "rand 0.8.5", -] +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] -name = "ark-std" -version = "0.4.0" +name = "arrayvec" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" -dependencies = [ - "num-traits", - "rand 0.8.5", -] +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "ark-std" -version = "0.5.0" +name = "asn1-rs" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ + "asn1-rs-derive 0.5.1", + "asn1-rs-impl", + "displaydoc", + "nom", "num-traits", - "rand 0.8.5", + "rusticata-macros", + "thiserror 1.0.69", + "time", ] -[[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.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 +132,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", + "synstructure", +] + [[package]] name = "asn1-rs-derive" version = "0.6.0" @@ -465,7 +152,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "synstructure", ] @@ -477,7 +164,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -494,7 +181,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -506,19 +193,20 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attestation" version = "0.0.1" -source = "git+https://github.com/flashbots/attested-tls?branch=peg%2Fadd-attestation-crate#4ebc03703510e65fd1317736b8887fc388860481" +source = "git+https://github.com/flashbots/attested-tls?branch=main#dab9db727b1436c0b9f066562ff625535f9c2234" dependencies = [ "anyhow", "az-tdx-vtpm", "base64 0.22.1", "configfs-tsm", - "dcap-qvl", + "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", @@ -532,7 +220,8 @@ dependencies = [ "tokio-rustls", "tracing", "tss-esapi", - "x509-parser", + "ureq", + "x509-parser 0.18.1", ] [[package]] @@ -554,30 +243,21 @@ dependencies = [ [[package]] name = "attested-tls" version = "0.0.1" +source = "git+https://github.com/flashbots/attested-tls?branch=main#dab9db727b1436c0b9f066562ff625535f9c2234" dependencies = [ - "alloy-rpc-client", - "alloy-transport-http", + "anyhow", "attestation", - "bytes", - "futures-util", - "http", - "http-body-util", - "hyper", - "hyper-util", - "parity-scale-codec", - "rcgen", + "ra-tls", + "rcgen 0.14.7", + "rustls", "serde_json", "sha2", - "tempfile", "thiserror 2.0.17", "tokio", - "tokio-rustls", - "tokio-tungstenite", - "tower-service", "tracing", - "url", - "webpki-roots", - "x509-parser", + "webpki-roots 1.0.7", + "x509-parser 0.16.0", + "yasna 0.5.2", ] [[package]] @@ -585,6 +265,7 @@ name = "attested-tls-proxy" version = "1.1.1" dependencies = [ "anyhow", + "attestation", "attested-tls", "axum", "bytes", @@ -594,12 +275,14 @@ dependencies = [ "hyper", "hyper-util", "jsonrpsee", + "nested-tls", "p256", + "pccs", "pem-rfc7468", "pin-project-lite", "pkcs1", "pkcs8", - "rcgen", + "rcgen 0.14.7", "reqwest", "rsa", "rustls-pemfile", @@ -613,26 +296,38 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", - "webpki-roots", - "x509-parser", + "webpki-roots 1.0.7", + "x509-parser 0.16.0", + "x509-parser 0.18.1", ] [[package]] -name = "auto_impl" -version = "1.3.0" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.108", + "aws-lc-sys", + "zeroize", ] [[package]] -name = "autocfg" -version = "1.5.0" +name = "aws-lc-sys" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] [[package]] name = "axum" @@ -790,21 +485,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" @@ -828,7 +508,7 @@ checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -855,6 +535,29 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[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 +567,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", +] + [[package]] name = "borsh" version = "1.6.0" @@ -884,7 +612,28 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.108", + "syn", +] + +[[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]] @@ -910,9 +659,6 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] [[package]] name = "cc" @@ -921,9 +667,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cc-eventlog" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "anyhow", + "digest", + "ez-hash", + "fs-err", + "hex", + "parity-scale-codec", + "serde", + "serde-human-bytes", + "serde_json", + "sha2", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -977,7 +742,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -986,6 +751,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" @@ -1005,15 +779,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187437900921c8172f33316ad51a3267df588e99a2aebfa5ca1a2ed44df9e703" [[package]] -name = "const-hex" -version = "1.17.0" +name = "console" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ - "cfg-if", - "cpufeatures", - "proptest", - "serde_core", + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -1042,6 +816,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" @@ -1052,38 +832,21 @@ dependencies = [ ] [[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 = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "core-foundation-sys", "libc", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "libc", + "cfg-if", ] [[package]] @@ -1116,12 +879,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" @@ -1153,9 +910,9 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", + "digest", "fiat-crypto", - "rustc_version 0.4.1", + "rustc_version", "subtle", ] @@ -1167,7 +924,41 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", +] + +[[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", +] + +[[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", ] [[package]] @@ -1179,7 +970,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/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1#f1dcc65371e941a7b83e3234833d23a1fb232ab1" dependencies = [ "anyhow", "asn1_der", @@ -1243,13 +1071,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", @@ -1265,7 +1107,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1277,17 +1119,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" @@ -1314,7 +1145,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1326,20 +1157,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" @@ -1381,7 +1203,45 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", +] + +[[package]] +name = "dstack-attest" +version = "0.5.8" +source = "git+https://github.com/Dstack-TEE/dstack.git?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#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?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "parity-scale-codec", + "serde", + "serde-human-bytes", + "sha3", + "size-parser", ] [[package]] @@ -1397,7 +1257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest 0.10.7", + "digest", "elliptic-curve", "rfc6979", "signature", @@ -1425,24 +1285,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" @@ -1451,7 +1293,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", "generic-array", "group", @@ -1463,6 +1305,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" @@ -1472,27 +1320,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]] @@ -1512,7 +1340,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1522,43 +1350,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.14" +name = "errify" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "4bb818c3c01af9cdeb367f7e92e290b9a080935cdc5fb6cc0c1193ae17032849" dependencies = [ - "libc", - "windows-sys 0.61.2", + "anyhow", + "errify-macros", ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "errify-macros" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9e87afa19e6030c2cf5514b00d5a242a3ea9492a2aa618635076914f5d15e7af" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] [[package]] -name = "fastrlp" -version = "0.3.1" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "arrayvec", - "auto_impl", - "bytes", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "fastrlp" -version = "0.4.0" +name = "ez-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +checksum = "42b3b3adc5fbbc9e21416d5b721b1bccb501a87d7b32ac89f2c7cea229d40772" dependencies = [ - "arrayvec", - "auto_impl", - "bytes", + "blake2", + "blake3", + "digest", + "md-5", + "sha1", + "sha2", + "sha3", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "ff" version = "0.13.1" @@ -1581,18 +1424,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" @@ -1600,16 +1431,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" [[package]] -name = "fnv" -version = "1.0.7" +name = "flate2" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] [[package]] -name = "foldhash" -version = "0.2.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" @@ -1635,6 +1470,21 @@ 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 = "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" @@ -1697,7 +1547,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -1730,12 +1580,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" @@ -1809,10 +1653,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" @@ -1826,6 +1666,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,13 +1718,22 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] @@ -1967,32 +1822,16 @@ name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "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", +dependencies = [ + "http", "hyper", "hyper-util", - "native-tls", + "rustls", + "rustls-pki-types", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", + "webpki-roots 1.0.7", ] [[package]] @@ -2014,11 +1853,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -2102,6 +1939,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" @@ -2123,15 +1966,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" @@ -2140,7 +1974,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -2151,8 +1985,18 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", - "serde", - "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]] @@ -2196,38 +2040,21 @@ 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" +name = "itoa" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "itertools" -version = "0.14.0" +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "either", + "getrandom 0.3.4", + "libc", ] -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - [[package]] name = "js-sys" version = "0.3.85" @@ -2340,7 +2167,6 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2", ] @@ -2353,16 +2179,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" @@ -2374,9 +2190,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" @@ -2427,17 +2243,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" @@ -2463,6 +2268,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", +] + [[package]] name = "memchr" version = "2.7.6" @@ -2500,6 +2315,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" @@ -2523,27 +2348,34 @@ dependencies = [ "equivalent", "parking_lot", "portable-atomic", - "rustc_version 0.4.1", + "rustc_version", "smallvec", "tagptr", "uuid", ] [[package]] -name = "native-tls" -version = "0.2.16" +name = "nested-tls" +version = "0.0.1" +source = "git+https://github.com/flashbots/attested-tls?branch=main#dab9db727b1436c0b9f066562ff625535f9c2234" +dependencies = [ + "rustls", + "tokio", + "tokio-rustls", + "tracing", +] + +[[package]] +name = "nix" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "memoffset", ] [[package]] @@ -2605,7 +2437,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -2647,13 +2479,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]] @@ -2674,9 +2515,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", @@ -2695,15 +2536,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" @@ -2715,9 +2550,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", @@ -2726,53 +2561,18 @@ 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" 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" @@ -2835,7 +2635,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -2858,14 +2658,26 @@ 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" +name = "pccs" +version = "0.0.1" +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)", + "hex", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.17", + "time", + "tokio", + "tracing", + "x509-parser 0.18.1", +] [[package]] name = "pem" @@ -2892,16 +2704,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" @@ -2954,7 +2756,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -3027,23 +2829,22 @@ dependencies = [ ] [[package]] -name = "primeorder" -version = "0.13.6" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "elliptic-curve", + "proc-macro2", + "syn", ] [[package]] -name = "primitive-types" -version = "0.12.2" +name = "primeorder" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "fixed-hash", - "impl-codec", - "uint", + "elliptic-curve", ] [[package]] @@ -3055,28 +2856,6 @@ 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", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.108", -] - [[package]] name = "proc-macro2" version = "1.0.103" @@ -3087,30 +2866,18 @@ dependencies = [ ] [[package]] -name = "proptest" -version = "1.10.0" +name = "proc-macro2-diagnostics" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" 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", + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", ] -[[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" @@ -3181,6 +2948,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?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#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" @@ -3206,7 +3010,6 @@ checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "serde", ] [[package]] @@ -3245,37 +3048,33 @@ 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" +name = "rcgen" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111325c42c4bafae99e777cd77b40dea9a2b30c69e9d8c74b6eccd7fba4337de" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ - "rustversion", + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.16.0", + "yasna 0.5.2", ] [[package]] name = "rcgen" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fae430c6b28f1ad601274e78b7dffa0546de0b73b4cd32f46723c0c2a16f7a5" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ "pem", "ring", "rustls-pki-types", "time", + "x509-parser 0.18.1", "yasna 0.5.2", ] @@ -3354,11 +3153,9 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -3370,7 +3167,6 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls", "tower", "tower-http", @@ -3379,7 +3175,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 1.0.7", ] [[package]] @@ -3413,13 +3209,22 @@ dependencies = [ ] [[package]] -name = "rlp" -version = "0.5.2" +name = "rmp" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "bytes", - "rustc-hex", + "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]] @@ -3435,7 +3240,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", @@ -3449,68 +3254,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]] @@ -3537,10 +3293,14 @@ dependencies = [ [[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 = [ + "aws-lc-rs", + "brotli", + "brotli-decompressor", + "log", "once_cell", "ring", "rustls-pki-types", @@ -3560,9 +3320,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", @@ -3574,6 +3334,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", @@ -3585,18 +3346,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" @@ -3625,16 +3374,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]] @@ -3657,53 +3397,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" @@ -3725,10 +3424,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", ] @@ -3760,7 +3460,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -3834,7 +3534,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -3845,7 +3545,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -3854,20 +3554,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" @@ -3898,10 +3588,32 @@ 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", ] +[[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?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#4f602dddc0542cd34da031c90ac0b3a560f316ed" +dependencies = [ + "anyhow", + "serde", + "thiserror 2.0.17", +] + [[package]] name = "slab" version = "0.4.11" @@ -3990,17 +3702,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" @@ -4012,18 +3713,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" @@ -4041,28 +3730,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]] @@ -4083,6 +3751,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?rev=4f602dddc0542cd34da031c90ac0b3a560f316ed#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" @@ -4136,7 +3823,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -4147,7 +3834,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -4217,9 +3904,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", @@ -4240,17 +3927,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]] @@ -4275,18 +3952,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" @@ -4389,9 +4054,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,20 +4066,20 @@ 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", - "syn 2.0.108", + "syn", ] [[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", @@ -4431,25 +4096,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" @@ -4520,53 +4166,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" @@ -4610,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]] @@ -4635,12 +4244,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" @@ -4690,12 +4293,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" [[package]] -name = "wait-timeout" -version = "0.2.1" +name = "vsock" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +checksum = "b82aeb12ad864eb8cd26a6c21175d0bdc66d398584ee6c93c76964c3bcfc78ff" dependencies = [ "libc", + "nix", ] [[package]] @@ -4768,7 +4372,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.108", + "syn", "wasm-bindgen-shared", ] @@ -4781,20 +4385,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" @@ -4817,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", ] @@ -4830,47 +4429,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" @@ -4889,6 +4453,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" @@ -4904,7 +4477,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]] @@ -4944,7 +4517,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", @@ -5158,16 +4731,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 = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.2", + "data-encoding", + "der-parser 9.0.0", + "lazy_static", + "nom", + "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 = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.1", "data-encoding", - "der-parser", + "der-parser 10.0.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.8.1", "ring", "rusticata-macros", "thiserror 2.0.17", @@ -5195,6 +4786,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" @@ -5232,7 +4829,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "synstructure", ] @@ -5253,7 +4850,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -5273,7 +4870,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", "synstructure", ] @@ -5294,7 +4891,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] @@ -5327,7 +4924,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 40a3ab3..7144059 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" @@ -11,10 +11,14 @@ 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"] } -tokio-rustls = { version = "0.26.4", default-features = false } +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, 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" @@ -30,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" @@ -45,14 +49,18 @@ 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 = "main", 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"] + +# Adds support for mock attestations for testing. Do not enable in production. +mock = ["attestation/mock"] [package.metadata.deb] maintainer = "Flashbots Team " 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/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 b7c5c13..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?; @@ -97,8 +97,9 @@ 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, + internal_pccs: None, }; let attestation_message = diff --git a/attested-tls/Cargo.toml b/attested-tls/Cargo.toml deleted file mode 100644 index 38a810e..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 } -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/add-attestation-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/add-attestation-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 9b1eafa..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_attestion() { - 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, - log_dcap_quote: 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 9b218a3..0000000 --- a/attested-tls/src/test_helpers.rs +++ /dev/null @@ -1,128 +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; - -/// 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) { - 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)) { - 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"); - } -} 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 - 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/attested_get.rs b/src/attested_get.rs index 27e7ad3..a2a0ad8 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 } @@ -69,14 +55,14 @@ 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, 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,17 +73,24 @@ 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 - let proxy_server = ProxyServer::new_with_tls_config( - cert_chain, - server_config, - "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: "localhost".to_string(), + }, + }), + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, ) .await .unwrap(); @@ -113,7 +106,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..abfef0b 100644 --- a/src/file_server.rs +++ b/src/file_server.rs @@ -1,23 +1,64 @@ //! 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; +/// 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, - cert_and_key: TlsCertAndKey, - listen_addr: impl ToSocketAddrs, - 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 { + listen_addr, + tls: OuterTlsMode::CertAndKey(cert_and_key), + }), + (Some(_), None) | (None, Some(_)) => { + return Err(ProxyError::NoListenersConfigured); + } + (None, None) => None, + }; let server = ProxyServer::new( - cert_and_key, - listen_addr, + outer_session, + inner_listen_addr, + inner_certificate_name, target_addr.to_string(), attestation_generator, attestation_verifier, @@ -52,10 +93,10 @@ pub(crate) async fn static_file_server(path: PathBuf) -> Resultfoo"); - 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/http_version.rs b/src/http_version.rs index bef817c..157a6d9 100644 --- a/src/http_version.rs +++ b/src/http_version.rs @@ -7,6 +7,10 @@ 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>; +type ProxyClientInnerOnlyTlsStream = tokio_rustls::client::TlsStream; + /// Supported HTTP versions #[derive(Debug)] pub enum HttpVersion { @@ -55,13 +59,20 @@ impl HttpVersion { type Http1Sender = hyper::client::conn::http1::SendRequest; type Http2Sender = hyper::client::conn::http2::SendRequest; -type Http1Connection = hyper::client::conn::http1::Connection< - TokioIo>, +type Http1Connection = + hyper::client::conn::http1::Connection, hyper::body::Incoming>; +type Http1InnerOnlyConnection = hyper::client::conn::http1::Connection< + TokioIo, hyper::body::Incoming, >; type Http2Connection = hyper::client::conn::http2::Connection< - TokioIo>, + TokioIo, + hyper::body::Incoming, + crate::TokioExecutor, +>; +type Http2InnerOnlyConnection = hyper::client::conn::http2::Connection< + TokioIo, hyper::body::Incoming, crate::TokioExecutor, >; @@ -101,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 }, } } @@ -117,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 8e0ce80..5c15792 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,40 +3,39 @@ pub mod attested_get; pub mod file_server; pub mod health_check; pub mod normalize_pem; -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, AttestationExchangeMessage, AttestationVerifier}; +use attested_tls::{AttestedCertificateResolver, AttestedCertificateVerifier, AttestedTlsError}; 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::{ + client::NestingTlsConnector, server::NestingTlsAcceptor, server::NestingTlsStream, +}; 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}; use tokio_rustls::rustls::{ - self, ClientConfig, RootCertStore, ServerConfig, pki_types::CertificateDer, + 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}; -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"; @@ -53,14 +52,110 @@ 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 = ( http::Request, oneshot::Sender>, hyper::Error>>, ); +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 + pub cert_chain: Vec>, + /// Der-encoded TLS private key + 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: String, + }, +} + +impl OuterTlsConfig +where + A: ToSocketAddrs, +{ + fn certificate_name(&self) -> Result { + match &self.tls { + OuterTlsMode::CertAndKey(cert_and_key) => { + Ok(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] { @@ -72,108 +167,128 @@ fn ensure_proxy_alpn_protocols(alpn_protocols: &mut Vec>) { } } -/// Retrieve the attested remote TLS certificate. -pub async fn get_tls_cert( +/// Retrieve the inner attested remote TLS certificate. +pub async fn get_inner_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? + 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()), }; - debug!("[get-tls-cert] Connected to proxy server with measurements: {measurements:?}"); - Ok((cert, measurements)) + 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, + outer_client_config: ClientConfig, +) -> Result>, ProxyError> { + let outbound_stream = tokio::net::TcpStream::connect(&server_name).await?; + + let domain = server_name_from_host(&server_name)?; + + let attested_cert_verifier = AttestedCertificateVerifier::try_default(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 { - /// The underlying attested TLS server - attested_tls_server: AttestedTlsServer, - /// The underlying TCP listener - listener: Arc, + outer: Option, + inner: Option, /// 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_session: Option>, + inner_local: Option, + inner_certificate_name: Option, target: String, attestation_generator: AttestationGenerator, attestation_verifier: AttestationVerifier, client_auth: bool, - ) -> Result { - let mut 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(), - )? - }; - ensure_proxy_alpn_protocols(&mut server_config.alpn_protocols); - - let attested_tls_server = AttestedTlsServer::new_with_tls_config( - cert_and_key.cert_chain, - server_config, - attestation_generator, - attestation_verifier, - )?; - - let listener = TcpListener::bind(local).await?; - - Ok(Self { - attested_tls_server, - listener: listener.into(), - target, - }) - } - - /// Start with preconfigured TLS - pub async fn new_with_tls_config( - cert_chain: Vec>, - mut server_config: ServerConfig, - local: impl ToSocketAddrs, - target: String, - attestation_generator: AttestationGenerator, - attestation_verifier: AttestationVerifier, - ) -> Result { - ensure_proxy_alpn_protocols(&mut server_config.alpn_protocols); + ) -> Result + where + O: ToSocketAddrs, + I: ToSocketAddrs, + { + if outer_session.is_none() && inner_local.is_none() { + return Err(ProxyError::NoListenersConfigured); + } - let attested_tls_server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, - attestation_generator, - attestation_verifier, - )?; + let certificate_name = outer_session + .as_ref() + .map(OuterTlsConfig::certificate_name) + .transpose()? + .or(inner_certificate_name); + let inner_server_config = Arc::new( + build_inner_server_config( + attestation_generator, + attestation_verifier, + client_auth, + certificate_name, + ) + .await?, + ); + 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 listener = TcpListener::bind(local).await?; + 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, outer_tls_acceptor)) + } + None => None, + }; Ok(Self { - attested_tls_server, - listener: listener.into(), + outer, + inner, target, }) } @@ -183,49 +298,205 @@ 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 attested_tls_server = self.attested_tls_server.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 - { - warn!("Failed to handle connection: {err}"); + 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), + }; + + 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}"); - } + }) } - }); + (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 { + 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}"); + } + } + }) + } + (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) } - /// 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 { - self.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(), + } } - /// Handle an incoming connection from a proxy-client - async fn handle_connection( + /// 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() + .map(|(listener, _)| listener.local_addr()) + .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() + .map(|(listener, _)| listener.local_addr()) + .transpose() + } + + async fn handle_outer_connection( + tls_stream: NestingTlsStream, + target: String, + client_addr: SocketAddr, + ) -> 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) => remote_cert_chain + .first() + .and_then(|cert| { + match 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, + } + }; + + let http_version = HttpVersion::from_negotiated_protocol_server(&tls_stream); + Self::serve_tls_stream(tls_stream, http_version, target, client_addr, attestation).await + } + + async fn handle_inner_connection( tls_stream: tokio_rustls::server::TlsStream, - measurements: Option, - remote_attestation_type: AttestationType, target: String, client_addr: SocketAddr, ) -> Result<(), ProxyError> { - debug!("[proxy-server] accepted connection with measurements: {measurements:?}"); + 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) => remote_cert_chain.first().and_then(|cert| { + match extract_custom_attestation_from_cert(cert) { + Ok(attestation) => Some(attestation), + Err(err) => { + warn!("Failed to extract remote attestation from certificate: {err}"); + None + } + } + }), + None => None, + } + }; let http_version = HttpVersion::from_negotiated_protocol_server(&tls_stream); + Self::serve_tls_stream(tls_stream, http_version, target, client_addr, attestation).await + } + + async fn serve_tls_stream( + tls_stream: IO, + 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), + match attestation.get_measurements() { + Ok(measurements) => measurements, + Err(err) => { + warn!("Failed to extract measurements from peer attestation: {err}"); + None + } + }, + ), + None => (None, None), + }; // Setup a request handler let service = service_fn(move |mut req| { @@ -251,8 +522,13 @@ 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(); + if let Some(measurements) = measurements { match measurements.to_header_format() { Ok(header_value) => { @@ -266,11 +542,13 @@ impl ProxyServer { } } - update_header( - headers, - ATTESTATION_TYPE_HEADER, - remote_attestation_type.as_str(), - ); + 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 { @@ -372,16 +650,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 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( @@ -393,43 +671,129 @@ 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 + cert_and_key.map(|cert_and_key| cert_and_key.cert_chain), + ) + .await } /// Create a new proxy client with given TLS configuration pub async fn new_with_tls_config( - mut 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 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(); - let attested_tls_client = AttestedTlsClient::new_with_tls_config( - client_config, + if outer_has_client_auth != inner_has_client_auth { + return Err(ProxyError::ClientAuthMisconfigured); + } + + 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( + 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); + + let nesting_tls_connector = + NestingTlsConnector::new(Arc::new(outer_client_config), Arc::new(inner_client_config)); + + 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 { + 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_chain, - )?; + None, + ) + .await + } + + /// Create a new inner-only proxy client with given TLS configuration. + pub async fn new_inner_only_with_tls_config( + address: impl ToSocketAddrs, + target_name: String, + attestation_generator: AttestationGenerator, + attestation_verifier: AttestationVerifier, + cert_chain: Option>>, + ) -> Result { + 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( + 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_inner(address, attested_tls_client, &target_name).await + Self::new_with_connector( + address, + ProxyTlsConnector::InnerOnly(TlsConnector::from(Arc::new(inner_client_config))), + &target_name, + ) + .await } - /// Create a new proxy client with given [AttestedTlsClient] - pub async fn new_with_inner( + /// Create a new proxy client with a configured TLS connector. + async fn new_with_connector( address: impl ToSocketAddrs, - attested_tls_client: AttestedTlsClient, + tls_connector: ProxyTlsConnector, target_name: &str, ) -> Result { let listener = TcpListener::bind(address).await?; @@ -452,9 +816,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, attestation) = // 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, &tls_connector, first) .await { Ok(output) => { @@ -482,6 +846,9 @@ impl ProxyClient { let (conn_done_tx, mut conn_done_rx) = tokio::sync::watch::channel::>(None); + let remote_attestation_type = attestation.attestation_type; + let measurements = attestation.get_measurements().ok().flatten(); + tokio::spawn(async move { let res = conn.await; let _ = conn_done_tx.send(res.err()); @@ -496,17 +863,15 @@ impl ProxyClient { let (response, should_reconnect) = match sender.send_request(req).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(); + headers.remove(MEASUREMENT_HEADER); + 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}"); } } @@ -517,6 +882,7 @@ impl ProxyClient { ATTESTATION_TYPE_HEADER, remote_attestation_type.as_str(), ); + (Ok(resp.map(|b| b.boxed())), false) } Err(e) => { @@ -622,27 +988,19 @@ impl ProxyClient { // If it fails retry with a backoff (indefinately) async fn setup_connection_with_backoff( target: &str, - attested_tls_client: &AttestedTlsClient, + tls_connector: &ProxyTlsConnector, should_bail: bool, - ) -> Result< - ( - HttpSender, - HttpConnection, - Option, - AttestationType, - ), - ProxyError, - > { + ) -> 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(attested_tls_client, target).await { + match Self::setup_connection(tls_connector, target).await { Ok(output) => { 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; @@ -659,45 +1017,88 @@ impl ProxyClient { /// Connect to the proxy-server, do TLS handshake and remote attestation async fn setup_connection( - inner: &AttestedTlsClient, + tls_connector: &ProxyTlsConnector, 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:?}"); - - // The attestation exchange is now complete - setup an HTTP client - let http_version = HttpVersion::from_negotiated_protocol_client(&tls_stream); + ) -> Result<(HttpSender, HttpConnection, AttestationExchangeMessage), ProxyError> { + let outbound_stream = tokio::net::TcpStream::connect(target).await?; + + let domain = server_name_from_host(target)?; + 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)) } - }; + } + } - // Return the HTTP client, as well as remote measurements - Ok((sender, conn, measurements, remote_attestation_type)) + 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)?; + + 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 @@ -711,6 +1112,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 @@ -747,16 +1167,28 @@ 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}")] + 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, + #[error("Inner-session-only mode does not support user-supplied TLS client certificates")] + InnerOnlyClientAuthUnsupported, + #[error("At least one server listener must be configured")] + NoListenersConfigured, } impl From> for ProxyError { @@ -765,6 +1197,78 @@ 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) + .map_err(|_| ProxyError::InvalidCertificateEncoding)?; + + Ok(cert + .subject() + .iter_common_name() + .next() + .ok_or(ProxyError::MissingCertificateName)? + .as_str() + .map_err(|_| ProxyError::InvalidCertificateName)? + .to_string()) +} + +fn certificate_identity_from_chain( + cert_chain: &[CertificateDer<'static>], +) -> Result { + 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::build(&certificate_name, attestation_generator) + .with_subject_alt_names(vec![]) + .with_certificate_validity(Duration::from_secs(ATTESTED_CERTIFICATE_VALIDITY_SECS)) + .finish()?, + ) +} + +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.unwrap_or_else(|| "localhost".to_string()), + ) + .await?; + + let mut inner_server_config = if client_auth { + 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)) + } 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 pub(crate) fn host_to_host_with_port(host: &str) -> String { if host.contains(':') { @@ -774,6 +1278,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; @@ -792,17 +1306,108 @@ where #[cfg(test)] mod tests { - use crate::{ - attestation::measurements::MeasurementPolicy, attested_tls::get_tls_cert_with_config, + use attestation::{AttestationType, measurements::MeasurementPolicy}; + use std::collections::HashMap; + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, }; + use tokio_rustls::TlsConnector; use super::*; use test_helpers::{ - example_http_service, generate_certificate_chain, 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] + 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 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(); ensure_proxy_alpn_protocols(&mut protocols); @@ -818,19 +1423,78 @@ mod tests { assert_eq!(protocols, vec![ALPN_HTTP11.to_vec(), ALPN_H2.to_vec()]); } - #[tokio::test] - async fn http_proxy_default_constructors_work() { + #[tokio::test(flavor = "multi_thread")] + async fn proxy_server_requires_at_least_one_listener() { + let result = ProxyServer::new( + None::>, + None::<&str>, + None, + "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; - let (cert_chain, private_key) = generate_certificate_chain("127.0.0.1".parse().unwrap()); - let server_cert = cert_chain[0].clone(); + let (cert_chain, private_key) = generate_certificate_chain_for_host("localhost"); + let tls_cert_and_key = TlsCertAndKey { + cert_chain, + key: private_key, + }; + + let dual_listener_server = ProxyServer::new( + Some(OuterTlsConfig { + listen_addr: "127.0.0.1:0", + 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(), + false, + ) + .await + .unwrap(); + + let outer_addr = dual_listener_server.outer_local_addr().unwrap().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::>, + Some("127.0.0.1:0"), + None, + 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().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 inner_only_listener_negotiates_http2_by_default() { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + let target_addr = example_http_service().await; let proxy_server = ProxyServer::new( - TlsCertAndKey { - cert_chain, - key: private_key, - }, - "127.0.0.1:0", + None::>, + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), @@ -839,19 +1503,65 @@ mod tests { .await .unwrap(); - let proxy_addr = proxy_server.local_addr().unwrap(); + let inner_addr = proxy_server.inner_local_addr().unwrap().unwrap(); tokio::spawn(async move { proxy_server.accept().await.unwrap(); }); - let proxy_client = ProxyClient::new( + let attested_cert_verifier = + AttestedCertificateVerifier::try_default(AttestationVerifier::mock()).unwrap(); + 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 client_config.alpn_protocols); + + 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(); + + assert!(matches!( + HttpVersion::from_negotiated_protocol_client(&tls_stream), + HttpVersion::Http2 + )); + + 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, - "127.0.0.1:0".to_string(), - proxy_addr.to_string(), + 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(), - Some(server_cert), + None, ) .await .unwrap(); @@ -866,41 +1576,220 @@ mod tests { .await .unwrap(); - let headers = res.headers(); + 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_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; + 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(); - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() + tokio::spawn(async move { + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr)) + .await .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()); + assert_attestation_type_header(res.headers(), "none"); + assert_no_measurements_header(res.headers()); + assert_mock_measurements(&res.text().await.unwrap()); + } - let res_body = res.text().await.unwrap(); - assert_eq!(res_body, "No measurements"); + #[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; + + 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( + 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"), + None, + 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 attested_cert_verifier = + AttestedCertificateVerifier::try_default(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, _attestation) = ProxyClient::setup_connection( + &ProxyTlsConnector::Nested(nesting_tls_connector), + &format!("localhost:{}", proxy_addr.port()), + ) + .await + .unwrap(); + + assert!(matches!(sender, HttpSender::Http2(_))); + assert!(matches!(conn, HttpConnection::Http2 { .. })); } // 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 (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", + 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"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, ) .await .unwrap(); @@ -914,7 +1803,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, @@ -928,38 +1817,26 @@ 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(); - 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()); + 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"); } // Server has no attestation, client has mock DCAP and client auth - #[tokio::test] + #[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("127.0.0.1".parse().unwrap()); + generate_certificate_chain_for_host("localhost"); let (client_cert_chain, client_private_key) = - generate_certificate_chain("127.0.0.1".parse().unwrap()); + generate_certificate_chain_for_host("localhost"); let ( (_client_tls_server_config, client_tls_client_config), @@ -971,13 +1848,20 @@ mod tests { server_private_key, ); - let proxy_server = ProxyServer::new_with_tls_config( - server_cert_chain, - server_tls_server_config, - "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: certificate_identity_from_chain(&server_cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), + true, ) .await .unwrap(); @@ -985,14 +1869,13 @@ mod tests { 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(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), Some(client_cert_chain), @@ -1003,52 +1886,44 @@ mod tests { 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())) + let res = reqwest::get(format!("http://{}", proxy_client_addr)) .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()); + assert_attestation_type_header(res.headers(), "none"); + assert_no_measurements_header(res.headers()); 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()); + assert_mock_measurements(&res_body); } // Server has no attestation, client has mock DCAP but no client auth - #[tokio::test] + #[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("127.0.0.1".parse().unwrap()); + 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", + 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"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), + false, ) .await .unwrap(); @@ -1063,7 +1938,7 @@ mod tests { let proxy_client = ProxyClient::new_with_tls_config( client_config, "127.0.0.1:0", - proxy_addr.to_string(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), None, @@ -1079,39 +1954,90 @@ 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(); - // 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()); + 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"); + } - // 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()); + #[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"), + None, + 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 = + serde_json::from_slice(&res.bytes().await.unwrap()).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] + #[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("127.0.0.1".parse().unwrap()); + generate_certificate_chain_for_host("localhost"); let (client_cert_chain, client_private_key) = - generate_certificate_chain("127.0.0.1".parse().unwrap()); + generate_certificate_chain_for_host("localhost"); let ( (_client_tls_server_config, client_tls_client_config), @@ -1123,13 +2049,20 @@ mod tests { server_private_key, ); - let proxy_server = ProxyServer::new_with_tls_config( - server_cert_chain, - server_tls_server_config, - "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: certificate_identity_from_chain(&server_cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::mock(), + true, ) .await .unwrap(); @@ -1137,14 +2070,13 @@ mod tests { 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(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::mock(), Some(client_cert_chain), @@ -1155,80 +2087,47 @@ mod tests { 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())) + 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_mock_measurements(&res.text().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())) + let res = reqwest::get(format!("http://{}", proxy_client_addr)) .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()); + 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 - #[tokio::test] + #[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("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( - cert_chain.clone(), - server_config, - "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: certificate_identity_from_chain(&cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, ) .await .unwrap(); @@ -1239,33 +2138,45 @@ mod tests { proxy_server.accept().await.unwrap(); }); - let (retrieved_chain, _measurements) = get_tls_cert_with_config( - &proxy_server_addr.to_string(), + 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, cert_chain); + 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] + #[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("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( - cert_chain, - server_config, - "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: certificate_identity_from_chain(&cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::with_no_attestation(), AttestationVerifier::expect_none(), + false, ) .await .unwrap(); @@ -1279,37 +2190,40 @@ mod tests { let proxy_client_result = 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, ) .await; - assert!(matches!( - proxy_client_result.unwrap_err(), - ProxyError::AttestedTls(AttestedTlsError::Attestation( - AttestationError::AttestationTypeNotAccepted - )) - )); + 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] + #[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("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( - cert_chain, - server_config, - "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: certificate_identity_from_chain(&cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, ) .await .unwrap(); @@ -1341,44 +2255,48 @@ mod tests { let attestation_verifier = AttestationVerifier { measurement_policy, pccs_url: None, - log_dcap_quote: false, + dump_dcap_quotes: false, override_azure_outdated_tcb: false, + internal_pccs: None, }; let proxy_client_result = 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(), attestation_verifier, None, ) .await; - assert!(matches!( - proxy_client_result.unwrap_err(), - ProxyError::AttestedTls(AttestedTlsError::Attestation( - AttestationError::MeasurementsNotAccepted - )) - )); + let err = proxy_client_result.unwrap_err().to_string(); + assert!(err.contains("ApplicationVerificationFailure"), "{err}"); } - #[tokio::test] + #[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("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( - cert_chain, - server_config, - "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: certificate_identity_from_chain(&cert_chain).unwrap(), + }, + }), + Some("127.0.0.1:0"), + None, target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, ) .await .unwrap(); @@ -1387,6 +2305,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(); @@ -1398,12 +2317,13 @@ mod tests { // Now accept another connection 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(), - proxy_addr.to_string(), + format!("localhost:{}", proxy_addr.port()), AttestationGenerator::with_no_attestation(), AttestationVerifier::mock(), None, @@ -1418,64 +2338,239 @@ 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(); + 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(); + reconnected_rx.await.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(); - let headers = res.headers(); + 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"); + } + + #[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"), + None, + 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(); - let attestation_type = headers - .get(ATTESTATION_TYPE_HEADER) - .unwrap() - .to_str() + 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_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()); + 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); + } - let res_body = res.text().await.unwrap(); - assert_eq!(res_body, "No measurements"); + #[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"), + None, + 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] + #[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("127.0.0.1".parse().unwrap()); + 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 attested_tls_server = AttestedTlsServer::new_with_tls_config( - cert_chain, - server_config, + 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"), + None, + target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), + false, ) + .await .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 { @@ -1485,7 +2580,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, @@ -1499,24 +2594,12 @@ 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(); - 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()); + 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/main.rs b/src/main.rs index d929778..6f04bd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,17 @@ use anyhow::{anyhow, ensure}; -use attested_tls::attestation::measurements::MultiMeasurements; +use attestation::{AttestationType, AttestationVerifier, measurements::MeasurementPolicy}; use clap::{Parser, Subcommand}; -use std::{ - fs::File, - net::{IpAddr, SocketAddr}, - path::PathBuf, -}; +use pccs::Pccs; +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; use attested_tls_proxy::{ - AttestationGenerator, ProxyClient, ProxyServer, + AttestationGenerator, OuterTlsConfig, OuterTlsMode, ProxyClient, ProxyServer, TlsCertAndKey, attested_get::attested_get, - attested_tls::{ - TlsCertAndKey, - attestation::{AttestationType, AttestationVerifier, measurements::MeasurementPolicy}, - }, - file_server::attested_file_server, - get_tls_cert, health_check, + file_server::{AttestedFileServerConfig, attested_file_server}, + get_inner_tls_cert, health_check, normalize_pem::normalize_private_key_pem_to_pkcs8, }; @@ -38,7 +31,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 @@ -62,16 +55,19 @@ 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) /// 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. @@ -84,25 +80,28 @@ 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 { - /// 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, if enabled + #[arg(long)] + outer_listen_addr: Option, + /// 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) - /// 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 @@ -124,9 +123,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, @@ -135,19 +131,25 @@ 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, if enabled + #[arg(long)] + outer_listen_addr: Option, + /// 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) - /// 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: PathBuf, - /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. + tls_private_key_path: Option, + /// PEM certificate chain for the optional outer nested-TLS listener #[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)] @@ -164,14 +166,13 @@ 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, }, } #[tokio::main] async fn main() -> anyhow::Result<()> { + let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default(); + let cli = Cli::parse(); ensure!( @@ -227,13 +228,15 @@ 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, + internal_pccs: Some(Pccs::new(None)), }; match cli.command { CliCommand::Client { listen_addr, + inner_session_only, target_addr, client_attestation_type, tls_private_key_path, @@ -241,7 +244,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://") @@ -252,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 @@ -277,19 +286,15 @@ async fn main() -> anyhow::Result<()> { }; let client_attestation_generator = - 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, + AttestationGenerator::new_with_detection(client_attestation_type, dev_dummy_dcap)?; + + let client = if inner_session_only { + ProxyClient::new_inner_only( + tls_cert_and_chain, listen_addr, target_addr, client_attestation_generator, attestation_verifier, - None, ) .await? } else { @@ -311,7 +316,9 @@ async fn main() -> anyhow::Result<()> { } } CliCommand::Server { - listen_addr, + outer_listen_addr, + inner_listen_addr, + inner_certificate_name, target_addr, tls_private_key_path, tls_certificate_path, @@ -324,19 +331,26 @@ 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)?; + 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?; + AttestationGenerator::new_with_detection(server_attestation_type, dev_dummy_dcap)?; let server = ProxyServer::new( - tls_cert_and_chain, - listen_addr, + 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, + inner_certificate_name, target_addr, local_attestation_generator, attestation_verifier, @@ -353,8 +367,7 @@ async fn main() -> anyhow::Result<()> { CliCommand::GetTlsCert { server, tls_ca_certificate, - allow_self_signed, - out_measurements, + out_measurements: _, // TODO } => { let remote_tls_cert = match tls_ca_certificate { Some(remote_cert_filename) => Some( @@ -365,36 +378,38 @@ 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 { path_to_serve, - listen_addr, + outer_listen_addr, + inner_listen_addr, + inner_certificate_name, 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)?; + 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())), @@ -403,21 +418,22 @@ 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, - listen_addr, + 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 { target_addr, url_path, tls_ca_certificate, - allow_self_signed, } => { let remote_tls_cert = match tls_ca_certificate { Some(remote_cert_filename) => Some( @@ -434,7 +450,6 @@ async fn main() -> anyhow::Result<()> { &url_path.unwrap_or_default(), attestation_verifier, remote_tls_cert, - allow_self_signed, ) .await?; @@ -455,22 +470,62 @@ 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( - cert_chain.ok_or(anyhow!("Private key given but no certificate chain"))?, - private_key, - ) - } else { - if cert_chain.is_some() { - return Err(anyhow!("Certificate chain provided but no private key")); +) -> anyhow::Result> { + match (cert_chain, private_key) { + (Some(cert_chain), Some(private_key)) => { + Ok(Some(load_tls_cert_and_key(cert_chain, private_key)?)) } - tracing::warn!("No TLS ceritifcate provided - generating self-signed"); - Ok(attested_tls_proxy::self_signed::generate_self_signed_cert( - ip, - )?) + (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), + } +} + +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(()) +} + +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() { + return Err(anyhow!( + "--tls-ca-certificate cannot be used with --inner-session-only" + )); + } + + 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(()) } /// Load TLS details from storage @@ -506,3 +561,27 @@ fn certs_to_pem_string(certs: &[CertificateDer<'_>]) -> Result 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 8990734..9448f7f 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; @@ -13,22 +12,31 @@ use tokio_rustls::rustls::{ }; use tracing_subscriber::{EnvFilter, fmt}; +use crate::MEASUREMENT_HEADER; + static INIT: Once = Once::new(); -use crate::{ - MEASUREMENT_HEADER, - attestation::measurements::{DcapMeasurementRegister, MultiMeasurements}, -}; +pub fn install_crypto_provider() { + static CRYPTO_PROVIDER_INIT: Once = Once::new(); -/// Helper to generate a self-signed certificate for testing -pub fn generate_certificate_chain( - ip: IpAddr, + 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>) { - let mut params = rcgen::CertificateParams::new(vec![]).unwrap(); - params.subject_alt_names.push(rcgen::SanType::IpAddress(ip)); + install_crypto_provider(); + + 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, ip.to_string()); + .push(rcgen::DnType::CommonName, host); let keypair = rcgen::KeyPair::generate().unwrap(); let cert = params.self_signed(&keypair).unwrap(); @@ -46,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) @@ -68,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()); @@ -119,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(); @@ -139,18 +153,9 @@ async fn get_handler(headers: http::HeaderMap) -> impl IntoResponse { .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() { + install_crypto_provider(); + INIT.call_once(|| { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));