Support multiple TLS certificates. (#95)
This commit is contained in:
parent
1749989dc5
commit
9a25143694
|
@ -23,3 +23,6 @@ jobs:
|
|||
- run: cargo test --verbose
|
||||
- run: cargo clippy --no-deps
|
||||
- run: cargo fmt --check
|
||||
|
||||
# Check for unused dependencies
|
||||
- uses: bnjbvr/cargo-machete@main
|
||||
|
|
|
@ -273,6 +273,26 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-server"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447f28c85900215cc1bea282f32d4a2f22d55c5a300afdfbc661c8d6a632e063"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
|
@ -306,15 +326,6 @@ version = "2.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.3.1"
|
||||
|
@ -458,15 +469,6 @@ version = "0.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
|
@ -476,32 +478,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[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.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
|
@ -712,16 +688,6 @@ 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.2.10"
|
||||
|
@ -782,30 +748,6 @@ version = "0.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"headers-core",
|
||||
"http",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers-core"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
|
||||
dependencies = [
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
|
@ -855,6 +797,12 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
|
@ -1065,16 +1013,6 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
@ -1110,7 +1048,6 @@ dependencies = [
|
|||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -1127,10 +1064,8 @@ dependencies = [
|
|||
"mp4",
|
||||
"quinn",
|
||||
"rfc6381-codec",
|
||||
"ring",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pemfile",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"url",
|
||||
|
@ -1142,6 +1077,8 @@ name = "moq-relay"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum-server",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"hex",
|
||||
|
@ -1149,16 +1086,17 @@ dependencies = [
|
|||
"moq-api",
|
||||
"moq-transport",
|
||||
"quinn",
|
||||
"ring",
|
||||
"ring 0.16.20",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pemfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"warp",
|
||||
"webpki",
|
||||
"webtransport-quinn",
|
||||
]
|
||||
|
||||
|
@ -1204,24 +1142,6 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a1fe2275b68991faded2c80aa4a33dba398b77d276038b8f50701a22e55918"
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin 0.9.8",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
|
@ -1497,7 +1417,7 @@ checksum = "e13f81c9a9d574310b8351f8666f5a93ac3b0069c45c28ad52c10291389a7cf9"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"rand",
|
||||
"ring",
|
||||
"ring 0.16.20",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
|
@ -1688,11 +1608,25 @@ dependencies = [
|
|||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted",
|
||||
"untrusted 0.7.1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin 0.9.8",
|
||||
"untrusted 0.9.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roff"
|
||||
version = "0.2.1"
|
||||
|
@ -1745,7 +1679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"ring 0.16.20",
|
||||
"rustls-webpki",
|
||||
"sct",
|
||||
]
|
||||
|
@ -1777,8 +1711,8 @@ version = "0.101.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.16.20",
|
||||
"untrusted 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1802,12 +1736,6 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
|
@ -1820,8 +1748,8 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.16.20",
|
||||
"untrusted 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1900,17 +1828,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.0"
|
||||
|
@ -2154,29 +2071,6 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.8"
|
||||
|
@ -2207,6 +2101,24 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-range-header",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
|
@ -2284,40 +2196,6 @@ version = "0.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
|
@ -2345,6 +2223,12 @@ 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.4.1"
|
||||
|
@ -2357,12 +2241,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
|
@ -2387,12 +2265,6 @@ version = "0.2.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.0"
|
||||
|
@ -2408,38 +2280,6 @@ dependencies = [
|
|||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "warp"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1e92e22e03ff1230c03a1a8ee37d2f89cd489e2e541b7550d6afad96faed169"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http",
|
||||
"hyper",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"multer",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"rustls-pemfile",
|
||||
"scoped-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -2522,6 +2362,16 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
|
||||
dependencies = [
|
||||
"ring 0.17.3",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.2"
|
||||
|
|
|
@ -19,7 +19,6 @@ categories = ["multimedia", "network-programming", "web-programming"]
|
|||
axum = "0.6"
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tower = "0.4"
|
||||
|
||||
# HTTP client
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||
|
|
|
@ -23,9 +23,7 @@ webtransport-quinn = "0.6"
|
|||
url = "2"
|
||||
|
||||
# Crypto
|
||||
ring = "0.16"
|
||||
rustls = "0.21"
|
||||
rustls-pemfile = "1"
|
||||
rustls-native-certs = "0.6"
|
||||
|
||||
# Async stuff
|
||||
|
|
|
@ -26,13 +26,16 @@ ring = "0.16"
|
|||
rustls = "0.21"
|
||||
rustls-pemfile = "1"
|
||||
rustls-native-certs = "0.6"
|
||||
webpki = "0.22"
|
||||
|
||||
# Async stuff
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
# Web server to serve the fingerprint
|
||||
warp = { version = "0.3.6", features = ["tls"] }
|
||||
axum = { version = "0.6", features = ["tokio"] }
|
||||
axum-server = { version = "0.5", features = ["tls-rustls"] }
|
||||
hex = "0.4"
|
||||
tower-http = { version = "0.4", features = ["cors"] }
|
||||
|
||||
# Error handling
|
||||
anyhow = { version = "1", features = ["backtrace"] }
|
||||
|
|
|
@ -10,13 +10,19 @@ pub struct Config {
|
|||
#[arg(long, default_value = "[::]:4443")]
|
||||
pub listen: net::SocketAddr,
|
||||
|
||||
/// Use the certificate file at this path
|
||||
/// Use the certificates at this path, encoded as PEM.
|
||||
///
|
||||
/// You can use this option multiple times for multiple certificates.
|
||||
/// The first match for the provided SNI will be used, otherwise the last cert will be used.
|
||||
/// You also need to provide the private key multiple times via `key``.
|
||||
#[arg(long)]
|
||||
pub cert: path::PathBuf,
|
||||
pub cert: Vec<path::PathBuf>,
|
||||
|
||||
/// Use the private key at this path
|
||||
/// Use the private key at this path, encoded as PEM.
|
||||
///
|
||||
/// There must be a key for every certificate provided via `cert`.
|
||||
#[arg(long)]
|
||||
pub key: path::PathBuf,
|
||||
pub key: Vec<path::PathBuf>,
|
||||
|
||||
/// Listen on HTTPS and serve /fingerprint, for self-signed certificates
|
||||
#[arg(long, action)]
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
use std::{fs, io, sync};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use ring::digest::{digest, SHA256};
|
||||
use warp::Filter;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod origin;
|
||||
mod server;
|
||||
mod quic;
|
||||
mod session;
|
||||
mod tls;
|
||||
mod web;
|
||||
|
||||
pub use config::*;
|
||||
pub use error::*;
|
||||
pub use origin::*;
|
||||
pub use server::*;
|
||||
pub use quic::*;
|
||||
pub use session::*;
|
||||
pub use tls::*;
|
||||
pub use web::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
|
@ -28,47 +28,20 @@ async fn main() -> anyhow::Result<()> {
|
|||
tracing::subscriber::set_global_default(tracer).unwrap();
|
||||
|
||||
let config = Config::parse();
|
||||
let tls = Tls::load(&config)?;
|
||||
|
||||
// Create a server to actually serve the media
|
||||
let server = Server::new(config.clone()).await.context("failed to create server")?;
|
||||
// Create a QUIC server for media.
|
||||
let quic = Quic::new(config.clone(), tls.clone())
|
||||
.await
|
||||
.context("failed to create server")?;
|
||||
|
||||
// Create the web server if the --fingerprint flag was set.
|
||||
// This is currently only useful in local development so it's not enabled by default.
|
||||
let web = config.fingerprint.then(|| Web::new(config, tls));
|
||||
|
||||
// Run all of the above
|
||||
tokio::select! {
|
||||
res = server.run() => res.context("failed to run server"),
|
||||
res = serve_http(config), if config.fingerprint => res.context("failed to run HTTP server"),
|
||||
res = quic.serve() => res.context("failed to run server"),
|
||||
res = web.unwrap().serve(), if web.is_some() => res.context("failed to run HTTP server"),
|
||||
}
|
||||
}
|
||||
|
||||
// Run a HTTP server using Warp
|
||||
// TODO remove this when Chrome adds support for self-signed certificates using WebTransport
|
||||
async fn serve_http(config: Config) -> anyhow::Result<()> {
|
||||
// Read the PEM certificate file
|
||||
let crt = fs::File::open(&config.cert)?;
|
||||
let mut crt = io::BufReader::new(crt);
|
||||
|
||||
// Parse the DER certificate
|
||||
let certs = rustls_pemfile::certs(&mut crt)?;
|
||||
let cert = certs.first().expect("no certificate found");
|
||||
|
||||
// Compute the SHA-256 digest
|
||||
let fingerprint = digest(&SHA256, cert.as_ref());
|
||||
let fingerprint = hex::encode(fingerprint.as_ref());
|
||||
let fingerprint = sync::Arc::new(fingerprint);
|
||||
|
||||
let cors = warp::cors().allow_any_origin();
|
||||
|
||||
// What an annoyingly complicated way to serve a static String
|
||||
// I spent a long time trying to find the exact way of cloning and dereferencing the Arc.
|
||||
let routes = warp::path!("fingerprint")
|
||||
.map(move || (*(fingerprint.clone())).clone())
|
||||
.with(cors);
|
||||
|
||||
warp::serve(routes)
|
||||
.tls()
|
||||
.cert_path(config.cert)
|
||||
.key_path(config.key)
|
||||
.run(config.listen)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
use std::{
|
||||
fs,
|
||||
io::{self, Read},
|
||||
sync::Arc,
|
||||
time,
|
||||
};
|
||||
use std::{sync::Arc, time};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use crate::{Config, Origin, Session};
|
||||
use crate::{Config, Origin, Session, Tls};
|
||||
|
||||
pub struct Server {
|
||||
pub struct Quic {
|
||||
quic: quinn::Endpoint,
|
||||
|
||||
// The active connections.
|
||||
|
@ -21,65 +16,11 @@ pub struct Server {
|
|||
origin: Origin,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
// Create a new server
|
||||
pub async fn new(config: Config) -> anyhow::Result<Self> {
|
||||
// Read the PEM certificate chain
|
||||
let certs = fs::File::open(config.cert).context("failed to open cert file")?;
|
||||
let mut certs = io::BufReader::new(certs);
|
||||
|
||||
let certs: Vec<rustls::Certificate> = rustls_pemfile::certs(&mut certs)?
|
||||
.into_iter()
|
||||
.map(rustls::Certificate)
|
||||
.collect();
|
||||
|
||||
anyhow::ensure!(!certs.is_empty(), "could not find certificate");
|
||||
|
||||
// Read the PEM private key
|
||||
let mut keys = fs::File::open(config.key).context("failed to open key file")?;
|
||||
|
||||
// Read the keys into a Vec so we can try parsing it twice.
|
||||
let mut buf = Vec::new();
|
||||
keys.read_to_end(&mut buf)?;
|
||||
|
||||
// Try to parse a PKCS#8 key
|
||||
// -----BEGIN PRIVATE KEY-----
|
||||
let mut keys = rustls_pemfile::pkcs8_private_keys(&mut io::Cursor::new(&buf))?;
|
||||
|
||||
// Try again but with EC keys this time
|
||||
// -----BEGIN EC PRIVATE KEY-----
|
||||
if keys.is_empty() {
|
||||
keys = rustls_pemfile::ec_private_keys(&mut io::Cursor::new(&buf))?
|
||||
};
|
||||
|
||||
anyhow::ensure!(!keys.is_empty(), "could not find private key");
|
||||
anyhow::ensure!(keys.len() < 2, "expected a single key");
|
||||
|
||||
let key = rustls::PrivateKey(keys.remove(0));
|
||||
|
||||
// Set up a QUIC endpoint that can act as both a client and server.
|
||||
|
||||
// Create a list of acceptable root certificates.
|
||||
let mut client_roots = rustls::RootCertStore::empty();
|
||||
|
||||
// Add the platform's native root certificates.
|
||||
for cert in rustls_native_certs::load_native_certs().context("could not load platform certs")? {
|
||||
client_roots
|
||||
.add(&rustls::Certificate(cert.0))
|
||||
.context("failed to add root cert")?;
|
||||
}
|
||||
|
||||
// For local development, we'll accept our own certificate.
|
||||
let mut client_config = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(client_roots)
|
||||
.with_no_client_auth();
|
||||
|
||||
let mut server_config = rustls::ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key)?;
|
||||
|
||||
impl Quic {
|
||||
// Create a QUIC endpoint that can be used for both clients and servers.
|
||||
pub async fn new(config: Config, tls: Tls) -> anyhow::Result<Self> {
|
||||
let mut client_config = tls.client();
|
||||
let mut server_config = tls.server();
|
||||
client_config.alpn_protocols = vec![webtransport_quinn::ALPN.to_vec()];
|
||||
server_config.alpn_protocols = vec![webtransport_quinn::ALPN.to_vec()];
|
||||
|
||||
|
@ -121,7 +62,7 @@ impl Server {
|
|||
Ok(Self { quic, origin, conns })
|
||||
}
|
||||
|
||||
pub async fn run(mut self) -> anyhow::Result<()> {
|
||||
pub async fn serve(mut self) -> anyhow::Result<()> {
|
||||
log::info!("listening on {}", self.quic.local_addr()?);
|
||||
|
||||
loop {
|
|
@ -0,0 +1,152 @@
|
|||
use anyhow::Context;
|
||||
use ring::digest::{digest, SHA256};
|
||||
use rustls::server::{ClientHello, ResolvesServerCert};
|
||||
use rustls::sign::CertifiedKey;
|
||||
use rustls::{Certificate, PrivateKey, RootCertStore};
|
||||
use rustls::{ClientConfig, ServerConfig};
|
||||
use std::fs;
|
||||
use std::io::{self, Cursor, Read};
|
||||
use std::path;
|
||||
use std::sync::Arc;
|
||||
use webpki::{DnsNameRef, EndEntityCert};
|
||||
|
||||
use crate::Config;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Tls {
|
||||
// Support serving multiple certificates, choosing one that looks valid for the given SNI.
|
||||
// We store the parsed certificate, and the certified cert/key that rustls expects
|
||||
serve: Arc<ServeCerts>,
|
||||
|
||||
// Accept any cert that is trusted by the system's native trust store.
|
||||
accept: Arc<RootCertStore>,
|
||||
}
|
||||
|
||||
impl Tls {
|
||||
pub fn load(config: &Config) -> anyhow::Result<Self> {
|
||||
let mut serve = ServeCerts::default();
|
||||
|
||||
// Load the certificate and key files based on their index.
|
||||
anyhow::ensure!(config.cert.len() == config.key.len(), "--cert and --key mismatch");
|
||||
for (chain, key) in config.cert.iter().zip(config.key.iter()) {
|
||||
serve.load(chain, key)?;
|
||||
}
|
||||
|
||||
// Create a list of acceptable root certificates.
|
||||
let mut roots = RootCertStore::empty();
|
||||
|
||||
// Add the platform's native root certificates.
|
||||
for cert in rustls_native_certs::load_native_certs().context("could not load platform certs")? {
|
||||
roots.add(&Certificate(cert.0)).context("failed to add root cert")?;
|
||||
}
|
||||
|
||||
let certs = Self {
|
||||
serve: Arc::new(serve),
|
||||
accept: Arc::new(roots),
|
||||
};
|
||||
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
pub fn client(&self) -> ClientConfig {
|
||||
rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(self.accept.clone())
|
||||
.with_no_client_auth()
|
||||
}
|
||||
|
||||
pub fn server(&self) -> ServerConfig {
|
||||
rustls::ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_cert_resolver(self.serve.clone())
|
||||
}
|
||||
|
||||
// Return the SHA256 fingerprint of our certificates.
|
||||
pub fn fingerprints(&self) -> Vec<String> {
|
||||
self.serve.fingerprints()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ServeCerts {
|
||||
list: Vec<Arc<CertifiedKey>>,
|
||||
}
|
||||
|
||||
impl ServeCerts {
|
||||
// Load a certificate and cooresponding key from a file
|
||||
pub fn load(&mut self, chain: &path::PathBuf, key: &path::PathBuf) -> anyhow::Result<()> {
|
||||
// Read the PEM certificate chain
|
||||
let chain = fs::File::open(chain).context("failed to open cert file")?;
|
||||
let mut chain = io::BufReader::new(chain);
|
||||
|
||||
let chain: Vec<Certificate> = rustls_pemfile::certs(&mut chain)?
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
|
||||
anyhow::ensure!(!chain.is_empty(), "could not find certificate");
|
||||
|
||||
// Read the PEM private key
|
||||
let mut keys = fs::File::open(key).context("failed to open key file")?;
|
||||
|
||||
// Read the keys into a Vec so we can parse it twice.
|
||||
let mut buf = Vec::new();
|
||||
keys.read_to_end(&mut buf)?;
|
||||
|
||||
// Try to parse a PKCS#8 key
|
||||
// -----BEGIN PRIVATE KEY-----
|
||||
let mut keys = rustls_pemfile::pkcs8_private_keys(&mut Cursor::new(&buf))?;
|
||||
|
||||
// Try again but with EC keys this time
|
||||
// -----BEGIN EC PRIVATE KEY-----
|
||||
if keys.is_empty() {
|
||||
keys = rustls_pemfile::ec_private_keys(&mut Cursor::new(&buf))?
|
||||
};
|
||||
|
||||
anyhow::ensure!(!keys.is_empty(), "could not find private key");
|
||||
anyhow::ensure!(keys.len() < 2, "expected a single key");
|
||||
|
||||
let key = PrivateKey(keys.remove(0));
|
||||
let key = rustls::sign::any_supported_type(&key)?;
|
||||
|
||||
let certified = Arc::new(CertifiedKey::new(chain, key));
|
||||
self.list.push(certified);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Return the SHA256 fingerprint of our certificates.
|
||||
pub fn fingerprints(&self) -> Vec<String> {
|
||||
self.list
|
||||
.iter()
|
||||
.map(|ck| {
|
||||
let fingerprint = digest(&SHA256, ck.cert[0].as_ref());
|
||||
let fingerprint = hex::encode(fingerprint.as_ref());
|
||||
fingerprint
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvesServerCert for ServeCerts {
|
||||
fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
|
||||
if let Some(name) = client_hello.server_name() {
|
||||
if let Ok(dns_name) = DnsNameRef::try_from_ascii_str(name) {
|
||||
for ck in &self.list {
|
||||
// TODO I gave up on caching the parsed result because of lifetime hell.
|
||||
// If this shows up on benchmarks, somebody should fix it.
|
||||
let leaf = ck.cert.first().expect("missing certificate");
|
||||
let parsed = EndEntityCert::try_from(leaf.0.as_ref()).expect("failed to parse certificate");
|
||||
|
||||
if parsed.verify_is_valid_for_dns_name(dns_name).is_ok() {
|
||||
return Some(ck.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the last certificate if we couldn't find one.
|
||||
self.list.last().cloned()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::State, http::Method, response::IntoResponse, routing::get, Router};
|
||||
use axum_server::{tls_rustls::RustlsAcceptor, Server};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
use crate::{Config, Tls};
|
||||
|
||||
// Run a HTTP server using Axum
|
||||
// TODO remove this when Chrome adds support for self-signed certificates using WebTransport
|
||||
pub struct Web {
|
||||
app: Router,
|
||||
server: Server<RustlsAcceptor>,
|
||||
}
|
||||
|
||||
impl Web {
|
||||
pub fn new(config: Config, tls: Tls) -> Self {
|
||||
// Get the first certificate's fingerprint.
|
||||
// TODO serve all of them so we can support multiple signature algorithms.
|
||||
let fingerprint = tls.fingerprints().first().expect("missing certificate").clone();
|
||||
|
||||
let mut tls_config = tls.server();
|
||||
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
let tls_config = axum_server::tls_rustls::RustlsConfig::from_config(Arc::new(tls_config));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/fingerprint", get(serve_fingerprint))
|
||||
.layer(CorsLayer::new().allow_origin(Any).allow_methods([Method::GET]))
|
||||
.with_state(fingerprint);
|
||||
|
||||
let server = axum_server::bind_rustls(config.listen, tls_config);
|
||||
|
||||
Self { app, server }
|
||||
}
|
||||
|
||||
pub async fn serve(self) -> anyhow::Result<()> {
|
||||
self.server.serve(self.app.into_make_service()).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_fingerprint(State(fingerprint): State<String>) -> impl IntoResponse {
|
||||
fingerprint
|
||||
}
|
Loading…
Reference in New Issue