From 30dfb3db0d15313130b2500feb94f06d1f9e697d Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 23 Feb 2024 15:01:39 -0800 Subject: [PATCH] Migrate TLS to rustls --- Cargo.lock | 710 ++++++++++--- Cargo.toml | 83 +- README.md | 41 +- fuzz/Cargo.lock | 588 +++++++---- src/admin.rs | 179 +--- src/app.rs | 15 +- src/hyper_util.rs | 11 +- src/identity.rs | 1 + src/identity/caclient.rs | 67 +- src/identity/manager.rs | 36 +- src/proxy.rs | 7 - src/proxy/inbound.rs | 27 +- src/proxy/outbound.rs | 19 +- src/state.rs | 2 +- src/test_helpers/ca.rs | 9 +- src/test_helpers/tcp.rs | 4 +- src/test_helpers/xds.rs | 9 +- src/tls.rs | 39 +- src/tls/boring.rs | 956 ------------------ src/tls/certificate.rs | 323 ++++++ src/tls/control.rs | 181 ++++ src/tls/csr.rs | 70 ++ src/tls/gen-certs.sh | 2 + src/tls/key.pem | 10 +- src/tls/lib.rs | 174 ++++ src/tls/mock.rs | 178 ++++ src/tls/workload.rs | 261 +++++ tests/namespaced.rs | 29 +- .../{build/crypto => lib}/libcrypto.a | Bin .../linux_x86_64/{build/ssl => lib}/libssl.a | Bin 30 files changed, 2414 insertions(+), 1617 deletions(-) delete mode 100644 src/tls/boring.rs create mode 100644 src/tls/certificate.rs create mode 100644 src/tls/control.rs create mode 100644 src/tls/csr.rs create mode 100644 src/tls/lib.rs create mode 100644 src/tls/mock.rs create mode 100644 src/tls/workload.rs rename vendor/boringssl-fips/linux_x86_64/{build/crypto => lib}/libcrypto.a (100%) rename vendor/boringssl-fips/linux_x86_64/{build/ssl => lib}/libssl.a (100%) diff --git a/Cargo.lock b/Cargo.lock index ee6e6bb67..7b0517adb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,11 +17,21 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" dependencies = [ "cfg-if", "once_cell", @@ -71,17 +81,11 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" -[[package]] -name = "antidote" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" - [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arrayvec" @@ -89,6 +93,45 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -119,7 +162,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -130,7 +173,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -256,7 +299,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -273,8 +316,9 @@ checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "boring" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8259fc1ea91894a550190683fbcda307d1ef85f2875d93a2b1ab3cab58b62fea" dependencies = [ "bitflags 2.4.2", "boring-sys", @@ -283,10 +327,41 @@ dependencies = [ "once_cell", ] +[[package]] +name = "boring-additions" +version = "0.0.1" +source = "git+https://github.com/janrueth/boring-rustls-provider#03b48134ca0582ddaea9488eee476aceaa3a444e" +dependencies = [ + "aead", + "boring", + "boring-sys", + "foreign-types", +] + +[[package]] +name = "boring-rustls-provider" +version = "0.0.1" +source = "git+https://github.com/janrueth/boring-rustls-provider#03b48134ca0582ddaea9488eee476aceaa3a444e" +dependencies = [ + "aead", + "boring", + "boring-additions", + "boring-sys", + "boring-sys-additions", + "foreign-types", + "lazy_static", + "once_cell", + "rustls", + "rustls-pki-types", + "rustls-webpki", + "spki", +] + [[package]] name = "boring-sys" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace69f2e0d89d2c5e0874efe47f46259ff794fe8cbddfca72132c817611ad471" dependencies = [ "bindgen", "cmake", @@ -294,11 +369,19 @@ dependencies = [ "fslock", ] +[[package]] +name = "boring-sys-additions" +version = "0.0.1" +source = "git+https://github.com/janrueth/boring-rustls-provider#03b48134ca0582ddaea9488eee476aceaa3a444e" +dependencies = [ + "boring-sys", +] + [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byteorder" @@ -323,12 +406,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" [[package]] name = "cexpr" @@ -356,7 +436,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -399,18 +479,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstyle", "clap_lex", @@ -477,6 +557,22 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[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-sys" version = "0.8.6" @@ -579,6 +675,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -594,6 +700,29 @@ dependencies = [ "uuid", ] +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.3.11" @@ -609,6 +738,17 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "drain" version = "0.1.1" @@ -659,7 +799,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -761,7 +901,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -866,7 +1006,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -899,6 +1039,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -933,12 +1083,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "go-parse-duration" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558b88954871f5e5b2af0e62e2e176c8bde7a6c2c4ed41b13d138d96da2e2cbd" - [[package]] name = "gperftools" version = "0.2.0" @@ -1275,9 +1419,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" dependencies = [ "bytes", "futures-channel", @@ -1289,25 +1433,27 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "smallvec", "tokio", "want", ] [[package]] -name = "hyper-boring" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ - "antidote", - "boring", + "futures-util", "http 1.0.0", - "hyper 1.1.0", + "hyper 1.2.0", "hyper-util", - "linked_hash_set", - "once_cell", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", "tokio", - "tokio-boring", - "tower-layer", + "tokio-rustls", "tower-service", ] @@ -1334,7 +1480,7 @@ dependencies = [ "futures-util", "http 1.0.0", "http-body 1.0.0", - "hyper 1.1.0", + "hyper 1.2.0", "pin-project-lite", "socket2", "tokio", @@ -1529,15 +1675,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linked_hash_set" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1779,12 +1916,32 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1813,6 +1970,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1825,6 +1991,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "overload" version = "0.1.1" @@ -1866,6 +2038,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.7", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1899,7 +2081,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -1990,7 +2172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2032,7 +2214,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2074,7 +2256,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.48", + "syn 2.0.50", "tempfile", "which", ] @@ -2089,7 +2271,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2242,6 +2424,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" +dependencies = [ + "pem", + "ring 0.17.8", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "realm_io" version = "0.4.0" @@ -2315,6 +2510,36 @@ dependencies = [ "quick-error", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.12", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rust_decimal" version = "1.34.3" @@ -2346,6 +2571,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.31" @@ -2359,6 +2593,60 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +dependencies = [ + "base64 0.21.7", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2367,9 +2655,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -2380,43 +2668,75 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -2448,9 +2768,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.31" +version = "0.9.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" dependencies = [ "indexmap 2.2.3", "itoa", @@ -2500,12 +2820,33 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "der", ] [[package]] @@ -2514,6 +2855,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "symbolic-common" version = "12.8.0" @@ -2550,9 +2897,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -2565,6 +2912,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.10.0" @@ -2595,7 +2954,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2606,7 +2965,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "test-case-core", ] @@ -2637,14 +2996,14 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -2657,10 +3016,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -2669,6 +3030,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2726,17 +3097,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "tokio-boring" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" -dependencies = [ - "boring", - "boring-sys", - "once_cell", - "tokio", -] - [[package]] name = "tokio-io-timeout" version = "1.2.0" @@ -2755,7 +3115,18 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", ] [[package]] @@ -2841,7 +3212,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2874,7 +3245,7 @@ dependencies = [ "http 1.0.0", "http-body 0.4.6", "http-body 1.0.0", - "hyper 1.1.0", + "hyper 1.2.0", "pin-project-lite", "tower", "tower-service", @@ -2912,7 +3283,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2960,6 +3331,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -2974,19 +3351,37 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "unsafe-libyaml" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -3075,7 +3470,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "wasm-bindgen-shared", ] @@ -3097,7 +3492,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3173,7 +3568,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -3191,7 +3586,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -3211,17 +3606,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -3232,9 +3627,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -3244,9 +3639,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -3256,9 +3651,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -3268,9 +3663,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -3280,9 +3675,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -3292,9 +3687,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -3304,9 +3699,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "winreg" @@ -3318,6 +3713,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring 0.16.20", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -3335,9 +3757,15 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + [[package]] name = "ztunnel" version = "0.0.0" @@ -3347,7 +3775,9 @@ dependencies = [ "async-trait", "atty", "backoff", + "base64 0.21.7", "boring", + "boring-rustls-provider", "boring-sys", "byteorder", "bytes", @@ -3360,7 +3790,6 @@ dependencies = [ "futures", "futures-core", "futures-util", - "go-parse-duration", "gperftools", "hashbrown 0.14.3", "hickory-client", @@ -3372,8 +3801,8 @@ dependencies = [ "http-body 1.0.0", "http-body-util", "http-types", - "hyper 1.1.0", - "hyper-boring", + "hyper 1.2.0", + "hyper-rustls", "hyper-util", "ipnet", "itertools 0.12.1", @@ -3383,6 +3812,7 @@ dependencies = [ "matches", "netns-rs", "nix 0.27.1", + "oid-registry", "once_cell", "pprof", "priority-queue", @@ -3392,8 +3822,13 @@ dependencies = [ "prost-build", "prost-types", "rand 0.8.5", + "rcgen", "realm_io", + "ring 0.17.8", "rustc_version", + "rustls", + "rustls-native-certs", + "rustls-pemfile", "serde", "serde_json", "serde_yaml", @@ -3403,7 +3838,7 @@ dependencies = [ "thiserror", "tls-listener", "tokio", - "tokio-boring", + "tokio-rustls", "tokio-stream", "tonic 0.11.0", "tonic-build", @@ -3412,5 +3847,6 @@ dependencies = [ "tracing", "tracing-subscriber", "url", + "x509-parser", "ztunnel", ] diff --git a/Cargo.toml b/Cargo.toml index 8a13d4d29..ab4c7a873 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,12 @@ edition = "2021" rust-version = "1.65" [features] -default = ["fips"] +default = ["tls-ring"] gperftools = ["dep:gperftools"] console = ["dep:console-subscriber"] -fips = ["boring/fips", "hyper-boring/fips", "tokio-boring/fips"] -testing = [] # Enables utilities supporting tests. +tls-boring = ["dep:boring", "dep:boring-sys", "boring-rustls-provider/fips-only", "ztunnel/tls-boring"] +tls-ring = ["dep:ring", "rustls/ring", "dep:rcgen", "ztunnel/tls-ring"] +testing = ["dep:rcgen", "rcgen/x509-parser"] # Enables utilities supporting tests. [lib] path = "src/lib.rs" @@ -25,70 +26,78 @@ name = "throughput" harness = false [dependencies] -http-02 = { package = "http", version = "0.2.9" } +# Enabled with 'tls-boring' +boring-rustls-provider = { git = "https://github.com/janrueth/boring-rustls-provider", optional = true } # +boring = { version = "4", optional = true } +boring-sys = { version = "4", optional = true } + +# Enabled with 'tls-ring' +ring = { version = "0.17", optional = true } + anyhow = "1.0" async-stream = "0.3" async-trait = "0.1" atty = "0.2" -# Fork will be dropped once Hyper goes 1.0.0 -hyper-boring = { git = "https://github.com/howardjohn/boring/", branch = "hyper-boring/adopt-hyper-1.0.0-snapshot1" } -boring = { git = "https://github.com/howardjohn/boring/", branch = "hyper-boring/adopt-hyper-1.0.0-snapshot1" } -tokio-boring = { git = "https://github.com/howardjohn/boring/", branch = "hyper-boring/adopt-hyper-1.0.0-snapshot1" } -boring-sys = { git = "https://github.com/howardjohn/boring/", branch = "hyper-boring/adopt-hyper-1.0.0-snapshot1" } +backoff = "0.4.0" +base64 = "0.21" +byteorder = "1.5" bytes = { version = "1.5", features = ["serde"] } +chrono = "0.4" console-subscriber = { version = "0.2", optional = true } drain = "0.1" +duration-str = "0.7" futures = "0.3" +futures-core = "0.3" +futures-util = "0.3" gperftools = { version = "0.2", features = ["heap"], optional = true } +hashbrown = "0.14" +hickory-client = "0.24" +hickory-proto = "0.24" +hickory-resolver = "0.24" +hickory-server = { version = "0.24", features = [ "hickory-resolver" ] } +http-02 = { package = "http", version = "0.2.9" } +http-body-04 = { package = "http-body", version = "0.4" } +http-body-1 = { package = "http-body", version = "1.0.0-rc.2" } +http-body-util = "0.1" +http-types = { version = "2.12", default-features = false } hyper = { version = "1.1", features = ["full"] } +hyper-rustls = { version = "0.26.0", features = ["http2"] } hyper-util = { version = "0.1", features = ["full"] } +ipnet = { version = "2.9", features = ["serde"] } +itertools = "0.12" libc = "0.2" log = "0.4" +nix = { version = "0.27", features = ["socket", "sched", "uio", "fs", "ioctl", "user"] } once_cell = "1.19" pprof = { version = "0.13", features = ["protobuf", "protobuf-codec", "criterion"] } +priority-queue = "1.4" prometheus-client = { version = "0.22" } +prometheus-parse = "0.2" prost = "0.12" prost-types = "0.12" rand = "0.8" +rcgen = { version = "0.12", optional = true, features = ["pem"] } +realm_io = "0.4" +rustls = { version = "0.22"} +rustls-native-certs = "0.7.0" +rustls-pemfile = "2.1" serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" serde_yaml = "0.9" socket2 = { version = "0.5", features = ["all"] } -byteorder = "1.5" +textnonce = { version = "1.0" } thiserror = "1.0" tls-listener = { version = "0.9" } tokio = { "version" = "1.0", features = ["full", "test-util"] } +tokio-rustls = "0.25" tokio-stream = { version = "0.1", features = ["net"] } tonic = { version = "0.11", default-features = false, features = ["prost", "codegen"] } tower = { version = "0.4", features = ["full"] } +tower-hyper-http-body-compat = { git = "https://github.com/howardjohn/tower-hyper-http-body-compat", branch = "deps/hyper-1.0.0-snapshot1", features = ["server", "http2"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] } -realm_io = "0.4" -go-parse-duration = "0.1" -prometheus-parse = "0.2" url = "2.2" -itertools = "0.12" -ipnet = { version = "2.9", features = ["serde"] } -http-types = { version = "2.12", default-features = false } -textnonce = { version = "1.0" } -priority-queue = "1.4" -http-body-util = "0.1" -http-body-04 = { package = "http-body", version = "0.4" } -http-body-1 = { package = "http-body", version = "1.0.0-rc.2" } -tower-hyper-http-body-compat = { git = "https://github.com/howardjohn/tower-hyper-http-body-compat", branch = "deps/hyper-1.0.0-snapshot1", features = ["server", "http2"] } -futures-util = "0.3" -chrono = "0.4" -futures-core = "0.3" -nix = { version = "0.27", features = ["socket", "sched", "uio", "fs", "ioctl", "user"] } -hashbrown = "0.14" -backoff = "0.4.0" - -# DNS -hickory-client = "0.24" -hickory-proto = "0.24" -hickory-resolver = "0.24" -hickory-server = { version = "0.24", features = [ "hickory-resolver" ] } -duration-str = "0.7" +x509-parser = { version = "0.15", default-features = false } [target.'cfg(target_os = "linux")'.dependencies] netns-rs = "0.1" @@ -113,11 +122,13 @@ incremental = true [dev-dependencies] # Enable testing utils on this crate. -ztunnel = { version = "0.0.0", path = ".", features = [ "testing" ] } +ztunnel = { version = "0.0.0", path = ".", default-features = false, features = ["testing"] } criterion = { version = "0.5", features = ["async_tokio", "html_reports"] } diff = "0.1" local-ip-address = "0.5" matches = "0.1" test-case = "3.3" +oid-registry = "0.6.1" +rcgen = { version = "0.12.1", features = ["pem", "x509-parser"] } #debug = true diff --git a/README.md b/README.md index 805ac160a..72295c2b0 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,23 @@ If a feature is not directly used to implement the node proxy component in ambie ## Building -### FIPS +### TLS/Crypto provider -Ztunnel builds currently enable the `fips` Cargo feature by default, which in turn enables the `fips` feature -on [BoringSSL](https://github.com/cloudflare/boring). +Ztunnel's TLS is built on [rustls](https://github.com/rustls/rustls). + +Rustls has support for plugging in various crypto providers to meet various needs (compliance, performance, etc). + +| Name | How To Enable | +|-----------------------------------------------|------------------------------------------------| +| [ring](https://github.com/briansmith/ring/) | Default (or `--features tls-ring`) | +| [boring](https://github.com/cloudflare/boring) | `--features tls-boring --no-default-features`) | + +In all options, only TLS 1.3 with cipher suites `TLS13_AES_256_GCM_SHA384` and `TLS13_AES_128_GCM_SHA256` is used. + +#### `boring` FIPS + +With the `boring` option, the FIPS version is used. +Please note this only implies the specific version of the library is used; FIPS compliance requires more than *just* using a specific library. FIPS has [strict requirements](https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4407.pdf) @@ -42,14 +55,14 @@ We vendor FIPS boringssl binaries for To use these vendored libraries and build ztunnel for either of these OS/arch combos, for the moment you must manually edit [.cargo/config.toml](.cargo/config.toml) and change the values of BORING_BSSL_PATH and BORING_BSSL_INCLUDE_PATH under the `[env]` key to match the path to the vendored libraries for your platform, e.g: -#### For linux/x86_64 +##### For linux/x86_64 ``` toml BORING_BSSL_PATH = { value = "vendor/boringssl-fips/linux_x86_64", force = true, relative = true } BORING_BSSL_INCLUDE_PATH = { value = "vendor/boringssl-fips/include/", force = true, relative = true } ``` -#### For linux/arm64 +##### For linux/arm64 ``` toml BORING_BSSL_PATH = { value = "vendor/boringssl-fips/linux_arm64", force = true, relative = true } @@ -65,21 +78,3 @@ cargo build This manual twiddling of environment vars is not ideal but given that the alternative is prefixing `cargo build` with these envs on every `cargo build/run`, for now we have chosen to hardcode these in `config.toml` - that may be revisited in the future depending on local pain and/or evolving `boring` upstream build flows. Note that the Dockerfiles used to build these vendored `boringssl` builds may be found in the respective vendor directories, and can serve as a reference for the build environment needed to generate FIPS-compliant ztunnel builds. - -### Non-FIPS - -If you are building for a platform we don't include vendored FIPS `boringssl` binaries for, or you don't want or need FIPS compliance, note that currently non-FIPS builds are **not supported** by us. However you may build `ztunnel` with a FIPS-less `boringssl` by doing the following: - -1. Comment out all of the `BORING_BSSL_*` environment variables in `.cargo/config.toml` entirely. -1. Run `cargo build --no-default-features` - -Some IDEs (such as the [Intellij-series](https://github.com/intellij-rust/intellij-rust/issues/9757)) do not support -globally applying arguments to cargo. In this case, it is probably easier to remove `fips` as a default feature in -`Cargo.toml`. - -```toml -# ... -[features] -default = [] -# ... -``` diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index fbe0aed43..b247c4af3 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -71,12 +71,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" -[[package]] -name = "antidote" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" - [[package]] name = "anyhow" version = "1.0.79" @@ -95,6 +89,45 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -125,7 +158,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -136,7 +169,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -200,26 +233,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "bindgen" -version = "0.68.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" -dependencies = [ - "bitflags 2.4.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -232,29 +245,6 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" -[[package]] -name = "boring" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" -dependencies = [ - "bitflags 2.4.2", - "boring-sys", - "foreign-types", - "libc", - "once_cell", -] - -[[package]] -name = "boring-sys" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" -dependencies = [ - "bindgen", - "cmake", - "fs_extra", - "fslock", -] - [[package]] name = "bumpalo" version = "3.14.0" @@ -292,15 +282,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -348,17 +329,6 @@ dependencies = [ "half", ] -[[package]] -name = "clang-sys" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.0" @@ -385,21 +355,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] -name = "cmake" -version = "0.1.50" +name = "concurrent-queue" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ - "cc", + "crossbeam-utils", ] [[package]] -name = "concurrent-queue" -version = "2.4.0" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "crossbeam-utils", + "core-foundation-sys", + "libc", ] [[package]] @@ -499,6 +470,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.3.11" @@ -508,6 +493,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "drain" version = "0.1.1" @@ -558,7 +554,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -622,33 +618,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -658,22 +627,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "fslock" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "futures" version = "0.3.30" @@ -745,7 +698,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -806,18 +759,6 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "go-parse-duration" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558b88954871f5e5b2af0e62e2e176c8bde7a6c2c4ed41b13d138d96da2e2cbd" - [[package]] name = "h2" version = "0.4.2" @@ -1110,20 +1051,21 @@ dependencies = [ ] [[package]] -name = "hyper-boring" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ - "antidote", - "boring", + "futures-util", "http 1.0.0", "hyper", "hyper-util", - "linked_hash_set", - "once_cell", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", "tokio", - "tokio-boring", - "tower-layer", + "tokio-rustls", "tower-service", ] @@ -1314,12 +1256,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.153" @@ -1337,31 +1273,12 @@ dependencies = [ "once_cell", ] -[[package]] -name = "libloading" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linked_hash_set" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1548,12 +1465,32 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1582,6 +1519,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1594,6 +1540,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "overload" version = "0.1.1" @@ -1630,10 +1582,14 @@ dependencies = [ ] [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "pem" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.7", + "serde", +] [[package]] name = "percent-encoding" @@ -1668,7 +1624,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -1753,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.48", ] [[package]] @@ -1795,7 +1751,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -1837,7 +1793,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn", + "syn 2.0.48", "tempfile", "which", ] @@ -1852,7 +1808,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -2005,6 +1961,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" +dependencies = [ + "pem", + "ring", + "time", + "yasna", +] + [[package]] name = "realm_io" version = "0.4.0" @@ -2078,6 +2046,20 @@ dependencies = [ "quick-error", ] +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.12", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rust_decimal" version = "1.34.3" @@ -2094,12 +2076,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc_version" version = "0.4.0" @@ -2109,6 +2085,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.31" @@ -2122,6 +2107,60 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +dependencies = [ + "base64 0.21.7", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.16" @@ -2137,12 +2176,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.21" @@ -2166,7 +2237,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -2225,12 +2296,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2265,12 +2330,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "symbolic-common" version = "12.8.0" @@ -2294,6 +2371,17 @@ dependencies = [ "symbolic-common", ] +[[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.48" @@ -2305,6 +2393,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.10.0" @@ -2344,7 +2444,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -2364,10 +2464,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -2376,6 +2478,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2432,17 +2544,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "tokio-boring" -version = "5.0.0" -source = "git+https://github.com/howardjohn/boring/?branch=hyper-boring/adopt-hyper-1.0.0-snapshot1#41ce085b7458431b45715dfdeab4f86d6d5e7c30" -dependencies = [ - "boring", - "boring-sys", - "once_cell", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.2.0" @@ -2451,7 +2552,18 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", ] [[package]] @@ -2510,7 +2622,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -2581,7 +2693,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -2650,12 +2762,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "unsafe-libyaml" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -2744,7 +2868,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -2766,7 +2890,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2987,6 +3111,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -3004,9 +3154,15 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + [[package]] name = "ztunnel" version = "0.0.0" @@ -3016,8 +3172,7 @@ dependencies = [ "async-trait", "atty", "backoff", - "boring", - "boring-sys", + "base64 0.21.7", "byteorder", "bytes", "chrono", @@ -3026,7 +3181,6 @@ dependencies = [ "futures", "futures-core", "futures-util", - "go-parse-duration", "hashbrown 0.14.3", "hickory-client", "hickory-proto", @@ -3038,7 +3192,7 @@ dependencies = [ "http-body-util", "http-types", "hyper", - "hyper-boring", + "hyper-rustls", "hyper-util", "ipnet", "itertools 0.12.1", @@ -3055,8 +3209,13 @@ dependencies = [ "prost-build", "prost-types", "rand 0.8.5", + "rcgen", "realm_io", + "ring", "rustc_version", + "rustls", + "rustls-native-certs", + "rustls-pemfile", "serde", "serde_json", "serde_yaml", @@ -3065,7 +3224,7 @@ dependencies = [ "thiserror", "tls-listener", "tokio", - "tokio-boring", + "tokio-rustls", "tokio-stream", "tonic", "tonic-build", @@ -3074,6 +3233,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", + "x509-parser", ] [[package]] diff --git a/src/admin.rs b/src/admin.rs index edae97711..d8b02c35d 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -16,13 +16,12 @@ use crate::config::Config; use crate::hyper_util::{empty_response, plaintext_response, Server}; use crate::identity::SecretManager; use crate::state::DemandProxyState; -use crate::tls::asn1_time_to_system_time; +use crate::tls::Certificate; use crate::version::BuildInfo; use crate::xds::LocalConfig; use crate::{signal, telemetry}; -use boring::asn1::Asn1TimeRef; -use boring::base64; -use boring::x509::X509; + +use base64::engine::general_purpose::STANDARD; use bytes::Bytes; use drain::Watch; use http_body_util::Full; @@ -33,6 +32,7 @@ use std::borrow::Borrow; use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; +use std::time::SystemTime; use std::{net::SocketAddr, time::Duration}; use tokio::time; use tracing::{error, info, warn}; @@ -93,7 +93,6 @@ pub struct CertDump { pub struct CertsDump { identity: String, state: String, - ca_cert: Vec, cert_chain: Vec, } @@ -218,25 +217,18 @@ async fn handle_dashboard( response } -fn x509_to_pem(x509: &X509) -> String { - match x509.to_pem() { - Err(e) => format!(""), - Ok(vec) => base64::encode_block(&vec), - } +fn rfc3339(t: SystemTime) -> String { + use chrono::prelude::{DateTime, Utc}; + let dt: DateTime = t.into(); + dt.to_rfc3339_opts(chrono::SecondsFormat::Secs, true) } -fn dump_cert(x509: &X509) -> CertDump { - fn rfc3339(t: &Asn1TimeRef) -> String { - use chrono::prelude::{DateTime, Utc}; - let dt: DateTime = asn1_time_to_system_time(t).into(); - dt.to_rfc3339_opts(chrono::SecondsFormat::Secs, true) - } - +fn dump_cert(cert: &Certificate) -> CertDump { CertDump { - pem: x509_to_pem(x509), - serial_number: x509.serial_number().to_bn().unwrap().to_string(), - valid_from: rfc3339(x509.not_before()), - expiration_time: rfc3339(x509.not_after()), + pem: base64_encode(cert.as_pem()), + serial_number: cert.serial(), + valid_from: rfc3339(cert.expiration().not_before), + expiration_time: rfc3339(cert.expiration().not_after), } } @@ -253,8 +245,10 @@ async fn dump_certs(cert_manager: &SecretManager) -> Vec { Unavailable(err) => dump.state = format!("Unavailable: {err}"), Available(certs) => { dump.state = "Available".to_string(); - dump.ca_cert = vec![dump_cert(certs.x509())]; - dump.cert_chain = certs.iter_chain().map(dump_cert).collect(); + dump.cert_chain = std::iter::once(&certs.cert) + .chain(certs.chain.iter()) + .map(dump_cert) + .collect(); } }; dump @@ -470,6 +464,11 @@ async fn handle_gprof_heap(_req: Request) -> Response> { .unwrap() } +fn base64_encode(data: String) -> String { + use base64::Engine; + STANDARD.encode(data) +} + #[cfg(test)] mod tests { use super::change_log_level; @@ -567,126 +566,48 @@ mod tests { let got = serde_json::to_value(dump_certs(&manager).await).unwrap(); let want = serde_json::json!([ { - "ca_cert": [], "cert_chain": [], "identity": "spiffe://error/ns/forgotten/sa/sa-failed", "state": "Unavailable: the identity is no longer needed" }, { - "ca_cert": [], "cert_chain": [], "identity": "spiffe://test/ns/test/sa/sa-pending", "state": "Initializing" }, { - "ca_cert": [{ - "expiration_time": "2023-03-11T12:57:26Z", - "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNkekNDQVYrZ0F3SUJBZ0lVWn\ - lUOTI5c3d0QjhPSG1qUmFURWFENnlqcWc0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dE\ - RVdNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZUZ3MHlNekF6TVRFd05UVT\ - NNalphRncweQpNekF6TVRFeE1qVTNNalphTUFBd1dUQVRCZ2NxaGtqT1BRSUJCZ2dx\ - aGtqT1BRTUJCd05DQUFSYXIyQm1JWUFnCnZKbU9yU3BDZUZRNzlKUHk4Y3c0K3pFRT\ - hmcXI1N2svdW1NcDVqWFpFR0JwZWRCSVkrcWZtSlBYRWlyYTlFOTIKZFNta2ZLNUFL\ - TVd4bzRHYk1JR1lNQTRHQTFVZER3RUIvd1FFQXdJRm9EQWRCZ05WSFNVRUZqQVVCZ2\ - dyQmdFRgpCUWNEQVFZSUt3WUJCUVVIQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdO\ - VkhTTUVHREFXZ0JRL0pPSDlXcTVMCnNFZmxFWVNnSHRpRTJTbWUxVEE0QmdOVkhSRU\ - JBZjhFTGpBc2hpcHpjR2xtWm1VNkx5OTBjblZ6ZEY5a2IyMWgKYVc0dmJuTXZibUZ0\ - WlhOd1lXTmxMM05oTDNOaExUQXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBR2RYY2\ - 8yQgo3a05LMzVRMjBPc0YwZjI2bkpXTFd6eGpYV3FzNUx0dXhnRW5URjNJc3RuUWdm\ - cDVSMEszRXhsK1U4ZlhjblYyClNPOEdQTkdxSC82SUxsQzl2a1BYeU90WkJDMEZSRm\ - dVajR2NlZhamlURm1RYzJnS1k4Y0ZJS2hGMHRocW5NN3IKTDA3QytLUUkxRW9sR2Nm\ - R3BkTy80OU1oUEMvRi9MbnFnS3BzOUs0dlh1QWZLWW1VbXNQQWVRdnV0cmU2Z3ZJdQ\ - pzMHdIWWZwSGRIakhPdUhuSWFObDkzdVpueTBDQ3ovZ2wxKzlwdHIzL2ZFR0NPZFZE\ - SUp5MG5Tcmwwd0RpY3BYCk8wV2VBYzFVZUsvTFlCR2V5ZmVrWlJ4c3RsbDMzVGxJUk\ - k1cUt5SnFtdjh4ajhUZFdjUXpiTTZpRkdJbkd0YVEKQUphdU00SmVQRWI4RnF3PQot\ - LS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", - "serial_number": "588850990443535479077311695632745359443207891470", - "valid_from": "2023-03-11T05:57:26Z" - }], - "cert_chain": [{ - "expiration_time": "2296-12-24T18:31:28Z", - "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lVQyt\ - jLzYwZStGMWVFKzdWcXhuYVdjT09abm1Fd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERV\ - dNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZ0Z3MHlNekF6TVRFeE9ETXhNa\ - mhhR0E4eQpNamsyTVRJeU5ERTRNekV5T0Zvd0dERVdNQlFHQTFVRUNnd05ZMngxYzNS\ - bGNpNXNiMk5oYkRDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0N\ - nZ0VCQU1lQ1R4UEp0dWQwVXh3K0NhYWRkV0Q3YStRRXVRWSsKQlBUS0pkbk1lajBzQk\ - 1mVU1iVDE2SkxrWU5GZ3JqMVVWSEhjcFNvSUhvY3Ayc2QzMlNZNGJkYm9rUWNvcCtCa\ - gp0azU1alE0NktMWXNKZ2IyTnd2WW8xdDhFMWFldEpxRkdWN3JtZVpiRlllYWkrNnE3\ - aU1qbGJDR0F1Ny9VbktKCnNkR25hSlFnTjhkdTBUMUtEZ2pxS1B5SHFkc3U5a2JwQ3F\ - pRVhNUm13NC9CRWhGR3ptSUQyb1VES0IzNmR1VmIKZHpTRW01MVF2Z1U1SUxYSWd5Vn\ - Jlak41Q0ZzQytXK3hqZU9YTEV6dGZIRlVvcWIzd1doa0J1RXhtcjgxSjJoRwpXOXBVT\ - Eoyd2tRZ2RmWFA3Z3RNa0I2RXlLdy94SWVhTm1MelBJR3JYMDF6UVlJZFpUdUR3TVkw\ - Q0F3RUFBYU5UCk1GRXdIUVlEVlIwT0JCWUVGRDhrNGYxYXJrdXdSK1VSaEtBZTJJVFp\ - LWjdWTUI4R0ExVWRJd1FZTUJhQUZEOGsKNGYxYXJrdXdSK1VSaEtBZTJJVFpLWjdWTU\ - E4R0ExVWRFd0VCL3dRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFTApCUUFEZ2dFQkFLc\ - m5BZVNzU1NLMy84engraHpqNlNGWGRKQTlDUTAyR0VKN2hIcktpakdXVllkZGFsOWRB\ - YlM1CnRMZC8vcUtPOXVJc0dldHkvT2syYlJRNmNxcU1sZ2ROejNqbW1yYlNsWVdtSVh\ - JMHlIR21DaVNhekhzWFZiRUYKNkl3eTN0Y1I0dm9YV0tJQ1dQaCtDMmNUZ0xtZVowRX\ - V6RnhxNHdabkNmNDB3S29BSjlpMWF3U3JCbkU5ald0bgpwNEY0aFduSlRwR2t5NWRSQ\ - UxFMGwvMkFicmwzOHdnZk04cjRJb3RtUFRoRktuRmVJSFU3YlExcllBb3FwYkFoCkN2\ - MEJONVBqQVFSV01rNmJvbzNmMGFrUzA3bmxZSVZxWGh4cWNZbk9nd2tkbFR0WDlNcUd\ - JcTI2bjhuMU5XV3cKbm1LT2pOc2s2cVJtdWxFZ2VHTzR2eFR2U0pZYitoVT0KLS0tLS\ - 1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", - "serial_number": "67955938755654933561614970125599055831405010529", - "valid_from": "2023-03-11T18:31:28Z" - }], + "cert_chain": [ + { + "expiration_time": "2023-03-11T12:57:26Z", + "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNXekNDQVVPZ0F3SUJBZ0lVWnlUOTI5c3d0QjhPSG1qUmFURWFENnlqcWc0d0RRWUpLb1pJaHZjTgpBUUVMQlFBd0dERVdNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZUZ3MHlNekF6TVRFd05UVTMKTWpaYUZ3MHlNekF6TVRFeE1qVTNNalphTUJneEZqQVVCZ05WQkFvTURXTnNkWE4wWlhJdWJHOWpZV3d3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSYXIyQm1JWUFndkptT3JTcENlRlE3OUpQeQo4Y3c0K3pFRThmcXI1N2svdW1NcDVqWFpFR0JwZWRCSVkrcWZtSlBYRWlyYTlFOTJkU21rZks1QUtNV3gKbzJnd1pqQTFCZ05WSFJFRUxqQXNoaXB6Y0dsbVptVTZMeTkwY25WemRGOWtiMjFoYVc0dmJuTXZibUZ0ClpYTndZV05sTDNOaEwzTmhMVEF3RGdZRFZSMFBBUUgvQkFRREFnV2dNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQkJnZ3JCZ0VGQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFjTzNlMjAvK0ZrRkwKUmttMTNtQlFNYjVPUmpTOGhwWjBRMkZKd2wrSXV4TGY2MUJDZS9RVlhOVklpSUdlMXRVRTh5UTRoMXZrCjhVb01sSmpTQkdiM3VDdHVLRFVKN0xOM1VBUmV4YU1uQkZobC9mWmQxU3ZZcmhlWjU3WDlrTElVa2hkSQpDUVdxOFVFcXBWZEloNGxTZjhoYnFRQksvUWhCN0I2bUJOSW5uMThZTEhiOEpmU0N2aXBWYTRuNXByTlYKbVNWc1JPMUtpY1FQYVhpUzJta0xBWVFRanROYkVJdnJwQldCYytmVWZPaEQ0YmhwUFVmSVFIN1dFcUZLCm5TMnQwSmh1d08zM2FoUDhLZVBWWDRDRkJ4VXc2SDhrd1dJUkh5dW9YbGFwMmVST1EycFRyYmtmVjJZbgpmWjZxV0huREJ5ZjN6bkFQQVM1ZnZ4b1RoKzBYTHc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "serial_number": "588850990443535479077311695632745359443207891470", + "valid_from": "2023-03-11T05:57:26Z" + }, + { + "expiration_time": "2296-12-24T18:31:28Z", + "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lVQytjLzYwZStGMWVFKzdWcXhuYVdjT09abm1Fd0RRWUpLb1pJaHZjTgpBUUVMQlFBd0dERVdNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZ0Z3MHlNekF6TVRFeE9ETXgKTWpoYUdBOHlNamsyTVRJeU5ERTRNekV5T0Zvd0dERVdNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oCmJEQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1lQ1R4UEp0dWQwVXh3KwpDYWFkZFdEN2ErUUV1UVkrQlBUS0pkbk1lajBzQk1mVU1iVDE2SkxrWU5GZ3JqMVVWSEhjcFNvSUhvY3AKMnNkMzJTWTRiZGJva1Fjb3ArQmp0azU1alE0NktMWXNKZ2IyTnd2WW8xdDhFMWFldEpxRkdWN3JtZVpiCkZZZWFpKzZxN2lNamxiQ0dBdTcvVW5LSnNkR25hSlFnTjhkdTBUMUtEZ2pxS1B5SHFkc3U5a2JwQ3FpRQpYTVJtdzQvQkVoRkd6bUlEMm9VREtCMzZkdVZiZHpTRW01MVF2Z1U1SUxYSWd5VnJlak41Q0ZzQytXK3gKamVPWExFenRmSEZVb3FiM3dXaGtCdUV4bXI4MUoyaEdXOXBVTEoyd2tRZ2RmWFA3Z3RNa0I2RXlLdy94CkllYU5tTHpQSUdyWDAxelFZSWRaVHVEd01ZMENBd0VBQWFOVE1GRXdIUVlEVlIwT0JCWUVGRDhrNGYxYQpya3V3UitVUmhLQWUySVRaS1o3Vk1COEdBMVVkSXdRWU1CYUFGRDhrNGYxYXJrdXdSK1VSaEtBZTJJVFoKS1o3Vk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcm5BZVNzClNTSzMvOHp4K2h6ajZTRlhkSkE5Q1EwMkdFSjdoSHJLaWpHV1ZZZGRhbDlkQWJTNXRMZC8vcUtPOXVJcwpHZXR5L09rMmJSUTZjcXFNbGdkTnozam1tcmJTbFlXbUlYSTB5SEdtQ2lTYXpIc1hWYkVGNkl3eTN0Y1IKNHZvWFdLSUNXUGgrQzJjVGdMbWVaMEV1ekZ4cTR3Wm5DZjQwd0tvQUo5aTFhd1NyQm5FOWpXdG5wNEY0CmhXbkpUcEdreTVkUkFMRTBsLzJBYnJsMzh3Z2ZNOHI0SW90bVBUaEZLbkZlSUhVN2JRMXJZQW9xcGJBaApDdjBCTjVQakFRUldNazZib28zZjBha1MwN25sWUlWcVhoeHFjWW5PZ3drZGxUdFg5TXFHSXEyNm44bjEKTldXd25tS09qTnNrNnFSbXVsRWdlR080dnhUdlNKWWIraFU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", + "serial_number": "67955938755654933561614970125599055831405010529", + "valid_from": "2023-03-11T18:31:28Z" + } + ], "identity": "spiffe://trust_domain/ns/namespace/sa/sa-0", "state": "Available" }, { - "ca_cert": [{ - "expiration_time": "2023-03-11T13:57:26Z", - "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNkekNDQVYrZ0F3SUJBZ0lVWEl\ - QK29ySVF3dDZFUGRLSFdRU0VMOTM0bjdFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERV\ - dNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZUZ3MHlNekF6TVRFd05qVTNNa\ - lphRncweQpNekF6TVRFeE16VTNNalphTUFBd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtq\ - T1BRTUJCd05DQUFSYXIyQm1JWUFnCnZKbU9yU3BDZUZRNzlKUHk4Y3c0K3pFRThmcXI\ - 1N2svdW1NcDVqWFpFR0JwZWRCSVkrcWZtSlBYRWlyYTlFOTIKZFNta2ZLNUFLTVd4bz\ - RHYk1JR1lNQTRHQTFVZER3RUIvd1FFQXdJRm9EQWRCZ05WSFNVRUZqQVVCZ2dyQmdFR\ - gpCUWNEQVFZSUt3WUJCUVVIQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVH\ - REFXZ0JRL0pPSDlXcTVMCnNFZmxFWVNnSHRpRTJTbWUxVEE0QmdOVkhSRUJBZjhFTGp\ - Bc2hpcHpjR2xtWm1VNkx5OTBjblZ6ZEY5a2IyMWgKYVc0dmJuTXZibUZ0WlhOd1lXTm\ - xMM05oTDNOaExURXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQ3luRSt6UgpIK0t0c\ - 3lzNThOUDY3REdlaCtEMi91eG4zdkc0U1ZDT1RoTU03RFR3cWZQVVFxUDRxSlVxU3gv\ - cnRYUDJwZU40CmRhSStHMVBaUTNhNmhXZFlkTUNhMitxZnRmNFZDYVlZRkY5VjUxejh\ - NcVhqck9oOXlYWXhPWEwrejNnemdsaW8KYnVHTG83b3U3VDNTQ0NkUWZQRE93M3ZTUV\ - dlZFBXOU0yekVWT3V1RDJaTkd5REYzcEMrNEpxMzFuME45U0w2MgpOWjVCdFpLNHRKY\ - kF1WGJzZnJHQlRmRkxNd0c1SzlES3F6cW9hWjRJcXI2aUdjN2NqeGJ3M25sUlVwWGU4\ - NzMyCkpPSmYySXZPVTZ6NExPN1ludEFhU0ZvaGhZWE1wQ0ZqUWtKRmlYNnZvTm9TZm5\ - mTE44c1NxUklEcHVCWjlHOFIKaG96SG1GbGRNYWxoNlNzPQotLS0tLUVORCBDRVJUSU\ - ZJQ0FURS0tLS0tCg==", - "serial_number": "528170730419860468572163268563070820131458817969", - "valid_from": "2023-03-11T06:57:26Z" - }], - "cert_chain": [{ - "expiration_time": "2296-12-24T18:31:28Z", - "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lVQyt\ - jLzYwZStGMWVFKzdWcXhuYVdjT09abm1Fd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERV\ - dNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZ0Z3MHlNekF6TVRFeE9ETXhNa\ - mhhR0E4eQpNamsyTVRJeU5ERTRNekV5T0Zvd0dERVdNQlFHQTFVRUNnd05ZMngxYzNS\ - bGNpNXNiMk5oYkRDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0N\ - nZ0VCQU1lQ1R4UEp0dWQwVXh3K0NhYWRkV0Q3YStRRXVRWSsKQlBUS0pkbk1lajBzQk\ - 1mVU1iVDE2SkxrWU5GZ3JqMVVWSEhjcFNvSUhvY3Ayc2QzMlNZNGJkYm9rUWNvcCtCa\ - gp0azU1alE0NktMWXNKZ2IyTnd2WW8xdDhFMWFldEpxRkdWN3JtZVpiRlllYWkrNnE3\ - aU1qbGJDR0F1Ny9VbktKCnNkR25hSlFnTjhkdTBUMUtEZ2pxS1B5SHFkc3U5a2JwQ3F\ - pRVhNUm13NC9CRWhGR3ptSUQyb1VES0IzNmR1VmIKZHpTRW01MVF2Z1U1SUxYSWd5Vn\ - Jlak41Q0ZzQytXK3hqZU9YTEV6dGZIRlVvcWIzd1doa0J1RXhtcjgxSjJoRwpXOXBVT\ - Eoyd2tRZ2RmWFA3Z3RNa0I2RXlLdy94SWVhTm1MelBJR3JYMDF6UVlJZFpUdUR3TVkw\ - Q0F3RUFBYU5UCk1GRXdIUVlEVlIwT0JCWUVGRDhrNGYxYXJrdXdSK1VSaEtBZTJJVFp\ - LWjdWTUI4R0ExVWRJd1FZTUJhQUZEOGsKNGYxYXJrdXdSK1VSaEtBZTJJVFpLWjdWTU\ - E4R0ExVWRFd0VCL3dRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFTApCUUFEZ2dFQkFLc\ - m5BZVNzU1NLMy84engraHpqNlNGWGRKQTlDUTAyR0VKN2hIcktpakdXVllkZGFsOWRB\ - YlM1CnRMZC8vcUtPOXVJc0dldHkvT2syYlJRNmNxcU1sZ2ROejNqbW1yYlNsWVdtSVh\ - JMHlIR21DaVNhekhzWFZiRUYKNkl3eTN0Y1I0dm9YV0tJQ1dQaCtDMmNUZ0xtZVowRX\ - V6RnhxNHdabkNmNDB3S29BSjlpMWF3U3JCbkU5ald0bgpwNEY0aFduSlRwR2t5NWRSQ\ - UxFMGwvMkFicmwzOHdnZk04cjRJb3RtUFRoRktuRmVJSFU3YlExcllBb3FwYkFoCkN2\ - MEJONVBqQVFSV01rNmJvbzNmMGFrUzA3bmxZSVZxWGh4cWNZbk9nd2tkbFR0WDlNcUd\ - JcTI2bjhuMU5XV3cKbm1LT2pOc2s2cVJtdWxFZ2VHTzR2eFR2U0pZYitoVT0KLS0tLS\ - 1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", - "serial_number": "67955938755654933561614970125599055831405010529", - "valid_from": "2023-03-11T18:31:28Z" - }], + "cert_chain": [ + { + "expiration_time": "2023-03-11T13:57:26Z", + "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNXekNDQVVPZ0F3SUJBZ0lVWElQK29ySVF3dDZFUGRLSFdRU0VMOTM0bjdFd0RRWUpLb1pJaHZjTgpBUUVMQlFBd0dERVdNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZUZ3MHlNekF6TVRFd05qVTMKTWpaYUZ3MHlNekF6TVRFeE16VTNNalphTUJneEZqQVVCZ05WQkFvTURXTnNkWE4wWlhJdWJHOWpZV3d3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSYXIyQm1JWUFndkptT3JTcENlRlE3OUpQeQo4Y3c0K3pFRThmcXI1N2svdW1NcDVqWFpFR0JwZWRCSVkrcWZtSlBYRWlyYTlFOTJkU21rZks1QUtNV3gKbzJnd1pqQTFCZ05WSFJFRUxqQXNoaXB6Y0dsbVptVTZMeTkwY25WemRGOWtiMjFoYVc0dmJuTXZibUZ0ClpYTndZV05sTDNOaEwzTmhMVEV3RGdZRFZSMFBBUUgvQkFRREFnV2dNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQkJnZ3JCZ0VGQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFHV2tCY1plUEhrZisKSEpoazY5NHhDaHZLVENkVlRoNE9QNTBvWC9TdE0vK3NsazU0Y2RkcnRpOG0rdEFnai8wK0FLaFhpSTJaCjBNRFZPaEpOWTVRT1VXdkVBUWNYVTlPR2NCWmsyRWNGVW9BOC9RRzFpcVB3ejJJRGluakYrb3lTWExEdApFRGxPdW1Sa3VETWtyME51TGNZTlJuYUI0LzMreDAvdVlRM2M3TXpvUEtUQmZQdW1DY0wzbG5mR1dGR3kKc1d3b1p5V01CK1ZFdjYzK2psdTZDZmwzUGN1NEtFNHVhQUJiWHVvRkhjeU8yMW5sZVVvT3Z2VXhLZDdGCkxvQWNsVDNaSUI3dzNUcXE2MFR3UlV6ZGZkQlA5UURabEVSL1JLTDZWbnBBUVZhbXZBWmNjZFVuTWZjOAppT0N6TWVqV2tweGxXL3MrMW1nMUxzQWxyYlJMdHc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "serial_number": "528170730419860468572163268563070820131458817969", + "valid_from": "2023-03-11T06:57:26Z" + }, + { + "expiration_time": "2296-12-24T18:31:28Z", + "pem": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lVQytjLzYwZStGMWVFKzdWcXhuYVdjT09abm1Fd0RRWUpLb1pJaHZjTgpBUUVMQlFBd0dERVdNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oYkRBZ0Z3MHlNekF6TVRFeE9ETXgKTWpoYUdBOHlNamsyTVRJeU5ERTRNekV5T0Zvd0dERVdNQlFHQTFVRUNnd05ZMngxYzNSbGNpNXNiMk5oCmJEQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1lQ1R4UEp0dWQwVXh3KwpDYWFkZFdEN2ErUUV1UVkrQlBUS0pkbk1lajBzQk1mVU1iVDE2SkxrWU5GZ3JqMVVWSEhjcFNvSUhvY3AKMnNkMzJTWTRiZGJva1Fjb3ArQmp0azU1alE0NktMWXNKZ2IyTnd2WW8xdDhFMWFldEpxRkdWN3JtZVpiCkZZZWFpKzZxN2lNamxiQ0dBdTcvVW5LSnNkR25hSlFnTjhkdTBUMUtEZ2pxS1B5SHFkc3U5a2JwQ3FpRQpYTVJtdzQvQkVoRkd6bUlEMm9VREtCMzZkdVZiZHpTRW01MVF2Z1U1SUxYSWd5VnJlak41Q0ZzQytXK3gKamVPWExFenRmSEZVb3FiM3dXaGtCdUV4bXI4MUoyaEdXOXBVTEoyd2tRZ2RmWFA3Z3RNa0I2RXlLdy94CkllYU5tTHpQSUdyWDAxelFZSWRaVHVEd01ZMENBd0VBQWFOVE1GRXdIUVlEVlIwT0JCWUVGRDhrNGYxYQpya3V3UitVUmhLQWUySVRaS1o3Vk1COEdBMVVkSXdRWU1CYUFGRDhrNGYxYXJrdXdSK1VSaEtBZTJJVFoKS1o3Vk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcm5BZVNzClNTSzMvOHp4K2h6ajZTRlhkSkE5Q1EwMkdFSjdoSHJLaWpHV1ZZZGRhbDlkQWJTNXRMZC8vcUtPOXVJcwpHZXR5L09rMmJSUTZjcXFNbGdkTnozam1tcmJTbFlXbUlYSTB5SEdtQ2lTYXpIc1hWYkVGNkl3eTN0Y1IKNHZvWFdLSUNXUGgrQzJjVGdMbWVaMEV1ekZ4cTR3Wm5DZjQwd0tvQUo5aTFhd1NyQm5FOWpXdG5wNEY0CmhXbkpUcEdreTVkUkFMRTBsLzJBYnJsMzh3Z2ZNOHI0SW90bVBUaEZLbkZlSUhVN2JRMXJZQW9xcGJBaApDdjBCTjVQakFRUldNazZib28zZjBha1MwN25sWUlWcVhoeHFjWW5PZ3drZGxUdFg5TXFHSXEyNm44bjEKTldXd25tS09qTnNrNnFSbXVsRWdlR080dnhUdlNKWWIraFU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", + "serial_number": "67955938755654933561614970125599055831405010529", + "valid_from": "2023-03-11T18:31:28Z" + } + ], "identity": "spiffe://trust_domain/ns/namespace/sa/sa-1", "state": "Available" } diff --git a/src/app.rs b/src/app.rs index e2cd4092c..cea644307 100644 --- a/src/app.rs +++ b/src/app.rs @@ -21,7 +21,6 @@ use std::pin::Pin; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{mpsc, Arc}; use std::thread; -use std::time::Duration; use anyhow::Context; use prometheus_client::registry::Registry; @@ -30,7 +29,7 @@ use tracing::{warn, Instrument}; use crate::identity::SecretManager; use crate::state::ProxyStateManager; -use crate::{admin, config, identity, metrics, proxy, readiness, signal}; +use crate::{admin, config, metrics, proxy, readiness, signal}; use crate::{dns, xds}; pub async fn build_with_cert( @@ -280,13 +279,23 @@ fn new_data_plane_pool(num_worker_threads: usize) -> mpsc::Sender pub async fn build(config: config::Config) -> anyhow::Result { let cert_manager = if config.fake_ca { - identity::mock::new_secret_manager(Duration::from_secs(86400)) + mock_secret_manager() } else { Arc::new(SecretManager::new(config.clone()).await?) }; build_with_cert(config, cert_manager).await } +#[cfg(feature = "testing")] +fn mock_secret_manager() -> Arc { + crate::identity::mock::new_secret_manager(std::time::::from_secs(86400)) +} + +#[cfg(not(feature = "testing"))] +fn mock_secret_manager() -> Arc { + unimplemented!("fake_ca requires --feature testing") +} + #[cfg(not(target_os = "linux"))] fn init_inpod_proxy_mgr( _registry: &mut Registry, diff --git a/src/hyper_util.rs b/src/hyper_util.rs index 22479e0fd..5e63ad70f 100644 --- a/src/hyper_util.rs +++ b/src/hyper_util.rs @@ -33,16 +33,15 @@ use tokio::net::{TcpListener, TcpStream}; use tokio_stream::Stream; use tracing::{debug, info, warn}; -use crate::tls::{BoringTlsAcceptor, ServerCertProvider}; +use crate::tls::ServerCertProvider; pub fn tls_server( - acceptor: T, + cert_provider: T, listener: TcpListener, -) -> impl Stream> { +) -> impl Stream> { use tokio_stream::StreamExt; - let boring_acceptor = BoringTlsAcceptor { acceptor }; - tls_listener::builder(boring_acceptor) + tls_listener::builder(crate::tls::InboundAcceptor::new(cert_provider)) .listen(listener) .filter_map(|conn| { // Avoid 'By default, if a client fails the TLS handshake, that is treated as an error, and the TlsListener will return an Err' @@ -58,7 +57,7 @@ pub fn tls_server( } }) .map(|(conn, _)| { - conn.get_ref().set_nodelay(true).unwrap(); + conn.get_ref().0.set_nodelay(true).unwrap(); conn }) } diff --git a/src/identity.rs b/src/identity.rs index 2d68bd4a9..317676982 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -24,6 +24,7 @@ pub use manager::*; mod auth; pub use auth::*; +#[cfg(any(test, feature = "testing"))] pub mod mock { pub use super::caclient::mock::CaClient; pub use super::manager::mock::{ diff --git a/src/identity/caclient.rs b/src/identity/caclient.rs index 0171ba387..5474ff4cb 100644 --- a/src/identity/caclient.rs +++ b/src/identity/caclient.rs @@ -19,12 +19,12 @@ use prost_types::value::Kind; use prost_types::Struct; use tonic::codegen::InterceptedService; -use tracing::{instrument, warn}; +use tracing::{error, instrument, warn}; use crate::identity::auth::AuthSource; use crate::identity::manager::Identity; use crate::identity::Error; -use crate::tls::{self, SanChecker, TlsGrpcChannel}; +use crate::tls::{self, TlsGrpcChannel}; use crate::xds::istio::ca::istio_certificate_service_client::IstioCertificateServiceClient; use crate::xds::istio::ca::IstioCertificateRequest; @@ -57,15 +57,14 @@ impl CaClient { impl CaClient { #[instrument(skip_all)] - async fn fetch_certificate(&self, id: &Identity) -> Result { - let cs = tls::CsrOptions { + async fn fetch_certificate(&self, id: &Identity) -> Result { + let cs = tls::csr::CsrOptions { san: id.to_string(), } .generate()?; - let csr: Vec = cs.csr; - let pkey = cs.pkey; + let csr = cs.csr; + let private_key = cs.private_key; - let csr = std::str::from_utf8(&csr).map_err(Error::Utf8)?.to_string(); let req = IstioCertificateRequest { csr, validity_duration: self.secret_ttl, @@ -101,11 +100,15 @@ impl CaClient { warn!("no chain certs for: {}", id); vec![] }; - let certs = tls::cert_from(&pkey, leaf, chain); - if self.enable_impersonated_identity { - certs - .verify_san(&[id.clone()]) - .map_err(|_| Error::SanError(id.to_owned()))?; + let certs = tls::WorkloadCertificate::new(&private_key, leaf, chain)?; + // Make the certificate actually matches the identity we requested. + if self.enable_impersonated_identity && certs.cert.identity().as_ref() != Some(id) { + error!( + "expected identity {:?}, got {:?}", + id, + certs.cert.identity() + ); + return Err(Error::SanError(id.to_owned())); } Ok(certs) } @@ -113,11 +116,12 @@ impl CaClient { #[async_trait] impl crate::identity::CaClientTrait for CaClient { - async fn fetch_certificate(&self, id: &Identity) -> Result { + async fn fetch_certificate(&self, id: &Identity) -> Result { self.fetch_certificate(id).await } } +#[cfg(any(test, feature = "testing"))] pub mod mock { use std::sync::Arc; use std::time::Duration; @@ -126,15 +130,13 @@ pub mod mock { use tokio::time::Instant; use crate::identity::Identity; - use crate::tls::mock::CertGenerator; - use crate::tls::Certs; use super::*; #[derive(Default)] struct ClientState { fetches: Vec, - gen: CertGenerator, + gen: tls::mock::CertGenerator, } #[derive(Clone)] @@ -186,7 +188,10 @@ pub mod mock { self.state.write().await.fetches.clear(); } - async fn fetch_certificate(&self, id: &Identity) -> Result { + async fn fetch_certificate( + &self, + id: &Identity, + ) -> Result { let Identity::Spiffe { trust_domain: td, namespace: ns, @@ -240,7 +245,10 @@ pub mod mock { #[async_trait] impl crate::identity::CaClientTrait for CaClient { - async fn fetch_certificate(&self, id: &Identity) -> Result { + async fn fetch_certificate( + &self, + id: &Identity, + ) -> Result { self.fetch_certificate(id).await } } @@ -248,6 +256,7 @@ pub mod mock { #[cfg(test)] mod tests { + use std::iter; use std::time::Duration; use matches::assert_matches; @@ -260,7 +269,7 @@ mod tests { async fn test_ca_client_with_response( res: IstioCertificateResponse, - ) -> Result { + ) -> Result { let (mock, ca_client) = test_helpers::ca::CaServer::spawn().await; mock.send(Ok(res)).unwrap(); ca_client.fetch_certificate(&Identity::default()).await @@ -280,11 +289,17 @@ mod tests { namespace: "foo".to_string(), trust_domain: "cluster.local".to_string(), }; - let certs = - tls::generate_test_certs(&id.into(), Duration::from_secs(0), Duration::from_secs(0)); + let certs = tls::mock::generate_test_certs( + &id.into(), + Duration::from_secs(0), + Duration::from_secs(0), + ); let res = test_ca_client_with_response(IstioCertificateResponse { - cert_chain: vec![String::from_utf8(certs.x509().to_pem().unwrap()).unwrap()], + cert_chain: iter::once(certs.cert) + .chain(certs.chain) + .map(|c| c.as_pem()) + .collect(), }) .await; assert_matches!(res, Err(Error::SanError(_))); @@ -292,13 +307,17 @@ mod tests { #[tokio::test] async fn fetch_certificate() { - let certs = tls::generate_test_certs( + let certs = tls::mock::generate_test_certs( &Identity::default().into(), Duration::from_secs(0), Duration::from_secs(0), ); + let res = test_ca_client_with_response(IstioCertificateResponse { - cert_chain: vec![String::from_utf8(certs.x509().to_pem().unwrap()).unwrap()], + cert_chain: iter::once(certs.cert) + .chain(certs.chain) + .map(|c| c.as_pem()) + .collect(), }) .await; assert_matches!(res, Ok(_)); diff --git a/src/identity/manager.rs b/src/identity/manager.rs index d0509f5c1..af288a267 100644 --- a/src/identity/manager.rs +++ b/src/identity/manager.rs @@ -15,7 +15,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::fmt; -use std::fmt::Write; +use std::fmt::{Formatter, Write}; use std::str::FromStr; use std::sync::Arc; @@ -104,7 +104,7 @@ impl Default for Identity { #[async_trait] pub trait CaClientTrait: Send + Sync { - async fn fetch_certificate(&self, id: &Identity) -> Result; + async fn fetch_certificate(&self, id: &Identity) -> Result; } #[derive(PartialOrd, PartialEq, Eq, Ord, Debug, Copy, Clone)] @@ -122,7 +122,7 @@ pub enum Priority { pub enum CertState { // Should happen only on the first request for an Identity. Initializing(Priority), - Available(tls::Certs), + Available(Arc), // The last attempt to fetch the certificate has failed and there is no previous certificate // available. // @@ -332,7 +332,7 @@ impl Worker { // Reset the backoff on success. // [`reset`](https://docs.rs/backoff/0.4.0/backoff/backoff/trait.Backoff.html#method.reset) cert_backoff.reset(); - let certs: tls::Certs = certs; // Type annotation. + let certs: tls::WorkloadCertificate = certs; // Type annotation. let refresh_at = self.time_conv.system_time_to_instant(certs.refresh_at()); let refresh_at = if let Some(t) = refresh_at { t.into() @@ -347,7 +347,7 @@ impl Worker { // conversion here, so for now leaving the code as is. Instant::now() }; - (CertState::Available(certs), refresh_at) + (CertState::Available(Arc::new(certs)), refresh_at) }, }; if self.update_certs(&id, state).await { @@ -408,6 +408,7 @@ pub struct SecretManagerConfig { } /// SecretManager provides a wrapper around a CaClient with caching. +#[derive(Clone)] pub struct SecretManager { worker: Arc, // Channel to which certificate requests are sent to. The Identity for which request is being @@ -416,11 +417,17 @@ pub struct SecretManager { requests: mpsc::Sender, } +impl fmt::Debug for SecretManager { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("SecretManager").finish() + } +} + impl SecretManager { pub async fn new(cfg: crate::config::Config) -> Result { let caclient = CaClient::new( cfg.ca_address.unwrap(), - Box::new(tls::FileClientCertProviderImpl::RootCert( + Box::new(tls::ControlPlaneAuthentication::RootCert( cfg.ca_root_cert.clone(), )), cfg.auth, @@ -494,7 +501,10 @@ impl SecretManager { } } - async fn wait(&self, mut rx: watch::Receiver) -> Result { + async fn wait( + &self, + mut rx: watch::Receiver, + ) -> Result, Error> { loop { tokio::select! { // Wait for the initial value if not ready yet. @@ -520,14 +530,17 @@ impl SecretManager { &self, id: &Identity, pri: Priority, - ) -> Result { + ) -> Result, Error> { // This method is intentionally left simple, since unit tests are based on start_fetch // and wait. Any changes should go to one of those two methods, and if that proves // impossible - unit testing strategy may need to be rethinked. self.wait(self.start_fetch(id, pri).await?).await } - pub async fn fetch_certificate(&self, id: &Identity) -> Result { + pub async fn fetch_certificate( + &self, + id: &Identity, + ) -> Result, Error> { self.fetch_certificate_pri(id, Priority::RealTime).await } @@ -557,6 +570,7 @@ fn init_pri(rx: &watch::Receiver) -> Option { } } +#[cfg(any(test, feature = "testing"))] pub mod mock { use std::{ sync::Arc, @@ -666,7 +680,7 @@ mod tests { .await .expect("Didn't get a cert as expected."); - if current_cert != new_cert { + if current_cert.cert.serial() != new_cert.cert.serial() { total_updates += 1; current_cert = new_cert; } @@ -950,7 +964,7 @@ mod tests { .unwrap(); for rx in rxs_iter { let got = test.secret_manager.wait(rx).await.unwrap(); - assert_eq!(got, want); + assert!(Arc::ptr_eq(&want, &got)); } assert_eq!(test.caclient.fetches().await.len(), 1); diff --git a/src/proxy.rs b/src/proxy.rs index 7fa7ab2c7..1d231713f 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -18,7 +18,6 @@ use std::sync::Arc; use std::time::Duration; use std::{fmt, io}; -use boring::error::ErrorStack; use drain::Watch; use hyper::{header, Request}; use rand::Rng; @@ -216,9 +215,6 @@ pub enum Error { #[error("{0}")] Generic(Box), - #[error("tls handshake failed: {0:?}")] - TlsHandshake(#[from] tokio_boring::HandshakeError), - #[error("http handshake failed: {0}")] HttpHandshake(#[source] hyper::Error), @@ -231,9 +227,6 @@ pub enum Error { #[error("tls error: {0}")] Tls(#[from] tls::Error), - #[error("ssl error: {0}")] - Ssl(#[from] ErrorStack), - #[error("identity error: {0}")] Identity(#[from] identity::Error), diff --git a/src/proxy/inbound.rs b/src/proxy/inbound.rs index 2b051e8e8..8ac76352b 100644 --- a/src/proxy/inbound.rs +++ b/src/proxy/inbound.rs @@ -25,21 +25,23 @@ use http_body_util::Empty; use hyper::body::Incoming; use hyper::service::service_fn; use hyper::{Method, Request, Response, StatusCode}; + use tokio::net::{TcpListener, TcpStream}; + use tracing::{debug, error, info, instrument, trace, trace_span, warn, Instrument}; use super::connection_manager::ConnectionManager; use super::{Error, SocketFactory}; use crate::baggage::parse_baggage_header; use crate::config::Config; -use crate::identity::SecretManager; +use crate::identity::{Identity, SecretManager}; use crate::metrics::Recorder; -use crate::proxy; use crate::proxy::inbound::InboundConnect::{DirectPath, Hbone}; use crate::proxy::metrics::{ConnectionOpen, Metrics, Reporter}; use crate::proxy::{metrics, ProxyInputs, TraceParent, BAGGAGE_HEADER, TRACEPARENT_HEADER}; use crate::rbac::Connection; use crate::socket::to_canonical; +use crate::{proxy, tls}; use crate::state::workload::{address, GatewayAddress, NetworkAddress, Workload}; use crate::state::DemandProxyState; @@ -99,7 +101,11 @@ impl Inbound { let (sub_drain_signal, sub_drain) = drain::channel(); - while let Some(socket) = stream.next().await { + while let Some(tls) = stream.next().await { + let (raw_socket, ssl) = tls.get_ref(); + let src_identity: Option = tls::identity_from_connection(ssl); + let dst = crate::socket::orig_dst_addr_or_default(raw_socket); + let src_ip = to_canonical(raw_socket.peer_addr().unwrap()).ip(); let state = self.state.clone(); let metrics = self.metrics.clone(); let socket_factory = self.socket_factory.clone(); @@ -107,13 +113,9 @@ impl Inbound { let drain = sub_drain.clone(); let network = self.cfg.network.clone(); tokio::task::spawn(async move { - let dst = crate::socket::orig_dst_addr_or_default(socket.get_ref()); let conn = Connection { - src_identity: socket - .ssl() - .peer_certificate() - .and_then(|x| crate::tls::boring::extract_sans(&x).first().cloned()), - src_ip: to_canonical(socket.get_ref().peer_addr().unwrap()).ip(), + src_identity, + src_ip, dst_network: network, // inbound request must be on our network dst, }; @@ -124,7 +126,7 @@ impl Inbound { .initial_connection_window_size(self.cfg.connection_window_size) .max_frame_size(self.cfg.frame_size) .serve_connection( - hyper_util::rt::TokioIo::new(socket), + hyper_util::rt::TokioIo::new(tls), service_fn(move |req| { Self::serve_connect( state.clone(), @@ -503,7 +505,7 @@ struct InboundCertProvider { #[async_trait::async_trait] impl crate::tls::ServerCertProvider for InboundCertProvider { - async fn fetch_cert(&mut self, fd: &TcpStream) -> Result { + async fn fetch_cert(&mut self, fd: &TcpStream) -> Result, TlsError> { let orig_dst_addr = crate::socket::orig_dst_addr_or_default(fd); let identity = { let wip = NetworkAddress { @@ -522,8 +524,7 @@ impl crate::tls::ServerCertProvider for InboundCertProvider { "fetching cert" ); let cert = self.cert_manager.fetch_certificate(&identity).await?; - let acc = cert.mtls_acceptor(Some(&identity))?; - Ok(acc) + Ok(Arc::new(cert.server_config()?)) } } diff --git a/src/proxy/outbound.rs b/src/proxy/outbound.rs index 44cb2468e..a674ff6c7 100644 --- a/src/proxy/outbound.rs +++ b/src/proxy/outbound.rs @@ -17,13 +17,14 @@ use std::str::FromStr; use std::time::Instant; -use boring::ssl::ConnectConfiguration; use bytes::Bytes; use drain::Watch; use http_body_util::Empty; use hyper::header::FORWARDED; use hyper::StatusCode; + use tokio::net::{TcpListener, TcpStream}; + use tracing::{debug, error, info, info_span, trace, trace_span, warn, Instrument}; use crate::config::ProxyMode; @@ -273,10 +274,7 @@ impl OutboundConnection { .then_some(remote_addr); let id = &req.source.identity(); let cert = self.pi.cert_manager.fetch_certificate(id).await?; - let connector = cert - .connector(dst_identity)? - .configure() - .expect("configure"); + let connector = cert.outbound_connector(dst_identity)?; let tcp_stream = super::freebind_connect( local, req.gateway, @@ -284,7 +282,7 @@ impl OutboundConnection { ) .await?; tcp_stream.set_nodelay(true)?; // TODO: this is backwards of expectations - let tls_stream = connect_tls(connector, tcp_stream).await?; + let tls_stream = connector.connect(tcp_stream).await?; let (request_sender, connection) = builder .handshake(::hyper_util::rt::TokioIo::new(tls_stream)) .await @@ -559,15 +557,6 @@ enum RequestType { Passthrough, } -pub async fn connect_tls( - mut connector: ConnectConfiguration, - stream: TcpStream, -) -> Result, tokio_boring::HandshakeError> { - connector.set_verify_hostname(false); - connector.set_use_server_name_indication(false); - tokio_boring::connect(connector, "", stream).await -} - #[cfg(test)] mod tests { use std::time::Duration; diff --git a/src/state.rs b/src/state.rs index bb0e00d80..32007df4d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -619,7 +619,7 @@ impl ProxyStateManager { let state: Arc> = Arc::new(RwLock::new(ProxyState::default())); let xds_client = if config.xds_address.is_some() { let updater = ProxyStateUpdater::new(state.clone(), cert_fetcher.clone()); - let tls_client_fetcher = Box::new(tls::FileClientCertProviderImpl::RootCert( + let tls_client_fetcher = Box::new(tls::ControlPlaneAuthentication::RootCert( config.xds_root_cert.clone(), )); Some( diff --git a/src/test_helpers/ca.rs b/src/test_helpers/ca.rs index 070431965..2f3357abc 100644 --- a/src/test_helpers/ca.rs +++ b/src/test_helpers/ca.rs @@ -18,6 +18,7 @@ use std::time::Duration; use async_trait::async_trait; use futures::StreamExt; use hyper_util::rt::TokioIo; +use itertools::Itertools; use tokio::sync::watch; @@ -51,13 +52,13 @@ impl CaServer { let server = CaServer { response: rx }; let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let server_addr = listener.local_addr().unwrap(); - let certs = tls::generate_test_certs( + let certs = tls::mock::generate_test_certs( &server_addr.ip().into(), Duration::from_secs(0), Duration::from_secs(100), ); - let root_cert = RootCert::Static(certs.chain().unwrap()); - let acceptor = tls::ControlPlaneCertProvider(certs); + let root_cert = RootCert::Static(certs.chain.iter().map(|c| c.as_pem()).join("\n").into()); + let acceptor = tls::mock::MockServerCertProvider::new(certs); let mut tls_stream = crate::hyper_util::tls_server(acceptor, listener); let srv = IstioCertificateServiceServer::new(server); tokio::spawn(async move { @@ -76,7 +77,7 @@ impl CaServer { }); let client = CaClient::new( "https://".to_string() + &server_addr.to_string(), - Box::new(tls::FileClientCertProviderImpl::RootCert(root_cert)), + Box::new(tls::ControlPlaneAuthentication::RootCert(root_cert)), AuthSource::Token( PathBuf::from(r"src/test_helpers/fake-jwt"), "Kubernetes".to_string(), diff --git a/src/test_helpers/tcp.rs b/src/test_helpers/tcp.rs index 3fefda794..da9f973cd 100644 --- a/src/test_helpers/tcp.rs +++ b/src/test_helpers/tcp.rs @@ -188,7 +188,7 @@ impl HboneTestServer { } pub async fn run(self) { - let certs = tls::generate_test_certs( + let certs = tls::mock::generate_test_certs( &identity::Identity::Spiffe { trust_domain: "cluster.local".to_string(), namespace: "default".to_string(), @@ -198,7 +198,7 @@ impl HboneTestServer { Duration::from_secs(0), Duration::from_secs(100), ); - let acceptor = tls::ControlPlaneCertProvider(certs); + let acceptor = tls::mock::MockServerCertProvider::new(certs); let mut tls_stream = crate::hyper_util::tls_server(acceptor, self.listener); let mode = self.mode; while let Some(socket) = tls_stream.next().await { diff --git a/src/test_helpers/xds.rs b/src/test_helpers/xds.rs index 5ce54b4b4..13e809e7e 100644 --- a/src/test_helpers/xds.rs +++ b/src/test_helpers/xds.rs @@ -24,6 +24,7 @@ use futures::StreamExt; use hickory_resolver::config::{ResolverConfig, ResolverOpts}; use hyper::server::conn::http2; use hyper_util::rt::TokioIo; +use itertools::Itertools; use prometheus_client::registry::Registry; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; @@ -67,13 +68,13 @@ impl AdsServer { let server = AdsServer { tx }; let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let server_addr = listener.local_addr().unwrap(); - let certs = tls::generate_test_certs( + let certs = tls::mock::generate_test_certs( &server_addr.ip().into(), Duration::from_secs(0), Duration::from_secs(100), ); - let root_cert = RootCert::Static(certs.chain().unwrap()); - let acceptor = tls::ControlPlaneCertProvider(certs); + let root_cert = RootCert::Static(certs.chain.iter().map(|c| c.as_pem()).join("\n").into()); + let acceptor = tls::mock::MockServerCertProvider::new(certs); let listener_addr_string = "https://".to_string() + &server_addr.to_string(); let mut tls_stream = crate::hyper_util::tls_server(acceptor, listener); let srv = AggregatedDiscoveryServiceServer::new(server); @@ -116,7 +117,7 @@ impl AdsServer { ResolverOpts::default(), ); let store_updater = ProxyStateUpdater::new_no_fetch(state); - let tls_client_fetcher = Box::new(tls::FileClientCertProviderImpl::RootCert( + let tls_client_fetcher = Box::new(tls::ControlPlaneAuthentication::RootCert( cfg.xds_root_cert.clone(), )); let xds_client = xds::Config::new(cfg, tls_client_fetcher) diff --git a/src/tls.rs b/src/tls.rs index c64423a1e..a2b43c772 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -12,24 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod boring; +mod certificate; +mod control; +pub mod csr; +mod lib; +#[cfg(any(test, feature = "testing"))] +pub mod mock; +mod workload; use std::sync::Arc; -pub use crate::tls::boring::*; -use ::boring::error::ErrorStack; +pub use crate::tls::certificate::*; +pub use crate::tls::control::*; +pub use crate::tls::lib::*; +pub use crate::tls::workload::*; use hyper::http::uri::InvalidUri; +use rustls::server::VerifierBuilderError; #[derive(thiserror::Error, Debug, Clone)] pub enum Error { - #[error("invalid operation: {0:?}")] - SslError(#[from] ErrorStack), - #[error("invalid root certificate: {0}")] - InvalidRootCert(ErrorStack), + InvalidRootCert(String), #[error("invalid uri: {0}")] InvalidUri(#[from] Arc), + + #[error("tls: {0}")] + Tls(#[from] rustls::Error), + + #[error("certificate parse: {0}")] + CertificateParseNomError(#[from] x509_parser::nom::Err), + + #[error("certificate: {0}")] + CertificateError(#[from] x509_parser::error::X509Error), + + #[error("certificate: {0}")] + CertificateParseError(String), + + #[error("invalid operation: {0:?}")] + #[cfg(feature = "tls-boring")] + SslError(#[from] boring::error::ErrorStack), + + #[error("failed to build server verifier: {0}")] + ServerVerifierBuilderError(#[from] VerifierBuilderError), } impl From for Error { diff --git a/src/tls/boring.rs b/src/tls/boring.rs deleted file mode 100644 index 4bd0174b5..000000000 --- a/src/tls/boring.rs +++ /dev/null @@ -1,956 +0,0 @@ -use std::fmt::Debug; -use std::future::Future; -use std::net::IpAddr; -use std::pin::Pin; -// Copyright Istio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use super::Error; -use crate::config::RootCert; -use crate::identity::{self, Identity}; -use crate::state::workload::NetworkAddress; -use boring::asn1::{Asn1Time, Asn1TimeRef}; -use boring::bn::BigNum; -use boring::ec::{EcGroup, EcKey}; -use boring::hash::MessageDigest; -use boring::nid::Nid; -use boring::pkey; -use boring::pkey::{PKey, Private}; -use boring::ssl::{self, SslContextBuilder}; -use boring::stack::Stack; -use boring::x509::extension::{ - AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName, -}; -use boring::x509::verify::X509CheckFlags; -use boring::x509::{self, X509StoreContext, X509StoreContextRef, X509VerifyError}; -use bytes::Bytes; -use http_body_1::{Body, Frame}; -use hyper::body::Incoming; -use hyper::Uri; -use hyper_boring::HttpsConnector; -use hyper_util::client::legacy::connect::HttpConnector; -use itertools::Itertools; -use rand::RngCore; -use std::str::FromStr; -use std::task::{Context, Poll}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use tokio::net::TcpStream; -use tonic::body::BoxBody; -use tower_hyper_http_body_compat::http02_request_to_http1; -use tower_hyper_http_body_compat::http1_response_to_http02; -use tower_hyper_http_body_compat::{HttpBody04ToHttpBody1, HttpBody1ToHttpBody04}; -use tracing::{error, info}; - -pub fn asn1_time_to_system_time(time: &Asn1TimeRef) -> SystemTime { - let unix_time = Asn1Time::from_unix(0).unwrap().diff(time).unwrap(); - SystemTime::UNIX_EPOCH - + Duration::from_secs(unix_time.days as u64 * 86400 + unix_time.secs as u64) -} - -fn system_time_to_asn1_time(time: SystemTime) -> Option { - let ts = time.duration_since(UNIX_EPOCH).ok()?.as_secs(); - Asn1Time::from_unix(ts.try_into().ok()?).ok() -} - -pub fn cert_from(key: &[u8], cert: &[u8], chain: Vec<&[u8]>) -> Certs { - let key = pkey::PKey::private_key_from_pem(key).unwrap(); - let cert = x509::X509::from_pem(cert).unwrap(); - let ztunnel_cert = ZtunnelCert::new(cert); - let chain = chain - .into_iter() - .flat_map(|pem| x509::X509::stack_from_pem(pem).unwrap()) - .dedup_by(|x, y| { - match ( - x.digest(MessageDigest::sha256()), - y.digest(MessageDigest::sha256()), - ) { - (Ok(x), Ok(y)) => x.as_ref() == y.as_ref(), - _ => false, - } - }) - .map(ZtunnelCert::new) - .collect(); - Certs { - cert: ztunnel_cert, - chain, - key, - } -} - -pub struct CertSign { - pub csr: Vec, - pub pkey: Vec, -} - -pub struct CsrOptions { - pub san: String, -} - -impl CsrOptions { - pub fn generate(&self) -> Result { - let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; - let ec_key = EcKey::generate(&group)?; - let pkey = PKey::from_ec_key(ec_key)?; - - let mut csr = x509::X509ReqBuilder::new()?; - csr.set_pubkey(&pkey)?; - let mut extensions = Stack::new()?; - let subject_alternative_name = SubjectAlternativeName::new() - .uri(&self.san) - .critical() - .build(&csr.x509v3_context(None))?; - - extensions.push(subject_alternative_name)?; - csr.add_extensions(&extensions)?; - csr.sign(&pkey, MessageDigest::sha256())?; - - let csr = csr.build(); - let pkey_pem = pkey.private_key_to_pem_pkcs8()?; - let csr_pem = csr.to_pem()?; - Ok(CertSign { - csr: csr_pem, - pkey: pkey_pem, - }) - } -} - -#[derive(Clone, Debug)] -pub struct ZtunnelCert { - x509: x509::X509, - not_before: SystemTime, - not_after: SystemTime, -} - -// Wrapper around X509 that uses SystemTime for not_before/not_after. -// Asn1Time does not support sub-second granularity. -impl ZtunnelCert { - pub fn new(cert: x509::X509) -> ZtunnelCert { - ZtunnelCert { - not_before: asn1_time_to_system_time(cert.not_before()), - not_after: asn1_time_to_system_time(cert.not_after()), - x509: cert, // cert is already owned, the asn1_ functions borrow cert so as long as we move cert to ZtunnelCert after the borrows this doesn't need cloning - } - } -} - -#[derive(Clone, Debug)] -pub struct Certs { - // the leaf cert - cert: ZtunnelCert, - // the remainder of the chain, not including the leaf cert - chain: Vec, - key: pkey::PKey, -} - -impl PartialEq for Certs { - fn eq(&self, other: &Self) -> bool { - self.cert - .x509 - .to_der() - .iter() - .eq(other.cert.x509.to_der().iter()) - && self - .key - .private_key_to_der() - .iter() - .eq(other.key.private_key_to_der().iter()) - && self.cert.not_after == other.cert.not_after - && self.cert.not_before == other.cert.not_before - } -} - -impl Certs { - pub fn chain(&self) -> Result { - Ok(self.chain[0].x509.to_pem()?.into()) - } - - // TODO: This works very differently from the chain method. Figure out what's the intention - // behind the chain method and make things more consistent. - pub fn iter_chain(&self) -> impl Iterator { - self.chain.iter().map(|zcert| &zcert.x509) - } - - pub fn is_expired(&self) -> bool { - SystemTime::now() > self.cert.not_after - } - - pub fn refresh_at(&self) -> SystemTime { - match self.cert.not_after.duration_since(self.cert.not_before) { - Ok(valid_for) => self.cert.not_before + valid_for / 2, - Err(_) => self.cert.not_after, - } - } - - pub fn get_duration_until_refresh(&self) -> Duration { - let halflife = self - .cert - .not_after - .duration_since(self.cert.not_before) - .unwrap_or_else(|_| std::time::Duration::from_secs(0)) - / 2; - // If now() is earlier than not_before, we need to refresh ASAP, so return 0. - let elapsed = SystemTime::now() - .duration_since(self.cert.not_before) - .unwrap_or(halflife); - halflife - .checked_sub(elapsed) - .unwrap_or_else(|| Duration::from_secs(0)) - } - - pub fn x509(&self) -> &x509::X509 { - &self.cert.x509 - } -} - -#[derive(Clone, Debug)] -pub struct TlsGrpcChannel { - uri: Uri, - client: hyper_util::client::legacy::Client< - hyper_boring::HttpsConnector, - BoxBody1, - >, -} - -#[async_trait::async_trait] -pub trait ClientCertProvider: Send + Sync { - async fn fetch_cert(&self) -> Result; -} - -#[derive(Clone, Debug)] -pub enum FileClientCertProviderImpl { - RootCert(RootCert), - ClientBundle(Certs), -} - -#[async_trait::async_trait] -impl ClientCertProvider for FileClientCertProviderImpl { - async fn fetch_cert(&self) -> Result { - match self { - FileClientCertProviderImpl::RootCert(root_cert) => { - let mut conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; - - conn.set_verify(ssl::SslVerifyMode::PEER); - conn.set_alpn_protos(Alpn::H2.encode())?; - conn.set_min_proto_version(Some(ssl::SslVersion::TLS1_2))?; - conn.set_max_proto_version(Some(ssl::SslVersion::TLS1_3))?; - match root_cert { - RootCert::File(f) => { - conn.set_ca_file(f).map_err(Error::InvalidRootCert)?; - } - RootCert::Static(b) => { - conn.cert_store_mut() - .add_cert(x509::X509::from_pem(b).map_err(Error::InvalidRootCert)?) - .map_err(Error::InvalidRootCert)?; - } - RootCert::Default => {} // Already configured to use system root certs - } - return Ok(conn); - } - FileClientCertProviderImpl::ClientBundle(bundle) => { - let mut conn: ssl::SslConnectorBuilder = - ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; - match bundle.setup_ctx(&mut conn) { - Ok(_) => { - return Ok(conn); - } - Err(e) => { - return Err(e); - } - } - } - } - } -} - -/// grpc_connector provides a client TLS channel for gRPC requests. -pub fn grpc_tls_connector(uri: String, root_cert: RootCert) -> Result { - let mut conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; - - conn.set_verify(ssl::SslVerifyMode::PEER); - conn.set_alpn_protos(Alpn::H2.encode())?; - conn.set_min_proto_version(Some(ssl::SslVersion::TLS1_2))?; - conn.set_max_proto_version(Some(ssl::SslVersion::TLS1_3))?; - match root_cert { - RootCert::File(f) => { - conn.set_ca_file(f).map_err(Error::InvalidRootCert)?; - } - RootCert::Static(b) => { - conn.cert_store_mut() - .add_cert(x509::X509::from_pem(&b).map_err(Error::InvalidRootCert)?) - .map_err(Error::InvalidRootCert)?; - } - RootCert::Default => {} // Already configured to use system root certs - } - - grpc_connector(uri, conn) -} - -/// grpc_connector provides a client TLS channel for gRPC requests. -pub fn grpc_connector( - uri: String, - conn: ssl::SslConnectorBuilder, -) -> Result { - let uri = Uri::try_from(uri)?; - let is_localhost_call = uri.host() == Some("localhost"); - let mut http: HttpConnector = HttpConnector::new(); - // Set keepalives to match istio's Envoy bootstrap configuration: - // https://github.com/istio/istio/blob/a29d5c9c27d80bff31f218936f5a96759d8911c8/tools/packaging/common/envoy_bootstrap.json#L322C14-L322C28 - // - // keepalive_interval and keepalive_retries match the linux default per Envoy docs: - // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-tcpkeepalive - http.set_keepalive(Some(Duration::from_secs(300))); - http.set_keepalive_interval(Some(Duration::from_secs(75))); - http.set_keepalive_retries(Some(9)); - http.set_connect_timeout(Some(Duration::from_secs(5))); - http.enforce_http(false); - let mut https: HttpsConnector = - hyper_boring::HttpsConnector::with_connector(http, conn)?; - https.set_callback(move |cc, _| { - if is_localhost_call { - // Follow Istio logic to allow localhost calls: https://github.com/istio/istio/blob/373fc89518c986c9f48ed3cd891930da6fdc8628/pkg/istio-agent/xds_proxy.go#L735 - cc.set_verify_hostname(false); - let param = cc.param_mut(); - param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS); - param.set_host("istiod.istio-system.svc").unwrap(); - } - Ok(()) - }); - - // Configure hyper's client to be h2 only and build with the - // correct https connector. - let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) - .http2_only(true) - .http2_keep_alive_interval(Duration::from_secs(30)) - .http2_keep_alive_timeout(Duration::from_secs(10)) - .timer(crate::hyper_util::TokioTimer) - .build(https); - - Ok(TlsGrpcChannel { uri, client }) -} - -type BoxBody1 = HttpBody04ToHttpBody1; - -#[derive(Default)] -pub enum DefaultIncoming { - Some(Incoming), - #[default] - Empty, -} - -impl Body for DefaultIncoming { - type Data = Bytes; - type Error = hyper::Error; - - fn poll_frame( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll, Self::Error>>> { - match self.get_mut() { - DefaultIncoming::Some(ref mut i) => Pin::new(i).poll_frame(cx), - DefaultIncoming::Empty => Pin::new(&mut http_body_util::Empty::::new()) - .poll_frame(cx) - .map_err(|_| unreachable!()), - } - } -} - -impl tower::Service> for TlsGrpcChannel { - type Response = http_02::Response>; - type Error = hyper_util::client::legacy::Error; - // type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Ok(()).into() - } - - fn call(&mut self, req: http_02::Request) -> Self::Future { - let mut req = http02_request_to_http1(req.map(HttpBody04ToHttpBody1::new)); - let uri = Uri::builder() - .scheme(self.uri.scheme().unwrap().to_owned()) - .authority(self.uri.authority().unwrap().to_owned()) - .path_and_query(req.uri().path_and_query().unwrap().to_owned()) - .build() - .unwrap(); - *req.uri_mut() = uri; - let future = self.client.request(req); - Box::pin(async move { - let res = future.await?; - Ok(http1_response_to_http02( - res.map(DefaultIncoming::Some) - .map(HttpBody1ToHttpBody04::new), - )) - }) - } -} - -impl Certs { - fn verify_mode() -> ssl::SslVerifyMode { - ssl::SslVerifyMode::PEER | ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT - } - - pub fn mtls_acceptor(&self, dest_id: Option<&Identity>) -> Result { - let _ctx = ssl::SslContext::builder(ssl::SslMethod::tls_server())?; - // mozilla_intermediate_v5 is the only variant that enables TLSv1.3, so we use that. - let mut conn = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; - self.setup_ctx(&mut conn)?; - - if let Some(dest_id) = dest_id { - // Validate that the source cert shares the same trust domain - conn.set_verify_callback( - Self::verify_mode(), - Verifier::SanTrustDomain(dest_id.clone()).callback(), - ); - } - - Ok(conn.build()) - } - - pub fn acceptor(&self) -> Result { - let _ctx = ssl::SslContext::builder(ssl::SslMethod::tls_server())?; - // mozilla_intermediate_v5 is the only variant that enables TLSv1.3, so we use that. - let mut conn = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; - self.setup_ctx(&mut conn)?; - - conn.set_verify_callback(ssl::SslVerifyMode::NONE, Verifier::None.callback()); - Ok(conn.build()) - } - - pub fn connector(&self, dest_id: Vec) -> Result { - let mut conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; - self.setup_ctx(&mut conn)?; - - // client verifies SAN - conn.set_verify_callback(Self::verify_mode(), Verifier::San(dest_id).callback()); - - Ok(conn.build()) - } - - fn setup_ctx(&self, conn: &mut SslContextBuilder) -> Result<(), Error> { - // general TLS options - conn.set_alpn_protos(Alpn::H2.encode())?; - conn.set_min_proto_version(Some(ssl::SslVersion::TLS1_3))?; - conn.set_max_proto_version(Some(ssl::SslVersion::TLS1_3))?; - - // key and certs - conn.set_private_key(&self.key)?; - conn.set_certificate(&self.cert.x509)?; - for (i, chain_cert) in self.chain.iter().enumerate() { - // Only include intermediate certs in the chain. - // The last cert is the root cert which should already exist on the peer. - if i < (self.chain.len() - 1) { - // This is an intermediate cert that should be added to the cert chain - conn.add_extra_chain_cert(chain_cert.x509.clone())?; - } - conn.cert_store_mut().add_cert(chain_cert.x509.clone())?; - } - conn.check_private_key()?; - - // by default, allow boringssl to do standard validation - conn.set_verify_callback(Self::verify_mode(), Verifier::None.callback()); - - Ok(()) - } -} - -enum Verifier { - // Does not verify an individual identity. - None, - - // Allows a list of accepted identities, making sure at least one of the presented certs matches one in the list - San(Vec), - - // Allows all identities that share the same trust domain - SanTrustDomain(Identity), -} - -impl Verifier { - fn base_verifier(verified: bool, ctx: &mut X509StoreContextRef) -> Result<(), TlsError> { - if !verified { - ctx.verify_result()?; - }; - Ok(()) - } - - fn verifiy_san(identities: &[Identity], ctx: &mut X509StoreContextRef) -> Result<(), TlsError> { - // internally, openssl tends to .expect the results of these methods. - // TODO bubble up better error message - let ssl_idx = X509StoreContext::ssl_idx().map_err(Error::SslError)?; - let cert = ctx - .ex_data(ssl_idx) - .ok_or(TlsError::ExDataError)? - .peer_certificate() - .ok_or(TlsError::PeerCertError)?; - - cert.verify_san(identities) - } - - fn verifiy_san_trust_domain( - identity: &Identity, - ctx: &mut X509StoreContextRef, - ) -> Result<(), TlsError> { - // internally, openssl tends to .expect the results of these methods. - // TODO bubble up better error message - let ssl_idx = X509StoreContext::ssl_idx().map_err(Error::SslError)?; - let cert = ctx - .ex_data(ssl_idx) - .ok_or(TlsError::ExDataError)? - .peer_certificate() - .ok_or(TlsError::PeerCertError)?; - - cert.verify_san_trust_domain(identity) - } - - fn verify(&self, verified: bool, ctx: &mut X509StoreContextRef) -> Result<(), TlsError> { - Self::base_verifier(verified, ctx)?; - match self { - Self::San(identities) => Verifier::verifiy_san(identities, ctx)?, - Self::SanTrustDomain(identity) => Verifier::verifiy_san_trust_domain(identity, ctx)?, - Self::None => (), - }; - Ok(()) - } - - fn callback(self) -> impl Fn(bool, &mut X509StoreContextRef) -> bool { - move |verified, ctx| match self.verify(verified, ctx) { - Ok(_) => true, - Err(e) => { - // TODO metrics/counters; info would be too noisy - info!("failed verifying TLS: {e}"); - false - } - } - } -} - -pub trait SanChecker { - fn verify_san(&self, identities: &[Identity]) -> Result<(), TlsError>; - fn verify_san_trust_domain(&self, identity: &Identity) -> Result<(), TlsError>; -} - -impl SanChecker for Certs { - fn verify_san(&self, identities: &[Identity]) -> Result<(), TlsError> { - self.cert.x509.verify_san(identities) - } - - fn verify_san_trust_domain(&self, identity: &Identity) -> Result<(), TlsError> { - self.cert.x509.verify_san_trust_domain(identity) - } -} - -pub fn extract_sans(cert: &x509::X509) -> Vec { - cert.subject_alt_names() - .iter() - .flat_map(|sans| sans.iter()) - .filter_map(|s| s.uri()) - .map(Identity::from_str) - .collect::, _>>() - .unwrap_or_default() -} - -impl SanChecker for x509::X509 { - fn verify_san(&self, identities: &[Identity]) -> Result<(), TlsError> { - let sans = extract_sans(self); - for ident in identities.iter() { - if let Some(_i) = sans.iter().find(|id| id == &ident) { - return Ok(()); - } - } - Err(TlsError::SanError(identities.to_vec(), sans)) - } - - fn verify_san_trust_domain(&self, identity: &Identity) -> Result<(), TlsError> { - let source_trust_domain = match identity { - Identity::Spiffe { trust_domain, .. } => trust_domain, - }; - let sans = extract_sans(self); - sans.iter() - .find(|id| match id { - Identity::Spiffe { trust_domain, .. } => trust_domain == source_trust_domain, - }) - .ok_or_else(|| { - TlsError::SanTrustDomainError(source_trust_domain.to_string(), sans.clone()) - }) - .map(|_| ()) - } -} - -enum Alpn { - H2, -} - -impl Alpn { - fn encode(&self) -> &[u8] { - match self { - Alpn::H2 => b"\x02h2", - } - } -} - -#[async_trait::async_trait] -pub trait ServerCertProvider: Send + Sync { - async fn fetch_cert(&mut self, fd: &TcpStream) -> Result; -} - -#[derive(Clone, Debug)] -pub struct ControlPlaneCertProvider(pub Certs); - -#[async_trait::async_trait] -impl ServerCertProvider for ControlPlaneCertProvider { - async fn fetch_cert(&mut self, _: &TcpStream) -> Result { - let acc = self.0.acceptor()?; - Ok(acc) - } -} - -#[derive(Clone)] -pub struct BoringTlsAcceptor { - /// Acceptor is a function that determines the TLS context to use. As input, the FD of the client - /// connection is provided. - pub acceptor: F, -} - -#[derive(thiserror::Error, Debug)] -pub enum TlsError { - #[error("tls handshake error: {0:?}")] - Handshake(#[from] tokio_boring::HandshakeError), - #[error("tls verification error: {0}")] - Verification(#[from] X509VerifyError), - #[error("certificate lookup error: {0} is not a known destination")] - CertificateLookup(NetworkAddress), - #[error("signing error: {0}")] - SigningError(#[from] identity::Error), - #[error("san verification error: remote did not present the expected SAN ({0:?}), got {1:?}")] - SanError(Vec, Vec), - #[error( - "san verification error: remote did not present the expected trustdomain ({0}), got {1:?}" - )] - SanTrustDomainError(String, Vec), - #[error("failed getting ex data")] - ExDataError, - #[error("failed getting peer cert")] - PeerCertError, - #[error("ssl error: {0}")] - SslError(#[from] Error), -} - -impl tls_listener::AsyncTls for BoringTlsAcceptor -where - F: ServerCertProvider + Clone + 'static, -{ - type Stream = tokio_boring::SslStream; - type Error = TlsError; - type AcceptFuture = Pin> + Send>>; - - fn accept(&self, conn: TcpStream) -> Self::AcceptFuture { - let mut acceptor = self.acceptor.clone(); - Box::pin(async move { - let tls = acceptor.fetch_cert(&conn).await?; - tokio_boring::accept(&tls, conn) - .await - .map_err(TlsError::Handshake) - }) - } -} - -const TEST_CERT: &[u8] = include_bytes!("cert-chain.pem"); -#[cfg(test)] -const TEST_WORKLOAD_CERT: &[u8] = include_bytes!("cert.pem"); -const TEST_PKEY: &[u8] = include_bytes!("key.pem"); -const TEST_ROOT: &[u8] = include_bytes!("root-cert.pem"); -const TEST_ROOT_KEY: &[u8] = include_bytes!("ca-key.pem"); - -/// TestIdentity is an identity used for testing. This extends the Identity with test-only types -#[derive(Debug)] -pub enum TestIdentity { - Identity(Identity), - Ip(IpAddr), -} - -impl From for TestIdentity { - fn from(i: Identity) -> Self { - Self::Identity(i) - } -} - -impl From for TestIdentity { - fn from(i: IpAddr) -> Self { - Self::Ip(i) - } -} - -// -// impl Display for TestIdentity { -// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { -// match self { -// TestIdentity::Identity(i) => std::fmt::Display::fmt(&i, f), -// TestIdentity::Ip(i) => std::fmt::Display::fmt(&i, f), -// } -// } -// } - -// TODO: Move to the mock submodule. - -// TODO: Move towards code that doesn't rely on SystemTime::now() for easier time control with -// tokio. Ideally we'll be able to also get rid of the sub-second timestamps on certificates -// (since right now they are there only for testing). -fn generate_test_certs_at( - id: &TestIdentity, - not_before: SystemTime, - not_after: SystemTime, - rng: Option<&mut dyn rand::RngCore>, -) -> Certs { - let key = pkey::PKey::private_key_from_pem(TEST_PKEY).unwrap(); - let (ca_cert, ca_key) = test_ca().unwrap(); - let mut builder = x509::X509::builder().unwrap(); - let not_before_asn = system_time_to_asn1_time(not_before).unwrap(); - builder.set_not_before(¬_before_asn).unwrap(); - builder - .set_not_after(&system_time_to_asn1_time(not_after).unwrap()) - .unwrap(); - - builder.set_pubkey(&key).unwrap(); - builder.set_version(2).unwrap(); - let serial_number = { - let mut data = [0u8; 20]; - match rng { - None => rand::thread_rng().fill_bytes(&mut data), - Some(rng) => rng.fill_bytes(&mut data), - } - // Clear the most significant bit to make the resulting bignum effectively 159 bit long. - data[0] &= 0x7f; - let serial = BigNum::from_slice(&data).unwrap(); - serial.to_asn1_integer().unwrap() - }; - builder.set_serial_number(&serial_number).unwrap(); - - let mut names = boring::x509::X509NameBuilder::new().unwrap(); - names.append_entry_by_text("O", "cluster.local").unwrap(); - let names = names.build(); - builder.set_issuer_name(&names).unwrap(); - - let basic_constraints = BasicConstraints::new().critical().build().unwrap(); - let key_usage = KeyUsage::new() - .critical() - .digital_signature() - .key_encipherment() - .build() - .unwrap(); - let ext_key_usage = ExtendedKeyUsage::new() - .server_auth() - .client_auth() - .build() - .unwrap(); - let authority_key_identifier = AuthorityKeyIdentifier::new() - .keyid(false) - .issuer(false) - .build(&builder.x509v3_context(Some(&ca_cert), None)) - .unwrap(); - let mut san = SubjectAlternativeName::new(); - let subject_alternative_name = match id { - TestIdentity::Identity(id) => san.uri(&id.to_string()), - TestIdentity::Ip(ip) => san.ip(&ip.to_string()), - }; - let subject_alternative_name = subject_alternative_name - .critical() - .build(&builder.x509v3_context(Some(&ca_cert), None)) - .unwrap(); - builder.append_extension(key_usage).unwrap(); - builder.append_extension(ext_key_usage).unwrap(); - builder.append_extension(basic_constraints).unwrap(); - builder.append_extension(authority_key_identifier).unwrap(); - builder.append_extension(subject_alternative_name).unwrap(); - - builder.sign(&ca_key, MessageDigest::sha256()).unwrap(); - - let mut cert = ZtunnelCert::new(builder.build()); - // For sub-second granularity - cert.not_before = not_before; - cert.not_after = not_after; - Certs { - cert, - key, - chain: vec![ZtunnelCert::new(ca_cert)], - } -} - -pub fn generate_test_certs( - id: &TestIdentity, - duration_until_valid: Duration, - duration_until_expiry: Duration, -) -> Certs { - let not_before = SystemTime::now() + duration_until_valid; - generate_test_certs_at(id, not_before, not_before + duration_until_expiry, None) -} - -fn test_ca() -> Result<(x509::X509, PKey), Error> { - let cert = x509::X509::from_pem(TEST_ROOT)?; - let key = pkey::PKey::private_key_from_pem(TEST_ROOT_KEY)?; - Ok((cert, key)) -} - -pub fn test_certs() -> Certs { - let cert = ZtunnelCert::new(x509::X509::from_pem(TEST_CERT).unwrap()); - let key = pkey::PKey::private_key_from_pem(TEST_PKEY).unwrap(); - let chain = vec![cert.clone()]; - Certs { cert, key, chain } -} - -pub mod mock { - use rand::{rngs::SmallRng, SeedableRng}; - use std::time::SystemTime; - - use super::{generate_test_certs_at, Certs, TestIdentity}; - - /// Allows generating test certificates in a deterministic manner. - pub struct CertGenerator { - rng: SmallRng, - } - - impl CertGenerator { - /// Returns a new test certificate generator. The seed parameter sets the seed for any - /// randomized operations. Multiple CertGenerator instances created with the same seed will - /// return the same successive certificates, if same arguments to new_certs are given. - pub fn new(seed: u64) -> Self { - Self { - rng: SmallRng::seed_from_u64(seed), - } - } - - pub fn new_certs( - &mut self, - id: &TestIdentity, - not_before: SystemTime, - not_after: SystemTime, - ) -> Certs { - generate_test_certs_at(id, not_before, not_after, Some(&mut self.rng)) - } - } - - impl Default for CertGenerator { - fn default() -> Self { - // Use arbitrary seed. - Self::new(427) - } - } -} - -#[cfg(test)] -pub mod tests { - use std::time::Duration; - - use crate::identity::Identity; - use crate::tls::TestIdentity; - - use super::*; - - #[test] - #[cfg(feature = "fips")] - fn is_fips_enabled() { - assert!(boring::fips::enabled()); - } - - #[test] - #[cfg(not(feature = "fips"))] - fn is_fips_disabled() { - assert!(!boring::fips::enabled()); - } - - fn get_subject_entries(x: &x509::X509) -> Vec<(String, String)> { - x.subject_name() - .entries() - .map(|x| { - ( - x.object().to_string(), - (x.data().as_utf8().unwrap().as_ref() as &str).to_string(), - ) - }) - .collect() - } - - #[test] - fn test_cert_from() { - // note that TEST_CERT contains more than one cert - this is how istiod serves it when - // intermediary cert is used.. - let certs = cert_from(TEST_PKEY, TEST_WORKLOAD_CERT, vec![TEST_CERT, TEST_ROOT]); - // 3 certs that should be here are the istiod cert, intermediary cert and the root cert. - - // validate the cert: - let entries = get_subject_entries(certs.x509()); - assert_eq!( - entries, - vec![( - "commonName".to_string(), - "default.default.svc.cluster.local".to_string() - )] - ); - - // validate the cert chain: - assert_eq!(certs.iter_chain().count(), 3); - let mut iter = certs.iter_chain(); - let entries = get_subject_entries(iter.next().unwrap()); - assert_eq!( - entries, - vec![( - "organizationName".to_string(), - "istiod.cluster.local".to_string() - )] - ); - - let entries = get_subject_entries(iter.next().unwrap()); - assert_eq!( - entries, - vec![( - "organizationName".to_string(), - "intermediary.cluster.local".to_string() - )] - ); - - let entries = get_subject_entries(iter.next().unwrap()); - assert_eq!( - entries, - vec![("organizationName".to_string(), "cluster.local".to_string())] - ); - } - - #[test] - fn cert_expiration() { - let expiry_seconds = 1000; - let id: TestIdentity = Identity::default().into(); - let zero_dur = Duration::from_secs(0); - let certs_not_expired = generate_test_certs( - &id, - Duration::from_secs(0), - Duration::from_secs(expiry_seconds), - ); - assert!(!certs_not_expired.is_expired()); - let seconds_until_refresh = certs_not_expired.get_duration_until_refresh().as_secs(); - // Give a couple second window to avoid flakiness in the test. - assert!( - seconds_until_refresh <= expiry_seconds / 2 - && seconds_until_refresh >= expiry_seconds / 2 - 1 - ); - - let certs_expired = generate_test_certs(&id, zero_dur, zero_dur); - assert!(certs_expired.is_expired()); - assert_eq!(certs_expired.get_duration_until_refresh(), zero_dur); - - let future_certs = generate_test_certs( - &id, - Duration::from_secs(1000), - Duration::from_secs(expiry_seconds), - ); - assert!(!future_certs.is_expired()); - assert_eq!(future_certs.get_duration_until_refresh(), zero_dur); - } -} diff --git a/src/tls/certificate.rs b/src/tls/certificate.rs new file mode 100644 index 000000000..fd29c39f2 --- /dev/null +++ b/src/tls/certificate.rs @@ -0,0 +1,323 @@ +use crate::identity::Identity; +use crate::tls::{Error, IdentityVerifier, OutboundConnector}; +use base64::engine::general_purpose::STANDARD; +use bytes::Bytes; +use itertools::Itertools; + +use rustls::client::Resumption; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; + +use rustls::server::WebPkiClientVerifier; +use rustls::{server, ClientConfig, RootCertStore, ServerConfig}; +use rustls_pemfile::Item; +use std::io::Cursor; +use std::str::FromStr; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tracing::warn; + +use crate::tls; +use x509_parser::certificate::X509Certificate; + +#[derive(Clone, Debug)] +pub struct Certificate { + pub(in crate::tls) expiry: Expiration, + der: CertificateDer<'static>, +} + +#[derive(Clone, Debug)] +pub struct Expiration { + pub not_before: SystemTime, + pub not_after: SystemTime, +} + +#[derive(Debug)] +pub struct WorkloadCertificate { + /// cert is the leaf certificate + pub cert: Certificate, + /// chain is the entire trust chain, excluding the leaf + pub chain: Vec, + pub private_key: PrivateKeyDer<'static>, + + /// precomputed roots + roots: Arc, +} + +pub fn identity_from_connection(conn: &server::ServerConnection) -> Option { + use x509_parser::prelude::*; + conn.peer_certificates() + .and_then(|certs| certs.first()) + .and_then(|cert| match X509Certificate::from_der(cert) { + Ok((_, a)) => Some(a), + Err(e) => { + warn!("invalid certificate: {e}"); + None + } + }) + .and_then(|cert| match identities(cert) { + Ok(ids) => ids.into_iter().next(), + Err(e) => { + warn!("failed to extract identity: {}", e); + None + } + }) +} + +pub fn identities(cert: X509Certificate) -> Result, Error> { + use x509_parser::prelude::*; + let names = cert + .subject_alternative_name()? + .map(|x| &x.value.general_names); + + if let Some(names) = names { + return Ok(names + .iter() + .filter_map(|n| { + let id = match n { + GeneralName::URI(uri) => Identity::from_str(uri), + _ => return None, + }; + + match id { + Ok(id) => Some(id), + Err(err) => { + warn!("SAN {n} could not be parsed: {err}"); + None + } + } + }) + .collect()); + } + Ok(Vec::default()) +} + +impl Certificate { + // TOOD: I would love to parse this once, but ran into lifetime issues. + fn parsed(&self) -> X509Certificate { + x509_parser::parse_x509_certificate(&self.der).unwrap().1 + } + + pub fn as_pem(&self) -> String { + der_to_pem(&self.der, CERTIFICATE) + } + + pub fn identity(&self) -> Option { + self.parsed() + .subject_alternative_name() + .ok() + .flatten() + .and_then(|ext| { + ext.value + .general_names + .iter() + .filter_map(|n| match n { + x509_parser::extensions::GeneralName::URI(uri) => Some(uri), + _ => None, + }) + .next() + }) + .and_then(|san| Identity::from_str(san).ok()) + } + + #[cfg(test)] + pub fn names(&self) -> Vec { + let reg = oid_registry::OidRegistry::default().with_x509(); + + self.parsed() + .subject + .iter() + .flat_map(|dn| { + dn.iter().map(|x| { + reg.get(x.attr_type()).unwrap().sn().to_string() + "/" + x.as_str().unwrap() + }) + }) + .chain( + self.parsed() + .subject_alternative_name() + .ok() + .flatten() + .iter() + .flat_map(|ext| ext.value.general_names.iter().map(|n| n.to_string())), + ) + .collect() + } + + pub fn serial(&self) -> String { + self.parsed().serial.to_string() + } + + pub fn expiration(&self) -> Expiration { + self.expiry.clone() + } +} + +fn expiration(cert: X509Certificate) -> Expiration { + Expiration { + not_before: UNIX_EPOCH + + Duration::from_secs( + cert.validity + .not_before + .timestamp() + .try_into() + .unwrap_or_default(), + ), + not_after: UNIX_EPOCH + + Duration::from_secs( + cert.validity + .not_after + .timestamp() + .try_into() + .unwrap_or_default(), + ), + } +} + +fn parse_cert(mut cert: Vec) -> Result { + let mut reader = std::io::BufReader::new(Cursor::new(&mut cert)); + let parsed = rustls_pemfile::read_one(&mut reader) + .map_err(|e| Error::CertificateParseError(e.to_string()))? + .ok_or_else(|| Error::CertificateParseError("no certificate".to_string()))?; + let Item::X509Certificate(der) = parsed else { + return Err(Error::CertificateParseError("no certificate".to_string())); + }; + + let (_, cert) = x509_parser::parse_x509_certificate(&der)?; + Ok(Certificate { + der: der.clone(), + expiry: expiration(cert), + }) +} + +fn parse_key(mut key: &[u8]) -> Result, Error> { + let mut reader = std::io::BufReader::new(Cursor::new(&mut key)); + let parsed = rustls_pemfile::read_one(&mut reader) + .map_err(|e| Error::CertificateParseError(e.to_string()))? + .ok_or_else(|| Error::CertificateParseError("no key".to_string()))?; + match parsed { + Item::Pkcs8Key(c) => Ok(PrivateKeyDer::Pkcs8(c)), + _ => Err(Error::CertificateParseError("no key".to_string())), + } +} + +impl WorkloadCertificate { + pub fn new(key: &[u8], cert: &[u8], chain: Vec<&[u8]>) -> Result { + let cert = parse_cert(cert.to_vec())?; + let chain = chain + .into_iter() + .map(|x| x.to_vec()) + .map(parse_cert) + .collect::, _>>()?; + let key: PrivateKeyDer = parse_key(key)?; + + let mut roots = RootCertStore::empty(); + roots.add_parsable_certificates(chain.iter().last().map(|c| c.der.clone())); + Ok(WorkloadCertificate { + cert, + chain, + private_key: key, + roots: Arc::new(roots), + }) + } + + // TODO: can we precompute some or all of this? + + pub(in crate::tls) fn cert_and_intermediates(&self) -> Vec> { + std::iter::once(self.cert.der.clone()) + .chain( + self.chain[..self.chain.len() - 1] + .iter() + .map(|x| x.der.clone()), + ) + .collect() + } + + pub fn server_config(&self) -> Result { + let td = self.cert.identity().map(|i| match i { + Identity::Spiffe { trust_domain, .. } => trust_domain, + }); + let raw_client_cert_verifier = WebPkiClientVerifier::builder_with_provider( + self.roots.clone(), + crate::tls::lib::provider(), + ) + .build()?; + + let client_cert_verifier = + crate::tls::workload::TrustDomainVerifier::new(raw_client_cert_verifier, td); + let mut sc = ServerConfig::builder_with_provider(crate::tls::lib::provider()) + .with_protocol_versions(tls::TLS_VERSIONS) + .expect("server config must be valid") + .with_client_cert_verifier(client_cert_verifier) + .with_single_cert(self.cert_and_intermediates(), self.private_key.clone_key())?; + sc.alpn_protocols = vec![b"h2".into()]; + Ok(sc) + } + + pub fn outbound_connector(&self, identity: Vec) -> Result { + let roots = self.roots.clone(); + let verifier = IdentityVerifier { roots, identity }; + let mut cc = ClientConfig::builder_with_provider(crate::tls::lib::provider()) + .with_protocol_versions(tls::TLS_VERSIONS) + .expect("client config must be valid") + .dangerous() // Customer verifier is requires "dangerous" opt-in + .with_custom_certificate_verifier(Arc::new(verifier)) + .with_client_auth_cert(self.cert_and_intermediates(), self.private_key.clone_key())?; + cc.alpn_protocols = vec![b"h2".into()]; + cc.resumption = Resumption::disabled(); + cc.enable_sni = false; + Ok(OutboundConnector { + client_config: Arc::new(cc), + }) + } + + pub fn dump_chain(&self) -> Bytes { + self.chain.iter().map(|c| c.as_pem()).join("\n").into() + } + + pub fn is_expired(&self) -> bool { + SystemTime::now() > self.cert.expiry.not_after + } + + pub fn refresh_at(&self) -> SystemTime { + let expiry = &self.cert.expiry; + match expiry.not_after.duration_since(expiry.not_before) { + Ok(valid_for) => expiry.not_before + valid_for / 2, + Err(_) => expiry.not_after, + } + } + + pub fn get_duration_until_refresh(&self) -> Duration { + let expiry = &self.cert.expiry; + let halflife = expiry + .not_after + .duration_since(expiry.not_before) + .unwrap_or_else(|_| std::time::Duration::from_secs(0)) + / 2; + // If now() is earlier than not_before, we need to refresh ASAP, so return 0. + let elapsed = SystemTime::now() + .duration_since(expiry.not_before) + .unwrap_or(halflife); + halflife + .checked_sub(elapsed) + .unwrap_or_else(|| Duration::from_secs(0)) + } +} + +const CERTIFICATE: &str = "CERTIFICATE"; + +/// Converts DER encoded data to PEM. +fn der_to_pem(der: &[u8], label: &str) -> String { + use base64::Engine; + let mut ans = String::from("-----BEGIN "); + ans.push_str(label); + ans.push_str("-----\n"); + let b64 = STANDARD.encode(der); + let line_length = 60; + for chunk in b64.chars().collect::>().chunks(line_length) { + ans.extend(chunk); + ans.push('\n'); + } + ans.push_str("-----END "); + ans.push_str(label); + ans.push_str("-----\n"); + ans +} diff --git a/src/tls/control.rs b/src/tls/control.rs new file mode 100644 index 000000000..5fd3edf1d --- /dev/null +++ b/src/tls/control.rs @@ -0,0 +1,181 @@ +use crate::config::RootCert; +use crate::tls::lib::provider; +use crate::tls::{ClientCertProvider, Error, WorkloadCertificate}; +use bytes::Bytes; +use http_body_1::{Body, Frame}; +use hyper::body::Incoming; +use hyper::Uri; +use hyper_rustls::HttpsConnector; +use hyper_util::client::legacy::connect::HttpConnector; +use rustls::ClientConfig; +use std::future::Future; +use std::io::Cursor; +use std::pin::Pin; + +use std::task::{Context, Poll}; +use std::time::Duration; + +use tonic::body::BoxBody; +use tower_hyper_http_body_compat::{ + http02_request_to_http1, http1_response_to_http02, HttpBody04ToHttpBody1, HttpBody1ToHttpBody04, +}; + +async fn root_to_store(root_cert: &RootCert) -> Result { + let mut roots = rustls::RootCertStore::empty(); + match root_cert { + RootCert::File(f) => { + let certfile = tokio::fs::read(f) + .await + .map_err(|e| Error::InvalidRootCert(e.to_string()))?; + let mut reader = std::io::BufReader::new(Cursor::new(certfile)); + let certs = rustls_pemfile::certs(&mut reader) + .collect::, _>>() + .map_err(|e| Error::InvalidRootCert(e.to_string()))?; + roots.add_parsable_certificates(certs); + } + RootCert::Static(b) => { + let mut reader = std::io::BufReader::new(Cursor::new(b)); + let certs = rustls_pemfile::certs(&mut reader) + .collect::, _>>() + .map_err(|e| Error::InvalidRootCert(e.to_string()))?; + roots.add_parsable_certificates(certs); + } + RootCert::Default => { + let certs = rustls_native_certs::load_native_certs() + .map_err(|e| Error::InvalidRootCert(e.to_string()))?; + roots.add_parsable_certificates(certs); + } + }; + Ok(roots) +} + +#[derive(Debug)] +pub enum ControlPlaneAuthentication { + RootCert(RootCert), + ClientBundle(WorkloadCertificate), +} + +#[async_trait::async_trait] +impl ClientCertProvider for ControlPlaneAuthentication { + async fn fetch_cert(&self) -> Result { + match self { + ControlPlaneAuthentication::RootCert(root_cert) => { + control_plane_client_config(root_cert).await + } + ControlPlaneAuthentication::ClientBundle(_bundle) => { + // TODO: implement this. Its is not currently used so no need. + unimplemented!(); + } + } + } +} + +async fn control_plane_client_config(root_cert: &RootCert) -> Result { + let roots = root_to_store(root_cert).await?; + Ok(ClientConfig::builder_with_provider(provider()) + .with_protocol_versions(crate::tls::TLS_VERSIONS)? + .with_root_certificates(roots) + .with_no_client_auth()) +} + +#[derive(Clone, Debug)] +pub struct TlsGrpcChannel { + uri: Uri, + client: hyper_util::client::legacy::Client, BoxBody1>, +} + +/// grpc_connector provides a client TLS channel for gRPC requests. +pub async fn grpc_tls_connector(uri: String, root_cert: RootCert) -> Result { + grpc_connector(uri, control_plane_client_config(&root_cert).await?) +} + +/// grpc_connector provides a client TLS channel for gRPC requests. +pub fn grpc_connector(uri: String, cc: ClientConfig) -> Result { + let uri = Uri::try_from(uri)?; + let _is_localhost_call = uri.host() == Some("localhost"); + let mut http: HttpConnector = HttpConnector::new(); + // Set keepalives to match istio's Envoy bootstrap configuration: + // https://github.com/istio/istio/blob/a29d5c9c27d80bff31f218936f5a96759d8911c8/tools/packaging/common/envoy_bootstrap.json#L322C14-L322C28 + // + // keepalive_interval and keepalive_retries match the linux default per Envoy docs: + // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-tcpkeepalive + http.set_keepalive(Some(Duration::from_secs(300))); + http.set_keepalive_interval(Some(Duration::from_secs(75))); + http.set_keepalive_retries(Some(9)); + http.set_connect_timeout(Some(Duration::from_secs(5))); + http.enforce_http(false); + let https: HttpsConnector = hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(cc) + .https_only() + .enable_http2() + .wrap_connector(http); + + // Configure hyper's client to be h2 only and build with the + // correct https connector. + let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) + .http2_only(true) + .http2_keep_alive_interval(Duration::from_secs(30)) + .http2_keep_alive_timeout(Duration::from_secs(10)) + .timer(crate::hyper_util::TokioTimer) + .build(https); + + Ok(TlsGrpcChannel { uri, client }) +} + +// Everything here is to hack hyper 1.0 onto tonic. +// TODO(https://github.com/hyperium/tonic/issues/1307) remove all of this and use tonic 'transport' + +type BoxBody1 = HttpBody04ToHttpBody1; + +#[derive(Default)] +pub enum DefaultIncoming { + Some(Incoming), + #[default] + Empty, +} + +impl Body for DefaultIncoming { + type Data = Bytes; + type Error = hyper::Error; + + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + match self.get_mut() { + DefaultIncoming::Some(ref mut i) => Pin::new(i).poll_frame(cx), + DefaultIncoming::Empty => Pin::new(&mut http_body_util::Empty::::new()) + .poll_frame(cx) + .map_err(|_| unreachable!()), + } + } +} + +impl tower::Service> for TlsGrpcChannel { + type Response = http_02::Response>; + type Error = hyper_util::client::legacy::Error; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Ok(()).into() + } + + fn call(&mut self, req: http_02::Request) -> Self::Future { + let mut req = http02_request_to_http1(req.map(HttpBody04ToHttpBody1::new)); + let uri = Uri::builder() + .scheme(self.uri.scheme().unwrap().to_owned()) + .authority(self.uri.authority().unwrap().to_owned()) + .path_and_query(req.uri().path_and_query().unwrap().to_owned()) + .build() + .unwrap(); + *req.uri_mut() = uri; + let future = self.client.request(req); + Box::pin(async move { + let res = future.await?; + Ok(http1_response_to_http02( + res.map(DefaultIncoming::Some) + .map(HttpBody1ToHttpBody04::new), + )) + }) + } +} diff --git a/src/tls/csr.rs b/src/tls/csr.rs new file mode 100644 index 000000000..c8386b70c --- /dev/null +++ b/src/tls/csr.rs @@ -0,0 +1,70 @@ +use crate::tls::Error; + +pub struct CertSign { + pub csr: String, + pub private_key: Vec, +} + +pub struct CsrOptions { + pub san: String, +} + +impl CsrOptions { + #[cfg(feature = "tls-boring")] + pub fn generate(&self) -> Result { + use boring::ec::{EcGroup, EcKey}; + use boring::hash::MessageDigest; + use boring::nid::Nid; + use boring::pkey::PKey; + use boring::stack::Stack; + use boring::x509::extension::SubjectAlternativeName; + use boring::x509::{self}; + // TODO: https://github.com/rustls/rcgen/issues/228 can we always use rcgen? + + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let ec_key = EcKey::generate(&group)?; + let pkey = PKey::from_ec_key(ec_key)?; + + let mut csr = x509::X509ReqBuilder::new()?; + csr.set_pubkey(&pkey)?; + let mut extensions = Stack::new()?; + let subject_alternative_name = SubjectAlternativeName::new() + .uri(&self.san) + .critical() + .build(&csr.x509v3_context(None))?; + + extensions.push(subject_alternative_name)?; + csr.add_extensions(&extensions)?; + csr.sign(&pkey, MessageDigest::sha256())?; + + let csr = csr.build(); + let pkey_pem = pkey.private_key_to_pem_pkcs8()?; + let csr_pem = csr.to_pem()?; + let csr_pem = std::str::from_utf8(&csr_pem) + .expect("CSR is valid string")? + .to_string(); + Ok(CertSign { + csr: csr_pem, + private_key: pkey_pem, + }) + } + + #[cfg(feature = "tls-ring")] + pub fn generate(&self) -> Result { + use rcgen::{Certificate, CertificateParams, SanType}; + let kp = rcgen::KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).expect("TODO"); + let private_key = kp.serialize_pem(); + let mut params = CertificateParams::default(); + params.subject_alt_names = vec![SanType::URI(self.san.clone())]; + params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + params.key_identifier_method = rcgen::KeyIdMethod::Sha256; + params.key_pair = Some(kp); + let cert = Certificate::from_params(params).expect("TODO"); + let csr = cert.serialize_request_pem().expect("TODO"); + + Ok(CertSign { + csr, + private_key: private_key.into(), + }) + } +} diff --git a/src/tls/gen-certs.sh b/src/tls/gen-certs.sh index 6f76be02c..fc7d8cd3d 100755 --- a/src/tls/gen-certs.sh +++ b/src/tls/gen-certs.sh @@ -16,6 +16,8 @@ openssl req -x509 -new -nodes -CA "intermediary-cert.pem" -CAkey "intermediary-k if [ ! -f key.pem ]; then # Only gen if doesn't exist. As some tests depend on the existing content of the key. openssl ecparam -name prime256v1 -genkey -noout -out key.pem + # Convert to more compatible format + openssl pkcs8 -topk8 -in key.pem -out key.pem -nocrypt fi cat > "client.conf" < Result; +} + +#[async_trait::async_trait] +pub trait ServerCertProvider: Send + Sync + Clone { + async fn fetch_cert(&mut self, fd: &TcpStream) -> Result, TlsError>; +} + +pub(super) static TLS_VERSIONS: &[&rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13]; + +// Ztunnel use `rustls` with pluggable crypto modules. +// All crypto MUST be done via the below providers. +// +// One exception is CSR generation which doesn't currently have a plugin mechanism (https://github.com/rustls/rcgen/issues/228); +// In that case, and any future ones, it is critical to guard the code with appropriate `cfg` guards. + +#[cfg(feature = "tls-boring")] +pub(super) fn provider() -> Arc { + // Due to 'fips-only' feature on the boring provider, this will use only AES_256_GCM_SHA384 + // and AES_128_GCM_SHA256 + // In later code we select to only use TLS 1.3 + Arc::new(boring_rustls_provider::provider()) +} + +#[cfg(feature = "tls-ring")] +pub(super) fn provider() -> Arc { + Arc::new(CryptoProvider { + // Limit to only the subset of ciphers that are FIPS compatible + cipher_suites: vec![ + rustls::crypto::ring::cipher_suite::TLS13_AES_256_GCM_SHA384, + rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256, + ], + ..rustls::crypto::ring::default_provider() + }) +} + +#[derive(thiserror::Error, Debug)] +pub enum TlsError { + #[error("tls handshake error: {0:?}")] + Handshake(std::io::Error), + #[error("certificate lookup error: {0} is not a known destination")] + CertificateLookup(NetworkAddress), + #[error("signing error: {0}")] + SigningError(#[from] identity::Error), + #[error("san verification error: remote did not present the expected SAN ({0:?}), got {1:?}")] + SanError(Vec, Vec), + #[error( + "san verification error: remote did not present the expected trustdomain ({0}), got {1:?}" + )] + SanTrustDomainError(String, Vec), + #[error("failed getting ex data")] + ExDataError, + #[error("failed getting peer cert")] + PeerCertError, + #[error("ssl error: {0}")] + SslError(#[from] Error), +} + +#[cfg(test)] +pub mod tests { + use std::time::Duration; + + use crate::identity::Identity; + use crate::tls::WorkloadCertificate; + + use crate::tls::mock::*; + + #[test] + #[cfg(feature = "tls-boring")] + fn is_fips_enabled() { + assert!(boring::fips::enabled()); + } + + #[test] + fn test_workload_cert() { + // note that TEST_CERT contains more than one cert - this is how istiod serves it when + // intermediary cert is used.. + let roots: Vec = std::str::from_utf8(TEST_CERT) + .unwrap() + .to_string() + .split("-----END CERTIFICATE-----") + .filter(|x| !x.trim().is_empty()) + .map(|x| format!("{}{}", x, "-----END CERTIFICATE-----")) + .collect(); + let roots: Vec<&[u8]> = roots.iter().map(|x| x.as_bytes()).collect(); + let certs = WorkloadCertificate::new(TEST_PKEY, TEST_WORKLOAD_CERT, roots).unwrap(); + + // 3 certs that should be here are the istiod cert, intermediary cert and the root cert. + assert_eq!(certs.chain.len(), 3); + assert_eq!( + certs.cert.names(), + vec![ + "commonName/default.default.svc.cluster.local", + "URI(spiffe://cluster.local/ns/default/sa/default)", + ] + ); + + assert_eq!( + certs.chain[0].names(), + vec!["organizationName/istiod.cluster.local".to_string()] + ); + + assert_eq!( + certs.chain[1].names(), + vec!["organizationName/intermediary.cluster.local".to_string(),] + ); + } + + #[test] + fn cert_expiration() { + let expiry_seconds = 1000; + let id: TestIdentity = Identity::default().into(); + let zero_dur = Duration::from_secs(0); + let certs_not_expired = generate_test_certs( + &id, + Duration::from_secs(0), + Duration::from_secs(expiry_seconds), + ); + assert!(!certs_not_expired.is_expired()); + let seconds_until_refresh = certs_not_expired.get_duration_until_refresh().as_secs(); + // Give a couple second window to avoid flakiness in the test. + assert!( + seconds_until_refresh <= expiry_seconds / 2 + && seconds_until_refresh >= expiry_seconds / 2 - 1 + ); + + let certs_expired = generate_test_certs(&id, zero_dur, zero_dur); + assert!(certs_expired.is_expired()); + assert_eq!(certs_expired.get_duration_until_refresh(), zero_dur); + + let future_certs = generate_test_certs( + &id, + Duration::from_secs(1000), + Duration::from_secs(expiry_seconds), + ); + assert!(!future_certs.is_expired()); + assert_eq!(future_certs.get_duration_until_refresh(), zero_dur); + } +} diff --git a/src/tls/mock.rs b/src/tls/mock.rs new file mode 100644 index 000000000..9d7704cce --- /dev/null +++ b/src/tls/mock.rs @@ -0,0 +1,178 @@ +use crate::identity::Identity; +use std::fmt::{Display, Formatter}; + +use rand::rngs::SmallRng; +use rand::RngCore; +use rand::SeedableRng; +use rcgen::{Certificate, CertificateParams, KeyPair}; +use std::net::IpAddr; +use std::sync::Arc; +use std::time::{Duration, SystemTime}; + +use crate::tls::TLS_VERSIONS; +use rustls::ServerConfig; +use tokio::net::TcpStream; + +use super::{ServerCertProvider, TlsError, WorkloadCertificate}; + +pub const TEST_CERT: &[u8] = include_bytes!("cert-chain.pem"); +pub const TEST_WORKLOAD_CERT: &[u8] = include_bytes!("cert.pem"); +pub const TEST_PKEY: &[u8] = include_bytes!("key.pem"); +pub const TEST_ROOT: &[u8] = include_bytes!("root-cert.pem"); +pub const TEST_ROOT_KEY: &[u8] = include_bytes!("ca-key.pem"); + +/// TestIdentity is an identity used for testing. This extends the Identity with test-only types +#[derive(Debug)] +pub enum TestIdentity { + Identity(Identity), + Ip(IpAddr), +} + +impl Display for TestIdentity { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TestIdentity::Identity(i) => std::fmt::Display::fmt(&i, f), + TestIdentity::Ip(i) => std::fmt::Display::fmt(&i, f), + } + } +} + +impl From for TestIdentity { + fn from(i: Identity) -> Self { + Self::Identity(i) + } +} + +impl From for TestIdentity { + fn from(i: IpAddr) -> Self { + Self::Ip(i) + } +} + +/// Allows generating test certificates in a deterministic manner. +pub struct CertGenerator { + rng: SmallRng, +} + +impl CertGenerator { + /// Returns a new test certificate generator. The seed parameter sets the seed for any + /// randomized operations. Multiple CertGenerator instances created with the same seed will + /// return the same successive certificates, if same arguments to new_certs are given. + pub fn new(seed: u64) -> Self { + Self { + rng: SmallRng::seed_from_u64(seed), + } + } + + pub fn new_certs( + &mut self, + id: &TestIdentity, + not_before: SystemTime, + not_after: SystemTime, + ) -> WorkloadCertificate { + generate_test_certs_at(id, not_before, not_after, Some(&mut self.rng)) + } +} + +impl Default for CertGenerator { + fn default() -> Self { + // Use arbitrary seed. + Self::new(427) + } +} + +// TODO: Move towards code that doesn't rely on SystemTime::now() for easier time control with +// tokio. Ideally we'll be able to also get rid of the sub-second timestamps on certificates +// (since right now they are there only for testing). +pub fn generate_test_certs_at( + id: &TestIdentity, + not_before: SystemTime, + not_after: SystemTime, + rng: Option<&mut dyn rand::RngCore>, +) -> WorkloadCertificate { + use rcgen::*; + let serial_number = { + let mut data = [0u8; 20]; + match rng { + None => rand::thread_rng().fill_bytes(&mut data), + Some(rng) => rng.fill_bytes(&mut data), + } + // Clear the most significant bit to make the resulting bignum effectively 159 bit long. + data[0] &= 0x7f; + data + }; + let ca_cert = test_ca(); + let mut p = CertificateParams::default(); + p.not_before = not_before.into(); + p.not_after = not_after.into(); + let kp = KeyPair::from_pem(std::str::from_utf8(TEST_PKEY).unwrap()).unwrap(); + let key = kp.serialize_pem(); + p.key_pair = Some(kp); + p.serial_number = Some(SerialNumber::from_slice(&serial_number)); + let mut dn = DistinguishedName::new(); + dn.push(DnType::OrganizationName, "cluster.local"); + p.distinguished_name = dn; + p.key_usages = vec![ + KeyUsagePurpose::DigitalSignature, + KeyUsagePurpose::KeyEncipherment, + ]; + p.extended_key_usages = vec![ + ExtendedKeyUsagePurpose::ServerAuth, + ExtendedKeyUsagePurpose::ClientAuth, + ]; + p.subject_alt_names = vec![match id { + TestIdentity::Identity(i) => SanType::URI(i.to_string()), + TestIdentity::Ip(i) => SanType::IpAddress(*i), + }]; + + let cert = Certificate::from_params(p).unwrap(); + let cert = cert.serialize_pem_with_signer(&ca_cert).unwrap(); + let mut workload = + WorkloadCertificate::new(key.as_bytes(), cert.as_bytes(), vec![TEST_ROOT]).unwrap(); + // Certificates do not allow sub-millisecond, but we need this for tests. + workload.cert.expiry.not_before = not_before; + workload.cert.expiry.not_after = not_after; + workload +} + +pub fn generate_test_certs( + id: &TestIdentity, + duration_until_valid: Duration, + duration_until_expiry: Duration, +) -> WorkloadCertificate { + let not_before = SystemTime::now() + duration_until_valid; + generate_test_certs_at(id, not_before, not_before + duration_until_expiry, None) +} + +fn test_ca() -> Certificate { + let key = KeyPair::from_pem(std::str::from_utf8(TEST_ROOT_KEY).unwrap()).unwrap(); + let ca_param = + CertificateParams::from_ca_cert_pem(std::str::from_utf8(TEST_ROOT).unwrap(), key).unwrap(); + Certificate::from_params(ca_param).unwrap() +} + +#[derive(Debug, Clone)] +pub struct MockServerCertProvider(Arc); + +impl MockServerCertProvider { + pub fn new(w: WorkloadCertificate) -> Self { + MockServerCertProvider(Arc::new(w)) + } +} + +#[async_trait::async_trait] +impl ServerCertProvider for MockServerCertProvider { + async fn fetch_cert(&mut self, _: &TcpStream) -> Result, TlsError> { + let mut sc = ServerConfig::builder_with_provider(crate::tls::lib::provider()) + .with_protocol_versions(TLS_VERSIONS) + .expect("server config must be valid") + .with_no_client_auth() + .with_single_cert( + self.0.cert_and_intermediates(), + self.0.private_key.clone_key(), + ) + .unwrap(); + sc.alpn_protocols = vec![b"h2".into()]; + Ok(Arc::new(sc)) + } +} diff --git a/src/tls/workload.rs b/src/tls/workload.rs new file mode 100644 index 000000000..9de9fd04d --- /dev/null +++ b/src/tls/workload.rs @@ -0,0 +1,261 @@ +use crate::identity::Identity; + +use crate::tls::lib::provider; +use crate::tls::{ServerCertProvider, TlsError}; +use futures_util::TryFutureExt; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; + +use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use rustls::server::danger::{ClientCertVerified, ClientCertVerifier}; +use rustls::server::ParsedCertificate; +use rustls::{ + ClientConfig, DigitallySignedStruct, DistinguishedName, RootCertStore, SignatureScheme, +}; +use std::future::Future; +use std::io; +use std::pin::Pin; +use std::sync::Arc; + +use crate::tls; +use tokio::net::TcpStream; +use tokio_rustls::client; +use tracing::{debug, trace}; + +#[derive(Clone, Debug)] +pub struct InboundAcceptor { + provider: F, +} + +impl InboundAcceptor { + pub fn new(provider: F) -> Self { + Self { provider } + } +} + +#[derive(Debug)] +pub(super) struct TrustDomainVerifier { + base: Arc, + trust_domain: Option, +} + +impl TrustDomainVerifier { + pub fn new(base: Arc, trust_domain: Option) -> Arc { + Arc::new(Self { base, trust_domain }) + } + + fn verify_trust_domain(&self, client_cert: &CertificateDer<'_>) -> Result<(), rustls::Error> { + use x509_parser::prelude::*; + let Some(want_trust_domain) = &self.trust_domain else { + // No need to verify + return Ok(()); + }; + let (_, c) = X509Certificate::from_der(client_cert).map_err(|_e| { + rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding) + })?; + let ids = tls::certificate::identities(c).map_err(|_e| { + rustls::Error::InvalidCertificate( + rustls::CertificateError::ApplicationVerificationFailure, + ) + })?; + trace!( + "verifying client identities {ids:?} against trust domain {:?}", + want_trust_domain + ); + ids.iter() + .find(|id| match id { + Identity::Spiffe { trust_domain, .. } => trust_domain == want_trust_domain, + }) + .ok_or_else(|| { + rustls::Error::InvalidCertificate(rustls::CertificateError::Other( + rustls::OtherError(Arc::new(TlsError::SanTrustDomainError( + want_trust_domain.to_string(), + ids.clone(), + ))), + )) + }) + .map(|_| ()) + } +} + +// Implement our custom ClientCertVerifier logic. We only want to add an extra check, but +// need a decent amount of boilerplate to do so. +impl ClientCertVerifier for TrustDomainVerifier { + fn root_hint_subjects(&self) -> &[DistinguishedName] { + self.base.root_hint_subjects() + } + + fn verify_client_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + now: UnixTime, + ) -> Result { + let res = self + .base + .verify_client_cert(end_entity, intermediates, now)?; + self.verify_trust_domain(end_entity)?; + Ok(res) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.base.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.base.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec { + self.base.supported_verify_schemes() + } +} + +impl tls_listener::AsyncTls for InboundAcceptor +where + F: ServerCertProvider + 'static, +{ + type Stream = tokio_rustls::server::TlsStream; + type Error = TlsError; + type AcceptFuture = Pin> + Send>>; + + fn accept(&self, conn: TcpStream) -> Self::AcceptFuture { + let mut acceptor = self.provider.clone(); + Box::pin(async move { + let tls = acceptor.fetch_cert(&conn).await?; + tokio_rustls::TlsAcceptor::from(tls) + .accept(conn) + .map_err(TlsError::Handshake) + .await + }) + } +} + +#[derive(Clone, Debug)] +pub struct OutboundConnector { + pub(super) client_config: Arc, +} + +impl OutboundConnector { + pub async fn connect( + self, + stream: TcpStream, + ) -> Result, io::Error> { + let dest = ServerName::IpAddress(stream.peer_addr().unwrap().ip().into()); + let c = tokio_rustls::TlsConnector::from(self.client_config); + c.connect(dest, stream).await + } +} + +#[derive(Debug)] +pub struct IdentityVerifier { + pub(super) roots: Arc, + pub(super) identity: Vec, +} + +impl IdentityVerifier { + fn verify_full_san(&self, server_cert: &CertificateDer<'_>) -> Result<(), rustls::Error> { + use x509_parser::prelude::*; + let (_, c) = X509Certificate::from_der(server_cert).map_err(|_e| { + rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding) + })?; + let id = tls::certificate::identities(c).map_err(|_e| { + rustls::Error::InvalidCertificate( + rustls::CertificateError::ApplicationVerificationFailure, + ) + })?; + trace!( + "verifying server identities {id:?} against {:?}", + self.identity + ); + for ident in id.iter() { + if let Some(_i) = self.identity.iter().find(|id| id == &ident) { + return Ok(()); + } + } + debug!("identity mismatch"); + Err(rustls::Error::InvalidCertificate( + rustls::CertificateError::ApplicationVerificationFailure, + )) + } +} + +// Rustls doesn't natively validate URI SAN. +// Build our own verifier, inspired by https://github.com/rustls/rustls/blob/ccb79947a4811412ee7dcddcd0f51ea56bccf101/rustls/src/webpki/server_verifier.rs#L239. +impl ServerCertVerifier for IdentityVerifier { + /// Will verify the certificate is valid in the following ways: + /// - Signed by a trusted `RootCertStore` CA + /// - Not Expired + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _sn: &ServerName, + ocsp_response: &[u8], + now: UnixTime, + ) -> Result { + let cert = ParsedCertificate::try_from(end_entity)?; + + let algs = provider().signature_verification_algorithms; + rustls::client::verify_server_cert_signed_by_trust_anchor( + &cert, + &self.roots, + intermediates, + now, + algs.all, + )?; + + if !ocsp_response.is_empty() { + trace!("Unvalidated OCSP response: {ocsp_response:?}"); + } + + self.verify_full_san(end_entity)?; + + Ok(ServerCertVerified::assertion()) + } + + // Rest use the default implementations + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &provider().signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + provider() + .signature_verification_algorithms + .supported_schemes() + } +} diff --git a/tests/namespaced.rs b/tests/namespaced.rs index 1165c4a13..8a4a89fb5 100644 --- a/tests/namespaced.rs +++ b/tests/namespaced.rs @@ -26,6 +26,7 @@ mod namespaced { use hyper::Method; use hyper_util::rt::TokioIo; + use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadBuf}; use tokio::net::TcpStream; use tokio::time::timeout; @@ -552,18 +553,12 @@ mod namespaced { identity::Identity::from_str("spiffe://cluster.local/ns/default/sa/default") .unwrap(); let cert = app.cert_manager.fetch_certificate(id).await?; - let mut connector = cert - .connector(vec![dst_id]) - .unwrap() - .configure() - .expect("configure"); - connector.set_verify_hostname(false); - connector.set_use_server_name_indication(false); + let connector = cert.outbound_connector(vec![dst_id]).unwrap(); + // connector.set_verify_hostname(false); + // connector.set_use_server_name_indication(false); let hbone = SocketAddr::new(srv.ip(), 15008); let tcp_stream = TcpStream::connect(hbone).await.unwrap(); - let tls_stream = tokio_boring::connect(connector, "", tcp_stream) - .await - .unwrap(); + let tls_stream = connector.connect(tcp_stream).await.unwrap(); let (mut request_sender, connection) = builder.handshake(TokioIo::new(tls_stream)).await.unwrap(); // spawn a task to poll the connection and drive the HTTP state @@ -611,19 +606,13 @@ mod namespaced { identity::Identity::from_str("spiffe://cluster.local/ns/default/sa/default") .unwrap(); let cert = app.cert_manager.fetch_certificate(id).await?; - let mut connector = cert - .connector(vec![dst_id]) - .unwrap() - .configure() - .expect("configure"); - connector.set_verify_hostname(false); - connector.set_use_server_name_indication(false); + let connector = cert.outbound_connector(vec![dst_id]).unwrap(); + // connector.set_verify_hostname(false); + // connector.set_use_server_name_indication(false); let tcp_stream = TcpStream::connect(app.proxy_addresses.inbound) .await .unwrap(); - let tls_stream = tokio_boring::connect(connector, "", tcp_stream) - .await - .unwrap(); + let tls_stream = connector.connect(tcp_stream).await.unwrap(); let (mut request_sender, connection) = builder.handshake(TokioIo::new(tls_stream)).await.unwrap(); // spawn a task to poll the connection and drive the HTTP state diff --git a/vendor/boringssl-fips/linux_x86_64/build/crypto/libcrypto.a b/vendor/boringssl-fips/linux_x86_64/lib/libcrypto.a similarity index 100% rename from vendor/boringssl-fips/linux_x86_64/build/crypto/libcrypto.a rename to vendor/boringssl-fips/linux_x86_64/lib/libcrypto.a diff --git a/vendor/boringssl-fips/linux_x86_64/build/ssl/libssl.a b/vendor/boringssl-fips/linux_x86_64/lib/libssl.a similarity index 100% rename from vendor/boringssl-fips/linux_x86_64/build/ssl/libssl.a rename to vendor/boringssl-fips/linux_x86_64/lib/libssl.a