diff --git a/Cargo.lock b/Cargo.lock index a8115de..712f616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.5.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dd146b3de349a6ffaa4e4e319ab3a90371fb159fb0bddeb1c7bbe8b1792eff" +checksum = "e57586581f2008933241d16c3e3f633168b3a5d2738c5c42ea5246ec5e0ef17a" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b88cf92ed20685979ed1d8472422f0c6c2d010cec77caf63aaa7669cc1a7bc2" +checksum = "66b1483f8c2562bf35f0270b697d5b5fe8170464e935bd855a4c5eaf6f89b354" dependencies = [ "alloy-rlp", "bytes", @@ -55,9 +55,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" dependencies = [ "arrayvec", "bytes", @@ -65,9 +65,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.5.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12768ae6303ec764905a8a7cd472aea9072f9f9c980d18151e26913da8ae0123" +checksum = "91577235d341a1bdbee30a463655d08504408a4d51e9f72edbfc5a622829f402" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fa1ca7e617c634d2bd9fa71f9ec8e47c07106e248b9fcbd3eaddc13cabd625" +checksum = "2c4b64c8146291f750c3f391dff2dd40cf896f7e2b253417a31e342aa7265baa" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c00c0c3a75150a9dc7c8c679ca21853a137888b4e1c5569f92d7e2b15b5102" +checksum = "d9df903674682f9bae8d43fdea535ab48df2d6a8cb5104ca29c58ada22ef67b3" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297db260eb4d67c105f68d6ba11b8874eec681caec5505eab8fbebee97f790bc" +checksum = "737b8a959f527a86e07c44656db237024a32ae9b97d449f788262a547e8aa136" dependencies = [ "const-hex", "dunce", @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc442cc2a75207b708d481314098a0f8b6f7b58e3148dd8d8cc7407b0d6f9385" +checksum = "fdf7effe4ab0a4f52c865959f790036e61a7983f68b13b75d7fbcedf20b753ce" dependencies = [ "alloy-primitives", "alloy-sol-macro", @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.5.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f169b85eb9334871db986e7eaf59c58a03d86a30cc68b846573d47ed0656bb" +checksum = "a03bb3f02b9a7ab23dacd1822fa7f69aa5c8eefcdcf57fad085e0b8d76fb4334" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -169,9 +169,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.5.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "019821102e70603e2c141954418255bec539ef64ac4117f8e84fb493769acf73" +checksum = "5ce599598ef8ebe067f3627509358d9faaa1ef94f77f834a7783cd44209ef55c" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -179,6 +179,7 @@ dependencies = [ "hyper", "hyper-tls", "hyper-util", + "itertools 0.14.0", "opentelemetry", "opentelemetry-http", "reqwest", @@ -520,17 +521,15 @@ dependencies = [ ] [[package]] -name = "attested-tls-proxy" -version = "1.0.0" +name = "attested-tls" +version = "0.0.1" dependencies = [ "alloy-rpc-client", "alloy-transport-http", "anyhow", - "axum", "az-tdx-vtpm", "base64 0.22.1", "bytes", - "clap", "configfs-tsm", "dcap-qvl", "futures-util", @@ -539,22 +538,15 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "jsonrpsee", "num-bigint", "once_cell", "openssl", - "p256", "parity-scale-codec", "pem-rfc7468", - "pin-project-lite", - "pkcs1", - "pkcs8", "rand_core 0.6.4", "rcgen", "reqwest", - "rsa", - "rustls-pemfile", - "rustls-webpki", + "rustls-webpki 0.103.8", "serde", "serde_json", "sha2", @@ -565,16 +557,52 @@ dependencies = [ "tokio", "tokio-rustls", "tokio-tungstenite", - "tower-http", "tower-service", "tracing", - "tracing-subscriber", "tss-esapi", "url", "webpki-roots", "x509-parser", ] +[[package]] +name = "attested-tls-proxy" +version = "1.0.0" +dependencies = [ + "anyhow", + "attested-tls", + "axum", + "base64 0.22.1", + "bytes", + "clap", + "http", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee", + "p256", + "pem-rfc7468", + "pin-project-lite", + "pkcs1", + "pkcs8", + "rcgen", + "reqwest", + "rsa", + "rustls-pemfile", + "serde", + "serde_json", + "tdx-quote", + "tempfile", + "thiserror 2.0.17", + "time", + "tokio", + "tokio-rustls", + "tower-http", + "tracing", + "tracing-subscriber", + "x509-parser", +] + [[package]] name = "auto_impl" version = "1.3.0" @@ -824,9 +852,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases", @@ -834,9 +862,9 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", @@ -1019,6 +1047,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1126,9 +1164,9 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "dcap-qvl" -version = "0.3.10" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3696cfa3d2b8b26df6dadafa67dd1fa69376c1e38971c207984bc3a9f0621d05" +checksum = "435989ce7ba46ba3f837f9df3c8139469e72ae810e707893b19f8b6b370d14ef" dependencies = [ "anyhow", "asn1_der", @@ -1139,20 +1177,18 @@ dependencies = [ "const-oid", "dcap-qvl-webpki", "der", - "derive_more 2.1.1", "futures", "hex", "log", - "p256", "parity-scale-codec", "pem", "reqwest", - "rustls-pki-types", + "ring", + "rustls-webpki 0.102.8", "scale-info", "serde", "serde-human-bytes", "serde_json", - "sha2", "tracing", "urlencoding", "wasm-bindgen-futures", @@ -1161,19 +1197,12 @@ dependencies = [ [[package]] name = "dcap-qvl-webpki" -version = "0.103.4+dcap.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0af040afe66c4f26ca05f308482d98bd75a35a80a227d877c2e28c9947a9fa6" +checksum = "70ebdcd097c369fe3422cf3978540e0406148435ec0f4d8ecbbf201c746f19c9" dependencies = [ - "ecdsa", - "ed25519-dalek", - "p256", - "p384", "ring", - "rsa", "rustls-pki-types", - "sha2", - "signature", "untrusted", ] @@ -2160,6 +2189,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2168,9 +2206,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -2284,9 +2322,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -2318,9 +2356,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -2355,9 +2393,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru-slab" @@ -2469,9 +2507,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" dependencies = [ "libc", "log", @@ -2638,9 +2676,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" @@ -2832,9 +2870,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -3026,9 +3064,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", @@ -3197,9 +3235,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.2.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8b5b858a440a0bc02625b62dd95131b9201aa9f69f411195dd4a7cfb1de3d7" +checksum = "111325c42c4bafae99e777cd77b40dea9a2b30c69e9d8c74b6eccd7fba4337de" dependencies = [ "rustversion", ] @@ -3380,7 +3418,6 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", - "sha2", "signature", "spki", "subtle", @@ -3482,7 +3519,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.8", "subtle", "zeroize", ] @@ -3506,6 +3543,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.8" @@ -3597,12 +3645,12 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3610,9 +3658,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -3703,16 +3751,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "indexmap", "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] @@ -3952,9 +4000,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2379beea9476b89d0237078be761cf8e012d92d5ae4ae0c9a329f974838870fc" +checksum = "f8658017776544996edc21c8c7cc8bb4f13db60955382f4bac25dc6303b38438" dependencies = [ "paste", "proc-macro2", @@ -3989,7 +4037,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4662,9 +4710,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -4675,9 +4723,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -4688,9 +4736,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4698,9 +4746,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ "bumpalo", "proc-macro2", @@ -4711,9 +4759,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -4734,9 +4782,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -5266,9 +5314,3 @@ dependencies = [ "quote", "syn 2.0.108", ] - -[[package]] -name = "zmij" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/Cargo.toml b/Cargo.toml index 1b1ba8b..c76ae53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "attestation-provider-server"] +members = [".", "attestation-provider-server", "attested-tls"] [package] name = "attested-tls-proxy" @@ -11,20 +11,17 @@ repository = "https://github.com/flashbots/attested-tls-proxy" keywords = ["attested-TLS", "CVM", "TDX"] [dependencies] +attested-tls = { path = "attested-tls" } tokio = { version = "1.48.0", features = ["full"] } -tokio-rustls = { version = "0.26.4", default-features = false } -sha2 = "0.10.9" +tokio-rustls = { version = "0.26.4", default-features = false, features = [ + "ring", +] } x509-parser = { version = "0.18.0", features = ["verify"] } thiserror = "2.0.17" clap = { version = "4.5.51", features = ["derive", "env"] } -webpki-roots = "1.0.4" rustls-pemfile = "2.2.0" anyhow = "1.0.100" pem-rfc7468 = { version = "0.7.0", features = ["std"] } -configfs-tsm = "0.0.2" -rand_core = { version = "0.6.4", features = ["getrandom"] } -dcap-qvl = "=0.3.10" -hex = "0.4.3" hyper = { version = "1.7.0", features = ["server", "http2"] } hyper-util = { version = "0.1.17", features = ["tokio"] } http-body-util = "0.1.3" @@ -38,11 +35,7 @@ reqwest = { version = "0.12.24", default-features = false, features = [ ] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["env-filter", "json"] } -parity-scale-codec = "3.7.5" -num-bigint = "0.4.6" -webpki = { package = "rustls-webpki", version = "0.103.8" } time = "0.3.44" -once_cell = "1.21.3" axum = "0.8.6" tower-http = { version = "0.6.7", features = ["fs"] } rsa = { version = "0.9", default-features = false } @@ -52,45 +45,17 @@ pkcs8 = "0.10.2" rcgen = "0.14.5" pin-project-lite = "0.2.16" -# For Azure vTPM attestation -az-tdx-vtpm = { version = "0.7.4", optional = true } -tss-esapi = { version = "7.6.0", optional = true } -openssl = { version = "0.10.75", optional = true } - -# For websockets -tokio-tungstenite = { version = "0.28.0", optional = true } -futures-util = { version = "0.3.31", optional = true } - -# For JSON RPC -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 } - [dev-dependencies] tempfile = "3.23.0" tdx-quote = { version = "0.0.5", features = ["mock"] } +attested-tls = { path = "attested-tls", features = ["test-helpers", "mock"] } jsonrpsee = { version = "0.26.0", features = ["server"] } [features] -default = ["azure", "ws", "rpc"] +default = ["azure"] # Adds support for Microsoft Azure attestation generation and verification -azure = ["tss-esapi", "az-tdx-vtpm", "openssl"] - -# Adds websocket support -ws = ["tokio-tungstenite", "futures-util"] - -# Adds JSON RPC support -rpc = [ - "alloy-rpc-client", - "tower-service", - "alloy-transport-http", - "url", - "futures-util", -] +azure = ["attested-tls/azure"] [package.metadata.deb] maintainer = "Flashbots Team " diff --git a/README.md b/README.md index cdd455f..851d205 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # `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. +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 work-in-progress crate 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 uses post-handshake remote-attested TLS, meaning regular CA-signed TLS certificates can be used. + +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. 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. @@ -29,88 +30,7 @@ Accepted measurements for the remote party can be specified in a JSON file conta 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. +Details and examples of the measurements file format are [in the attested-tls documentation](attested-tls/README.md#measurements-file). 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. @@ -144,7 +64,7 @@ These are the attestation type names used in the HTTP headers, and the measureme - `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 MAA (not yet supported) +- `azure-tdx` - TDX on Azure, with vTPM attestation - `qemu-tdx` - TDX on Qemu (no cloud platform) - `dcap-tdx` - DCAP TDX (platform not specified) @@ -156,43 +76,20 @@ Proxy-client to proxy-server connections use TLS 1.3. 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`. -### 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. +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). -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. - -### HTTP reverse proxy - -Following a successful attestation exchange, the client can make HTTP requests using HTTP2, and the server will forward them to the target service. +Following a successful attestation exchange, the client can make HTTP requests, and the server will forward them to the target service. 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. + + ## 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 `tpm2-tss`. This feature is enabled by default. For non-azure deployments you can compile without this requirement by specifying `--no-default-features`. But note that this is will disable both generation and verification of azure attestations. - ## Trying it out locally (without CVM attestation) This might help give an understanding of how it works. @@ -336,4 +233,3 @@ A `docker-compose.yml` is provided to test the full proxy chain: openssl s_client -connect localhost:8443 -CAfile certs/ca.crt -servername proxy-server # Should show "Verify return code: 0 (ok)" ``` - diff --git a/attested-tls/Cargo.toml b/attested-tls/Cargo.toml new file mode 100644 index 0000000..ec38c0f --- /dev/null +++ b/attested-tls/Cargo.toml @@ -0,0 +1,92 @@ +[package] +name = "attested-tls" +version = "0.0.1" +edition = "2024" +license = "MIT" +description = "A remote-attested TLS protocol for secure communication with CVM services" +repository = "https://github.com/flashbots/attested-tls-proxy" +keywords = ["attested-TLS", "CVM", "TDX"] + +[dependencies] +tokio = { version = "1.48.0", features = ["full"] } +tokio-rustls = { version = "0.26.4", default-features = false, features = [ + "ring", +] } +sha2 = "0.10.9" +x509-parser = "0.18.0" +thiserror = "2.0.17" +webpki-roots = "1.0.4" +anyhow = "1.0.100" +pem-rfc7468 = { version = "0.7.0", features = ["std"] } +configfs-tsm = "0.0.2" +rand_core = { version = "0.6.4", features = ["getrandom"] } +dcap-qvl = "0.3.4" +hex = "0.4.3" +http = "1.3.1" +serde_json = "1.0.145" +serde = "1.0.228" +base64 = "0.22.1" +reqwest = { version = "0.12.23", default-features = false, features = [ + "rustls-tls-webpki-roots-no-provider", +] } +tracing = "0.1.41" +parity-scale-codec = "3.7.5" +openssl = "0.10.75" +num-bigint = "0.4.6" +webpki = { package = "rustls-webpki", version = "0.103.8" } +time = "0.3.44" +once_cell = "1.21.3" + +# Used for azure vTPM attestation support +az-tdx-vtpm = { version = "0.7.4", optional = true } +tss-esapi = { version = "7.6.0", optional = true } + +# 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 } +tdx-quote = { version = "0.0.5", features = ["mock"], optional = true } + +[dev-dependencies] +rcgen = "0.14.5" +tempfile = "3.23.0" +tdx-quote = { version = "0.0.5", features = ["mock"] } + +[features] +default = ["azure", "ws", "rpc"] + +# Adds support for Microsoft Azure attestation generation and verification +azure = ["tss-esapi", "az-tdx-vtpm"] + +# 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"] + +mock = ["tdx-quote"] diff --git a/attested-tls/README.md b/attested-tls/README.md new file mode 100644 index 0000000..73b8d03 --- /dev/null +++ b/attested-tls/README.md @@ -0,0 +1,145 @@ +# 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/src/attestation/azure/ak_certificate.rs b/attested-tls/src/attestation/azure/ak_certificate.rs similarity index 100% rename from src/attestation/azure/ak_certificate.rs rename to attested-tls/src/attestation/azure/ak_certificate.rs diff --git a/src/attestation/azure/mod.rs b/attested-tls/src/attestation/azure/mod.rs similarity index 100% rename from src/attestation/azure/mod.rs rename to attested-tls/src/attestation/azure/mod.rs diff --git a/src/attestation/azure/nv_index.rs b/attested-tls/src/attestation/azure/nv_index.rs similarity index 100% rename from src/attestation/azure/nv_index.rs rename to attested-tls/src/attestation/azure/nv_index.rs diff --git a/src/attestation/dcap.rs b/attested-tls/src/attestation/dcap.rs similarity index 95% rename from src/attestation/dcap.rs rename to attested-tls/src/attestation/dcap.rs index 94ddd3e..144cd49 100644 --- a/src/attestation/dcap.rs +++ b/attested-tls/src/attestation/dcap.rs @@ -20,7 +20,7 @@ pub async fn create_dcap_attestation(input_data: [u8; 64]) -> Result, At } /// Verify a DCAP TDX quote, and return the measurement values -#[cfg(not(test))] +#[cfg(not(any(test, feature = "mock")))] pub async fn verify_dcap_attestation( input: Vec, expected_input_data: [u8; 64], @@ -36,7 +36,7 @@ pub async fn verify_dcap_attestation( /// Allows the timestamp to be given, making it possible to test with existing attestations /// /// If collateral is given, it is used instead of contacting PCCS (used in tests) -async fn verify_dcap_attestation_with_given_timestamp( +pub async fn verify_dcap_attestation_with_given_timestamp( input: Vec, expected_input_data: [u8; 64], pccs_url: Option, @@ -62,8 +62,7 @@ async fn verify_dcap_attestation_with_given_timestamp( } }; - let quote_verifier = dcap_qvl::verify::QuoteVerifier::new_prod(); - let _verified_report = quote_verifier.verify(&input, &collateral, now)?; + let _verified_report = dcap_qvl::verify::verify(&input, &collateral, now)?; let measurements = MultiMeasurements::from_dcap_qvl_quote("e)?; @@ -74,7 +73,7 @@ async fn verify_dcap_attestation_with_given_timestamp( Ok(measurements) } -#[cfg(test)] +#[cfg(any(test, feature = "mock"))] pub async fn verify_dcap_attestation( input: Vec, expected_input_data: [u8; 64], @@ -89,7 +88,7 @@ pub async fn verify_dcap_attestation( } /// Create a mock quote for testing on non-confidential hardware -#[cfg(test)] +#[cfg(any(test, feature = "mock"))] fn generate_quote(input: [u8; 64]) -> Result, QuoteGenerationError> { let attestation_key = tdx_quote::SigningKey::random(&mut rand_core::OsRng); let provisioning_certification_key = tdx_quote::SigningKey::random(&mut rand_core::OsRng); @@ -103,7 +102,7 @@ fn generate_quote(input: [u8; 64]) -> Result, QuoteGenerationError> { } /// Create a quote -#[cfg(not(test))] +#[cfg(not(any(test, feature = "mock")))] fn generate_quote(input: [u8; 64]) -> Result, QuoteGenerationError> { configfs_tsm::create_tdx_quote(input) } @@ -128,7 +127,7 @@ pub enum DcapVerificationError { SystemTime(#[from] std::time::SystemTimeError), #[error("DCAP quote verification: {0}")] DcapQvl(#[from] anyhow::Error), - #[cfg(test)] + #[cfg(any(test, feature = "mock"))] #[error("Quote parse: {0}")] QuoteParse(#[from] tdx_quote::QuoteParseError), } diff --git a/src/attestation/measurements.rs b/attested-tls/src/attestation/measurements.rs similarity index 99% rename from src/attestation/measurements.rs rename to attested-tls/src/attestation/measurements.rs index fae3a77..69780c2 100644 --- a/src/attestation/measurements.rs +++ b/attested-tls/src/attestation/measurements.rs @@ -128,7 +128,7 @@ impl MultiMeasurements { ]))) } - #[cfg(test)] + #[cfg(any(test, feature = "mock"))] pub fn from_tdx_quote(quote: &tdx_quote::Quote) -> Self { Self::Dcap(HashMap::from([ (DcapMeasurementRegister::MRTD, quote.mrtd()), @@ -249,7 +249,7 @@ impl MeasurementPolicy { } /// Expect mock measurements used in tests - #[cfg(test)] + #[cfg(any(test, feature = "mock"))] pub fn mock() -> Self { Self { accepted_measurements: vec![MeasurementRecord { diff --git a/src/attestation/mod.rs b/attested-tls/src/attestation/mod.rs similarity index 99% rename from src/attestation/mod.rs rename to attested-tls/src/attestation/mod.rs index 6d1543b..b43dd98 100644 --- a/src/attestation/mod.rs +++ b/attested-tls/src/attestation/mod.rs @@ -267,7 +267,7 @@ impl AttestationVerifier { } /// Expect mock measurements used in tests - #[cfg(test)] + #[cfg(any(test, feature = "test-helpers"))] pub fn mock() -> Self { Self { measurement_policy: MeasurementPolicy::mock(), diff --git a/src/attested_rpc.rs b/attested-tls/src/attested_rpc.rs similarity index 69% rename from src/attested_rpc.rs rename to attested-tls/src/attested_rpc.rs index 2134b07..55062b1 100644 --- a/src/attested_rpc.rs +++ b/attested-tls/src/attested_rpc.rs @@ -14,11 +14,15 @@ use tower_service::Service; use crate::{ attestation::{measurements::MultiMeasurements, AttestationType}, - attested_tls::{AttestedTlsClient, AttestedTlsError}, - http_version::HttpVersion, - TokioExecutor, + AttestedTlsClient, AttestedTlsError, }; +/// 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 @@ -45,27 +49,18 @@ impl AttestedRpcClient { /// Connect to an attested RPC server /// - /// This could be a regular JSON RPC server behind an attested TLS proxy - /// - /// `is_local` is passed on to [RpcClient] and represents not whether the connection - /// leaves the internal network, but whether it is a public RPC or an RPC behind a load - /// balancer. This gives different client behaviour allowing for less reliable connections. + /// 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> { - // Make a TCP connection to the attested server, and do TLS handshake and attestation - // exchange let (stream, measurements, attestation_type) = self.inner.connect_tcp(server).await?; - - // Setup HTTP client let io = TokioIo::new(stream); let rpc_client = match self.http_version { HttpVersion::Http1 => { let (sender, conn) = conn::http1::handshake(io).await?; - // Drive the connection for the lifetime of `sender` tokio::spawn(async move { if let Err(e) = conn.await { tracing::error!("AttestedRpcClient connection error: {e}"); @@ -76,7 +71,6 @@ impl AttestedRpcClient { } HttpVersion::Http2 => { let (sender, conn) = conn::http2::handshake(TokioExecutor, io).await?; - // Drive the connection for the lifetime of `sender` tokio::spawn(async move { if let Err(e) = conn.await { tracing::error!("AttestedRpcClient connection error: {e}"); @@ -90,42 +84,31 @@ impl AttestedRpcClient { Ok((rpc_client, measurements, attestation_type)) } - /// Given an HTTP1 connection, setup RPC client 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); - - let rpc_client = RpcClient::new(http, is_local); - - Ok(rpc_client) + Ok(RpcClient::new(http, is_local)) } - /// Given an HTTP2 connection, setup RPC client 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); - - let rpc_client = RpcClient::new(http, is_local); - - Ok(rpc_client) + Ok(RpcClient::new(http, is_local)) } } -/// Wrap hyper's HTTP1 client connection so we can implement a tower service for it #[derive(Debug, Clone)] struct Http1ClientConnectionService { sender: Arc< @@ -156,7 +139,6 @@ impl Service>> for Http1ClientC 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?; @@ -165,7 +147,6 @@ impl Service>> for Http1ClientC } } -/// Wrap hyper's HTTP2 client connection so we can implement a tower service for it #[derive(Debug, Clone)] struct Http2ClientConnectionService { sender: hyper::client::conn::http2::SendRequest>, @@ -181,17 +162,11 @@ impl Service>> for Http2ClientC } fn call(&mut self, req: Request>) -> Self::Future { - // Clone so multiple calls can proceed concurrently over the same HTTP2 connection let mut sender = self.sender.clone(); - - Box::pin(async move { - // Note: SendRequest docs mention req must have a host header - sender.send_request(req).await - }) + Box::pin(async move { sender.send_request(req).await }) } } -/// An error from attested JSON RPC #[derive(Error, Debug)] pub enum AttestedRpcError { #[error("Attested TLS: {0}")] @@ -204,64 +179,99 @@ pub enum AttestedRpcError { 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::net::SocketAddr; + 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::{json, Value}; + use tokio::net::TcpListener; + + use super::AttestedRpcClient; - use super::*; use crate::{ attestation::{AttestationGenerator, AttestationType, AttestationVerifier}, test_helpers::{generate_certificate_chain, generate_tls_config}, - ProxyServer, ALPN_H2, + AttestedTlsClient, AttestedTlsServer, }; - use jsonrpsee::server::{ServerBuilder, ServerHandle}; - use jsonrpsee::RpcModule; - /// Starts a JSON-RPC HTTP server on a random local port - async fn spawn_test_rpc_server() -> (SocketAddr, ServerHandle) { - let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); - - let addr: SocketAddr = server.local_addr().unwrap(); + 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(); - let mut module = RpcModule::new(()); + Ok(Response::builder() + .status(StatusCode::OK) + .header("content-type", "application/json") + .body(Full::new(Bytes::from(response_body))) + .unwrap()) + } - // Mock ethereum-like RPC method - module - .register_async_method("eth_chainId", |_params, _ctx, _ext| async move { - Ok::<_, jsonrpsee::types::ErrorObjectOwned>("0x1") - }) + 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(); - - let handle = server.start(module); - - (addr, handle) } #[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, mut client_config) = - generate_tls_config(cert_chain.clone(), private_key); + let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - let (target_addr, _handle) = spawn_test_rpc_server().await; - - let proxy_server = ProxyServer::new_with_tls_config( + let server = AttestedTlsServer::new_with_tls_config( cert_chain, server_config, - "127.0.0.1:0", - target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), ) .await .unwrap(); - let proxy_addr = proxy_server.local_addr().unwrap(); + 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 { - proxy_server.accept().await.unwrap(); + let (tcp_stream, _) = listener.accept().await.unwrap(); + serve_json_rpc_connection(server, tcp_stream).await; }); - client_config.alpn_protocols.push(ALPN_H2.to_vec()); let client = AttestedTlsClient::new_with_tls_config( client_config, @@ -275,7 +285,7 @@ mod tests { let attested_rpc_client = AttestedRpcClient::new_http2(client); let (rpc_client, _measurements, _attestation_type) = attested_rpc_client - .connect(&proxy_addr.to_string(), true) + .connect(&server_addr.to_string(), true) .await .unwrap(); @@ -286,40 +296,38 @@ mod tests { #[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, mut client_config) = - generate_tls_config(cert_chain.clone(), private_key); - - let (target_addr, _handle) = spawn_test_rpc_server().await; + let (server_config, client_config) = generate_tls_config(cert_chain.clone(), private_key); - let proxy_server = ProxyServer::new_with_tls_config( + let server = AttestedTlsServer::new_with_tls_config( cert_chain, server_config, - "127.0.0.1:0", - target_addr.to_string(), AttestationGenerator::new(AttestationType::DcapTdx, None).unwrap(), AttestationVerifier::expect_none(), ) .await .unwrap(); - let proxy_addr = proxy_server.local_addr().unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let server_addr = listener.local_addr().unwrap(); + let server = Arc::new(server); - // This is used to trigger a dropped connection to the proxy 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 connection_handle = proxy_server.accept().await.unwrap(); + let (tcp_stream_1, _) = listener.accept().await.unwrap(); + let first_conn_handle = + tokio::spawn(serve_json_rpc_connection(server.clone(), tcp_stream_1)); - // Wait for a signal to simulate a dropped connection, then drop the task handling the - // connection connection_breaker_rx.await.unwrap(); - connection_handle.abort(); + first_conn_handle.abort(); + let _ = first_conn_handle.await; + let _ = connection_closed_tx.send(()); - proxy_server.accept().await.unwrap(); + let (tcp_stream_2, _) = listener.accept().await.unwrap(); + serve_json_rpc_connection(server, tcp_stream_2).await; }); - client_config.alpn_protocols.push(ALPN_H2.to_vec()); - let client = AttestedTlsClient::new_with_tls_config( client_config, AttestationGenerator::with_no_attestation(), @@ -332,30 +340,31 @@ mod tests { let attested_rpc_client = AttestedRpcClient::new_http2(client); let (rpc_client, _measurements, _attestation_type) = attested_rpc_client - .connect(&proxy_addr.to_string(), true) + .connect(&server_addr.to_string(), true) .await .unwrap(); let response: String = rpc_client.request("eth_chainId", ()).await.unwrap(); assert_eq!(response, "0x1"); - // Now break the connection connection_breaker_tx.send(()).unwrap(); + connection_closed_rx.await.unwrap(); - // Show that the next call fails let err = rpc_client .request::<(), String>("eth_chainId", ()) .await .unwrap_err(); - assert_eq!(err.to_string(), "connection error".to_string()); + let err_msg = err.to_string(); + assert!( + err_msg.contains("connection error") || err_msg.contains("operation was canceled"), + "unexpected error: {err_msg}" + ); - // Make another connection let (rpc_client, _measurements, _attestation_type) = attested_rpc_client - .connect(&proxy_addr.to_string(), true) + .connect(&server_addr.to_string(), true) .await .unwrap(); - // Now the call succeeds let response: String = rpc_client.request("eth_chainId", ()).await.unwrap(); assert_eq!(response, "0x1"); } diff --git a/src/attested_tls.rs b/attested-tls/src/lib.rs similarity index 78% rename from src/attested_tls.rs rename to attested-tls/src/lib.rs index 1a9ac62..bf87f50 100644 --- a/src/attested_tls.rs +++ b/attested-tls/src/lib.rs @@ -1,10 +1,18 @@ //! Attested TLS protocol server and client -use crate::{ - attestation::{ - measurements::MultiMeasurements, AttestationError, AttestationExchangeMessage, - AttestationGenerator, AttestationType, AttestationVerifier, - }, - host_to_host_with_port, +pub mod attestation; + +#[cfg(feature = "ws")] +pub mod websockets; + +#[cfg(feature = "rpc")] +pub mod attested_rpc; + +#[cfg(any(test, feature = "test-helpers"))] +pub mod test_helpers; + +use crate::attestation::{ + measurements::MultiMeasurements, AttestationError, AttestationExchangeMessage, + AttestationGenerator, AttestationType, AttestationVerifier, }; use parity_scale_codec::{Decode, Encode}; use sha2::{Digest, Sha256}; @@ -54,6 +62,16 @@ pub struct AttestedTlsServer { 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 async fn new( cert_and_key: TlsCertAndKey, @@ -85,12 +103,16 @@ impl AttestedTlsServer { } /// Start with preconfigured TLS + /// + /// This allows dangerous configuration pub async 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); @@ -271,12 +293,16 @@ impl AttestedTlsClient { } /// Create a new proxy client with given TLS configuration + /// + /// This allows dangerous configuration but is used in tests pub async 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); } @@ -439,8 +465,8 @@ pub async fn get_tls_cert( } /// Helper for testing getting remote certificate -#[cfg(test)] -pub(crate) async fn get_tls_cert_with_config( +#[cfg(any(test, feature = "test-helpers"))] +pub async fn get_tls_cert_with_config( server_name: &str, attestation_verifier: AttestationVerifier, client_config: ClientConfig, @@ -533,6 +559,15 @@ pub(crate) fn server_name_from_host( 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> { @@ -564,7 +599,10 @@ fn map_alpn_protocols(existing_protocols: Vec>) -> Vec> { #[cfg(test)] mod tests { use super::*; - use crate::test_helpers::{generate_certificate_chain, generate_tls_config}; + use crate::{ + attestation::measurements::MeasurementPolicy, + test_helpers::{generate_certificate_chain, generate_tls_config}, + }; use tokio::net::TcpListener; #[tokio::test] @@ -602,4 +640,113 @@ mod tests { 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(), + ) + .await + .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, + ) + .await + .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(), + ) + .await + .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(), + ) + .await + .unwrap(); + + let attestation_verifier = AttestationVerifier { + measurement_policy, + pccs_url: None, + log_dcap_quote: false, + }; + + let client = AttestedTlsClient::new_with_tls_config( + client_config, + AttestationGenerator::with_no_attestation(), + attestation_verifier, + None, + ) + .await + .unwrap(); + + let client_result = client.connect_tcp(&server_addr.to_string()).await; + + assert!(matches!( + client_result.unwrap_err(), + AttestedTlsError::Attestation(AttestationError::MeasurementsNotAccepted) + )); + } } diff --git a/attested-tls/src/test_helpers.rs b/attested-tls/src/test_helpers.rs new file mode 100644 index 0000000..47b0ce0 --- /dev/null +++ b/attested-tls/src/test_helpers.rs @@ -0,0 +1,143 @@ +//! Helper functions used in tests +use std::{collections::HashMap, net::IpAddr, sync::Arc}; +use tokio_rustls::rustls::{ + pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}, + server::{danger::ClientCertVerifier, WebPkiClientVerifier}, + ClientConfig, RootCertStore, ServerConfig, +}; + +use crate::{ + attestation::measurements::{DcapMeasurementRegister, MultiMeasurements}, + SUPPORTED_ALPN_PROTOCOL_VERSIONS, +}; + +/// 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, + ) +} + +/// 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]), + ])) +} diff --git a/src/websockets.rs b/attested-tls/src/websockets.rs similarity index 98% rename from src/websockets.rs rename to attested-tls/src/websockets.rs index c08e26d..4c3f6d6 100644 --- a/src/websockets.rs +++ b/attested-tls/src/websockets.rs @@ -1,3 +1,4 @@ +//! An attested Websocket server and client use std::{net::SocketAddr, sync::Arc}; use thiserror::Error; use tokio::net::{TcpListener, ToSocketAddrs}; @@ -5,7 +6,7 @@ use tokio_tungstenite::{tungstenite::protocol::WebSocketConfig, WebSocketStream} use crate::{ attestation::{measurements::MultiMeasurements, AttestationType}, - attested_tls::{AttestedTlsClient, AttestedTlsError, AttestedTlsServer}, + AttestedTlsClient, AttestedTlsError, AttestedTlsServer, }; /// Websocket message type re-exported for convenience diff --git a/test-assets/azure-tdx-1764662251380464271 b/attested-tls/test-assets/azure-tdx-1764662251380464271 similarity index 100% rename from test-assets/azure-tdx-1764662251380464271 rename to attested-tls/test-assets/azure-tdx-1764662251380464271 diff --git a/test-assets/dcap-quote-collateral-00.json b/attested-tls/test-assets/dcap-quote-collateral-00.json similarity index 100% rename from test-assets/dcap-quote-collateral-00.json rename to attested-tls/test-assets/dcap-quote-collateral-00.json diff --git a/test-assets/dcap-tdx-1766059550570652607 b/attested-tls/test-assets/dcap-tdx-1766059550570652607 similarity index 100% rename from test-assets/dcap-tdx-1766059550570652607 rename to attested-tls/test-assets/dcap-tdx-1766059550570652607 diff --git a/test-assets/hclreport.bin b/attested-tls/test-assets/hclreport.bin similarity index 100% rename from test-assets/hclreport.bin rename to attested-tls/test-assets/hclreport.bin diff --git a/test-assets/measurements.json b/attested-tls/test-assets/measurements.json similarity index 100% rename from test-assets/measurements.json rename to attested-tls/test-assets/measurements.json diff --git a/test-assets/measurements_2.json b/attested-tls/test-assets/measurements_2.json similarity index 100% rename from test-assets/measurements_2.json rename to attested-tls/test-assets/measurements_2.json diff --git a/src/lib.rs b/src/lib.rs index f8f586d..690d0c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,14 @@ //! An attested TLS protocol and HTTPS proxy -pub mod attestation; pub mod attested_get; -pub mod attested_tls; pub mod file_server; pub mod health_check; pub mod normalize_pem; pub mod self_signed; -#[cfg(feature = "ws")] -pub mod websockets; +pub use attested_tls; +pub use attested_tls::attestation; +pub use attested_tls::attestation::AttestationGenerator; -#[cfg(feature = "rpc")] -pub mod attested_rpc; - -pub use attestation::AttestationGenerator; mod http_version; #[cfg(test)] @@ -33,12 +28,12 @@ use tokio_rustls::rustls::server::VerifierBuilderError; use tokio_rustls::rustls::{pki_types::CertificateDer, ClientConfig, ServerConfig}; use tracing::{debug, error, warn}; -use crate::{ +use crate::http_version::{HttpConnection, HttpSender, HttpVersion, ALPN_H2, ALPN_HTTP11}; +use attested_tls::{ attestation::{ measurements::MultiMeasurements, AttestationError, AttestationType, AttestationVerifier, }, - attested_tls::{AttestedTlsClient, AttestedTlsError, AttestedTlsServer, TlsCertAndKey}, - http_version::{HttpConnection, HttpSender, HttpVersion, ALPN_H2, ALPN_HTTP11}, + AttestedTlsClient, AttestedTlsError, AttestedTlsServer, TlsCertAndKey, }; /// The header name for giving attestation type @@ -373,10 +368,8 @@ impl ProxyClient { Self::new_with_inner(address, attested_tls_client, &target_name).await } - /// Create a new proxy client with given TLS configuration - /// - /// This is private as it allows dangerous configuration but is used in tests - async fn new_with_inner( + /// Create a new proxy client with given [AttestedTlsClient] + pub async fn new_with_inner( address: impl ToSocketAddrs, attested_tls_client: AttestedTlsClient, target_name: &str, @@ -740,13 +733,8 @@ where #[cfg(test)] mod tests { - use std::collections::HashMap; - use crate::{ - attestation::measurements::{ - DcapMeasurementRegister, ExpectedMeasurements, MeasurementPolicy, MeasurementRecord, - }, - attested_tls::get_tls_cert_with_config, + attestation::measurements::MeasurementPolicy, attested_tls::get_tls_cert_with_config, }; use super::*; @@ -758,6 +746,7 @@ mod tests { // Server has mock DCAP, client has no attestation and no client auth #[tokio::test] 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()); @@ -1189,19 +1178,27 @@ mod tests { proxy_server.accept().await.unwrap(); }); + let measurement_policy = MeasurementPolicy::from_json_bytes( + br#" + [{ + "measurement_id": "test", + "attestation_type": "dcap-tdx", + "measurements": { + "0": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + "1": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + "2": { "expected": "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" }, + "3": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, + "4": { "expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } + } + }] + "# + .to_vec(), + ) + .await + .unwrap(); + let attestation_verifier = AttestationVerifier { - measurement_policy: MeasurementPolicy { - accepted_measurements: vec![MeasurementRecord { - measurement_id: "test".to_string(), - measurements: ExpectedMeasurements::Dcap(HashMap::from([ - (DcapMeasurementRegister::MRTD, vec![[0; 48]]), - (DcapMeasurementRegister::RTMR0, vec![[0; 48]]), - (DcapMeasurementRegister::RTMR1, vec![[1; 48]]), // This differs from the mock measurements - (DcapMeasurementRegister::RTMR2, vec![[0; 48]]), - (DcapMeasurementRegister::RTMR3, vec![[0; 48]]), - ])), - }], - }, + measurement_policy, pccs_url: None, log_dcap_quote: false, }; diff --git a/src/main.rs b/src/main.rs index 67160e9..f3c1a8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,9 +10,11 @@ use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; use tracing::level_filters::LevelFilter; use attested_tls_proxy::{ - attestation::{measurements::MeasurementPolicy, AttestationType, AttestationVerifier}, attested_get::attested_get, - attested_tls::{get_tls_cert, TlsCertAndKey}, + attested_tls::{ + attestation::{measurements::MeasurementPolicy, AttestationType, AttestationVerifier}, + get_tls_cert, TlsCertAndKey, + }, file_server::attested_file_server, health_check, normalize_pem::normalize_private_key_pem_to_pkcs8, diff --git a/src/self_signed.rs b/src/self_signed.rs index 2360e1b..b14dbd9 100644 --- a/src/self_signed.rs +++ b/src/self_signed.rs @@ -201,11 +201,12 @@ mod tests { use super::*; use crate::{ attestation::{AttestationType, AttestationVerifier}, - attested_tls::{server_name_from_host, AttestedTlsClient, AttestedTlsServer}, + attested_tls::{AttestedTlsClient, AttestedTlsServer}, test_helpers::{generate_certificate_chain, generate_tls_config}, AttestationGenerator, }; use tokio::net::TcpListener; + use tokio_rustls::rustls::pki_types::ServerName; #[tokio::test] async fn self_signed_server_attestation() { @@ -311,11 +312,9 @@ mod tests { 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_from_host(&server_addr.to_string()).unwrap(), - client_tcp_stream, - ) + .connect(server_name, client_tcp_stream) .await .unwrap(); diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 7132279..1dbc68c 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -157,6 +157,7 @@ pub fn init_tracing() { fmt() .with_env_filter(filter) .with_test_writer() // <-- IMPORTANT for tests - .init(); + .try_init() + .ok(); }); }