signal-tlsd-0.1.1/.cargo_vcs_info.json0000644000000001361046102023000132760ustar { "git": { "sha1": "7e94e8c08f1211f60dab9558e3e997ada4a21859" }, "path_in_vcs": "" }signal-tlsd-0.1.1/.dockerignore000064400000000000000000000000531046102023000145160ustar 00000000000000Dockerfile .dockerignore target .env *.pem signal-tlsd-0.1.1/.github/workflows/docker-image.yml000064400000000000000000000036111046102023000205140ustar 00000000000000name: Docker on: push: branches: [ "main" ] tags: [ "v*.*.*" ] pull_request: branches: [ "main" ] jobs: build: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: docker/setup-buildx-action@v3 - name: Expose actions cache variables uses: actions/github-script@v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env['ACTIONS_CACHE_URL']) core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']) - name: Build Docker image uses: docker/build-push-action@v5 with: tags: signal-tlsd load: true cache-from: type=gha cache-to: type=gha,mode=max - name: Test the Docker image run: | docker run --rm signal-tlsd --help - name: Login to github container registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Push the image to `edge` if: github.event_name == 'push' && github.ref_name == 'main' run: | docker tag signal-tlsd ghcr.io/${{ github.repository }}:edge docker push ghcr.io/${{ github.repository }}:edge - name: Push the image to `${{ github.ref_name }}` if: github.ref_type == 'tag' run: | docker tag signal-tlsd ghcr.io/${{ github.repository }}:${{ github.ref_name }} docker push ghcr.io/${{ github.repository }}:${{ github.ref_name }} - name: Push the image to `latest` if: github.ref_type == 'tag' run: | docker tag signal-tlsd ghcr.io/${{ github.repository }}:latest docker push ghcr.io/${{ github.repository }}:latest signal-tlsd-0.1.1/.github/workflows/rust.yml000064400000000000000000000033501046102023000171620ustar 00000000000000name: Rust on: push: branches: [ "main" ] pull_request: branches: [ "main" ] schedule: - cron: '0 9 * * 1' permissions: {} env: CARGO_TERM_COLOR: always jobs: build: runs-on: ${{ matrix.os.name }} strategy: fail-fast: false matrix: os: - name: ubuntu-24.04 - name: macos-latest - name: windows-latest steps: - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up cargo cache uses: actions/cache@v5 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-cargo-release- - name: Build run: cargo build --release --verbose unit-test: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up cargo cache uses: actions/cache@v5 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-cargo-debug- - name: Run clippy run: cargo clippy --all -- -D warnings - name: Run tests run: cargo test --all fmt: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 with: persist-credentials: false - name: Run cargo fmt run: cargo fmt --all -- --check signal-tlsd-0.1.1/.gitignore000064400000000000000000000000171046102023000140320ustar 00000000000000/target /*.pem signal-tlsd-0.1.1/Cargo.lock0000644000000335701046102023000112610ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] [[package]] name = "aws-lc-rs" version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "zeroize", ] [[package]] name = "aws-lc-sys" version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", "dunce", "fs_extra", ] [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_derive" version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "env_filter" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", ] [[package]] name = "env_logger" version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "env_filter", "log", ] [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", "wasip2", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.4", "libc", ] [[package]] name = "libc" version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mio" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustls" version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[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.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ "errno", "libc", ] [[package]] name = "signal-tlsd" version = "0.1.1" dependencies = [ "anyhow", "arc-swap", "clap", "env_logger", "log", "tokio", "tokio-rustls", ] [[package]] name = "socket2" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tokio" version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ "wit-bindgen", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" signal-tlsd-0.1.1/Cargo.toml0000644000000026351046102023000113020ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "signal-tlsd" version = "0.1.1" authors = ["kpcyrd "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Standalone Rust implementation of Signal's domain fronting TLS proxy" readme = "README.md" license = "MIT-0" repository = "https://github.com/kpcyrd/signal-tlsd" [[bin]] name = "signal-tlsd" path = "src/main.rs" [dependencies.anyhow] version = "1" [dependencies.arc-swap] version = "1" [dependencies.clap] version = "4" features = [ "derive", "env", "error-context", "help", "std", ] default-features = false [dependencies.env_logger] version = "0.11" default-features = false [dependencies.log] version = "0.4" [dependencies.tokio] version = "1.50" features = [ "fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time", ] [dependencies.tokio-rustls] version = "0.26" signal-tlsd-0.1.1/Cargo.toml.orig000064400000000000000000000011641046102023000147350ustar 00000000000000[package] name = "signal-tlsd" version = "0.1.1" description = "Standalone Rust implementation of Signal's domain fronting TLS proxy" authors = ["kpcyrd "] license = "MIT-0" repository = "https://github.com/kpcyrd/signal-tlsd" edition = "2024" [dependencies] anyhow = "1" arc-swap = "1" clap = { version = "4", default-features = false, features = ["derive", "env", "error-context", "help", "std"] } env_logger = { version = "0.11", default-features = false } log = "0.4" tokio = { version = "1.50", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] } tokio-rustls = "0.26" signal-tlsd-0.1.1/Dockerfile000064400000000000000000000007751046102023000140470ustar 00000000000000FROM rust:1-alpine3.23 ENV RUSTFLAGS="-C strip=debuginfo" WORKDIR /app COPY . . RUN --mount=type=cache,target=/var/cache/buildkit \ CARGO_HOME=/var/cache/buildkit/cargo \ CARGO_TARGET_DIR=/var/cache/buildkit/target \ cargo build --release --locked && \ cp -v /var/cache/buildkit/target/release/signal-tlsd / FROM alpine:3.23 RUN apk add libcap-setcap COPY --from=0 /signal-tlsd / RUN setcap cap_net_bind_service=+ep /signal-tlsd USER nobody ENV BIND_ADDR=[::]:443 ENTRYPOINT ["/signal-tlsd"] signal-tlsd-0.1.1/LICENSE-MIT-0000064400000000000000000000016261046102023000136420ustar 00000000000000MIT No Attribution Copyright (c) 2026 kpcyrd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. signal-tlsd-0.1.1/README.md000064400000000000000000000130351046102023000133250ustar 00000000000000# signal-tlsd Standalone Rust implementation of Signal's domain fronting TLS proxy. Negotiates an outer TLS handshake and listens for an incoming TLS connection from the Signal client. The inner TLS connection remains end-to-end encrypted between the client and the Signal server, while the outer TLS connection is terminated at the proxy. ``` -----------------------------.------------. .-' '-. .'-------------------------.------. / .' '. www.example.com | chat.signal.org / \ | | | encrypted | encrypted \ / \ '. .' '.-------------------------'------' '-. .-' -----------------------------'------------' ``` This evades censorship based on IP address blocking, DNS filtering, and SNI-based [deep packet inspection](https://en.wikipedia.org/wiki/Deep_packet_inspection). It implements the protocol of the [official Signal-TLS-Proxy](https://github.com/signalapp/Signal-TLS-Proxy), but in a single process instead of two nginx instances glued together with docker-compose. It was successfully field-tested in April 2026 to evade Russian censorship of Signal, through a VPS located in Kazakhstan. ## Usage Packaging status A real-world example invocation may look like this: ``` ./signal-tlsd -v -B '[::]:443' \ --cert /var/lib/acme-redirect/live/example.com/fullchain \ --private-key /var/lib/acme-redirect/live/example.com/privkey ``` You may use [acme-redirect](https://github.com/kpcyrd/acme-redirect) to obtain your TLS certificates, but anything else works too. To privately share your proxy with your friends you can send them a link like: ``` https://signal.tube/#example.com ``` ### Fallback endpoint By default, if the inner connection either doesn't start with a TLS client hello, or the inner SNI value does not match any configured endpoints, the connection is shut down. When setting `-F 127.0.0.1:8080` those connections aren't dropped, but instead forwarded to this default endpoint (together with the buffered data). This can be used to host regular websites on your cover domain, while still allowing Signal to use the same domain for its TLS connections. ### Non-standard allowlist Without any `-A` options used, it's using the built-in allowlist of signal endpoints. If the `-A` option _is_ used, it starts with an empty allowlist rejecting everything, and only the specified endpoints are allowed: ``` ./signal-tlsd -B '[::]:443' \ --cert /var/lib/acme-redirect/live/example.com/fullchain \ --private-key /var/lib/acme-redirect/live/example.com/privkey \ -A orcas.sink.yachts -A example.com ``` The special value `-` leaves the allowlist unmodified, yet still counts as using the `-A` option, giving you an empty allowlist instead of the standard built-in list. ### Running signal-tlsd as regular TLS termination proxy It's possible to use signal-tlsd as a regular off-the-shelf TLS termination proxy, without using the inner TLS feature at all. For this mode of operation, use an empty allowlist (`-A -`) together with the `-F ` option, causing everything to be forwarded to the fallback unconditionally. ``` ./signal-tlsd -B '[::]:443' \ --cert /var/lib/acme-redirect/live/example.com/fullchain \ --private-key /var/lib/acme-redirect/live/example.com/privkey \ -A - -F 127.0.0.1:8080 -v ``` ### Reload certificates When receiving a `SIGHUP` signal, the certificates are reloaded from disk. This allows you to update the TLS certificate without needing to restart the server. ## Compiling You need Rust to compile this project, if you don't have it installed already you can either get it from your operating system's package manager or from [rustup.rs](https://rustup.rs/). ``` git clone https://github.com/kpcyrd/signal-tlsd.git cd signal-tlsd cargo build --release ./target/release/signal-tlsd --help ``` ### Compiling with Docker Alternatively, you can use the provided Dockerfile: ``` docker build -t signal-tlsd . docker run --rm signal-tlsd --help ``` ### Testing inner TLS layer This runs the server without the outer TLS layer, so you can connect directly to the inner TLS layer. This is useful for testing the inner TLS handling without needing to set up an outer TLS certificate, or if you terminate the outer TLS layer with a separate TLS proxy, like nginx. ``` # run this in other terminal cargo run --release -- -v -A orca.toys -N -B 127.0.0.1:4443 # send a request curl --resolve orca.toys:4443:127.0.0.1 https://orca.toys:4443/ ``` ### Testing outer TLS layer This is the regular mode of operation, you need to provide a TLS certificate for the outer TLS layer. For testing, any self-signed certificate can be used. We use `socat` so `curl` can connect to the inner TLS layer directly, without needing to understand Signal's TLS proxying mechanism. ``` # generate certificate sh4d0wup keygen tls > tls.pem # run this in other terminal cargo run --release -- -v -A orca.toys:443 -B 127.0.0.1:4443 --cert tls.pem --private-key tls.pem # run this in yet other terminal socat tcp-listen:4442,reuseaddr openssl:localhost:4443,verify=0 # send a request curl --resolve orca.toys:4442:127.0.0.1 https://orca.toys:4442/ ``` ## License `MIT-0` signal-tlsd-0.1.1/contrib/signal-tlsd.confd000064400000000000000000000003741046102023000167440ustar 00000000000000export TLS_CERT_PATH=/etc/signal-tlsd/fullchain.pem export TLS_PRIVATE_KEY_PATH=/etc/signal-tlsd/privkey.pem #export SIGNAL_TLSD_FALLBACK_ADDR=127.0.0.1:8080 signal_tlsd_user="signal-tlsd" signal_tlsd_group="signal-tlsd" signal_tlsd_args="-B [::]:443" signal-tlsd-0.1.1/contrib/signal-tlsd.initd000064400000000000000000000005611046102023000167600ustar 00000000000000#!/sbin/openrc-run name=signal-tlsd description="signal-tlsd proxy daemon" command=/usr/bin/signal-tlsd command_args="$signal_tlsd_args" command_user="$signal_tlsd_user:$signal_tlsd_group" command_background=true pidfile="/run/$RC_SVCNAME.pid" error_logger="logger -t '${RC_SVCNAME}' -p daemon.info" depend() { use dns logger need localmount net after firewall } signal-tlsd-0.1.1/contrib/signal-tlsd.service000064400000000000000000000020511046102023000173050ustar 00000000000000# To configure certificates, either place them at the right location, # use symlinks, or use `systemctl edit signal-tlsd`, then add the following: # # [Service] # Environment=TLS_CERT_PATH=/your/path/fullchain.pem # Environment=TLS_PRIVATE_KEY_PATH=/your/path/privkey.pem # # Optionally: # #Environment=SIGNAL_TLSD_FALLBACK_ADDR=127.0.0.1:8080 # # Use systemctl daemon-reload afterwards. [Unit] Description=signal-tlsd: Standalone Rust implementation of Signal's domain fronting TLS proxy After=network-online.target remote-fs.target nss-lookup.target Wants=network-online.target [Service] User=signal-tlsd Environment=TLS_CERT_PATH=/etc/signal-tlsd/fullchain.pem Environment=TLS_PRIVATE_KEY_PATH=/etc/signal-tlsd/privkey.pem ExecStart=/usr/bin/signal-tlsd -B [::]:443 AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE MemoryDenyWriteExecute=yes NoNewPrivileges=yes PrivateTmp=yes ProtectControlGroups=yes ProtectHome=yes ProtectKernelTunables=yes ProtectSystem=strict RestrictSUIDSGID=yes [Install] WantedBy=multi-user.target signal-tlsd-0.1.1/contrib/signal-tlsd.sysusers000064400000000000000000000000171046102023000175450ustar 00000000000000u! signal-tlsd signal-tlsd-0.1.1/contrib/signal-tlsd.tmpfiles000064400000000000000000000000511046102023000174660ustar 00000000000000d /etc/signal-tlsd 0750 root signal-tlsd signal-tlsd-0.1.1/src/errors.rs000064400000000000000000000001301046102023000145070ustar 00000000000000pub use anyhow::{Context as _, Error, Result}; pub use log::{debug, info, trace, warn}; signal-tlsd-0.1.1/src/main.rs000064400000000000000000000254131046102023000141320ustar 00000000000000mod errors; mod readahead; mod rules; mod signals; mod tls; use crate::errors::*; use crate::readahead::ReadAhead; use crate::rules::Rules; use crate::tls::Tls; use clap::{ArgAction, Parser}; use env_logger::Env; use std::path::PathBuf; use std::sync::Arc; use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; use tokio::sync::Notify; use tokio::time::{Duration, timeout}; use tokio_rustls::TlsAcceptor; use tokio_rustls::rustls::{self, ServerConfig}; const CONNECT_TIMEOUT: Duration = Duration::from_secs(30); const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(30); const IDLE_TIMEOUT: Duration = Duration::from_secs(600); /// Standalone Rust implementation of Signal's domain fronting TLS proxy #[derive(Parser)] #[command(version, override_usage = concat!(env!("CARGO_BIN_NAME"), " [OPTIONS]"))] struct Args { /// Increase log level (can be set multiple times) #[arg(short = 'v', long = "verbose", action(ArgAction::Count))] verbose: u8, /// Decrease log level (twice to fully turn off error logging too) #[arg(short = 'q', long = "quiet", action(ArgAction::Count))] quiet: u8, /// Address to bind to #[arg( short = 'B', long = "bind", default_value = "127.0.0.1:4443", env = "BIND_ADDR" )] bind: String, /// Allowed destination server names (replaces default signal.org allowlist, can be set multiple times) #[arg(short = 'A', long = "allow")] allow: Vec, /// Fallback destination if inner connection isn't TLS, or the SNI value is not on allowlist (:) #[arg(short = 'F', long = "fallback", env = "SIGNAL_TLSD_FALLBACK_ADDR")] fallback: Option, /// Do not expect an outer TLS layer, assume the outer TLS layer has already been terminated #[arg(short = 'N')] no_tls: bool, /// Path to TLS certificate for outer TLS layer (PEM format) #[arg(long = "cert", env = "TLS_CERT_PATH")] cert: Option, /// Path to TLS private key for outer TLS layer (PEM format) #[arg(long = "private-key", env = "TLS_PRIVATE_KEY_PATH")] private_key: Option, } async fn connect(addr: A) -> Result { timeout(CONNECT_TIMEOUT, TcpStream::connect(addr)) .await .context("connection timed out")? .map_err(Error::from) } async fn forward( mut reader: R, mut writer: W, notify: &Notify, ) -> io::Result<()> { let mut buffer = [0u8; 8192]; let ret = loop { match reader.read(&mut buffer).await { Ok(0) => break Ok(()), // EOF Ok(n) => { writer.write_all(&buffer[..n]).await?; writer.flush().await?; notify.notify_one(); } Err(err) => break Err(err), } }; writer.shutdown().await.ok(); ret } async fn forward_bidirectional( port: u16, client: C, upstream: U, ) -> io::Result<()> { let (client_read, client_write) = io::split(client); let (upstream_read, upstream_write) = io::split(upstream); let notify = Notify::new(); tokio::select! { // Forward client <-> upstream ret = async { let (client, upstream) = tokio::join!( forward(client_read, upstream_write, ¬ify), forward(upstream_read, client_write, ¬ify), ); client?; upstream?; Ok(()) } => ret, // Expire the connection if idle for too long _ = async { loop { if timeout(IDLE_TIMEOUT, notify.notified()).await.is_err() { debug!("X.X.X.X:{port}: Connection idle for too long, shutting down"); break; } } } => Ok(()), } } // Allow dynamic dispatch of TLS and non-TLS stream trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send {} impl AsyncReadWrite for T {} async fn accept( stream: S, port: u16, tls_config: Option>, rules: &Rules, ) { // If enabled, perform outer TLS handshake let stream: Box = if let Some(config) = tls_config { let acceptor = TlsAcceptor::from(config); match timeout(HANDSHAKE_TIMEOUT, acceptor.accept(stream)).await { Ok(Ok(stream)) => { debug!("X.X.X.X:{port}: Completed outer TLS handshake"); Box::new(stream) } Ok(Err(err)) => { debug!("X.X.X.X:{port}: Failed to accept outer TLS connection: {err:#}"); return; } Err(_) => { debug!("X.X.X.X:{port}: Outer TLS handshake timed out"); return; } } } else { Box::new(stream) }; // Read inner TLS client hello let acceptor = tokio_rustls::LazyConfigAcceptor::new( rustls::server::Acceptor::default(), ReadAhead::new(stream), ); tokio::pin!(acceptor); let (server_name, stream) = match timeout(HANDSHAKE_TIMEOUT, acceptor.as_mut()).await { Ok(Ok(start)) => { let client_hello = start.client_hello(); let Some(server_name) = client_hello.server_name() else { debug!("X.X.X.X:{port}: Received inner TLS client hello with no server name"); return; }; let server_name = server_name.to_string(); let stream = start.io; info!( "X.X.X.X:{port}: Received inner TLS client hello for server name: {server_name:?}" ); debug!( "X.X.X.X:{port}: Buffered {} bytes of inner TLS client hello", stream.buffered().len() ); if rules.allowed(&server_name) { (Some(server_name), stream) } else { debug!( "X.X.X.X:{port}: Rejecting connection request, destination not allowed: {server_name:?}" ); (None, stream) } } Ok(Err(err)) => { if err.kind() == io::ErrorKind::UnexpectedEof { debug!( "X.X.X.X:{port}: Connection closed before inner TLS client hello could be read" ); return; } debug!("X.X.X.X:{port}: Failed to read inner TLS client hello: {err:#}"); let Some(stream) = acceptor.take_io() else { return; }; (None, stream) } Err(_) => { debug!("X.X.X.X:{port}: Inner TLS handshake timed out"); return; } }; // Setup remote connection let remote = if let Some(server_name) = server_name { connect((server_name, 443)).await } else if let Some(fallback) = rules.fallback() { debug!("X.X.X.X:{port}: Falling back to configured fallback destination: {fallback:?}"); connect(fallback).await } else { return; }; let mut remote = match remote { Ok(stream) => stream, Err(err) => { warn!("X.X.X.X:{port}: Failed to connect to remote server: {err:#}"); return; } }; debug!("X.X.X.X:{port}: Connected to remote server"); let Ok(_) = remote.write_all(stream.buffered()).await else { warn!("X.X.X.X:{port}: Failed to forward buffered TLS client hello"); return; }; debug!("X.X.X.X:{port}: Flushed buffered data to remote server"); if let Err(err) = forward_bidirectional(port, &mut stream.into_inner(), &mut remote).await { if err.kind() == io::ErrorKind::UnexpectedEof { // This is harmless, don't log as warning debug!("X.X.X.X:{port}: Unclean TLS shutdown by peer"); } else { warn!("X.X.X.X:{port}: Error while forwarding connection: {err:#}"); trace!("X.X.X.X:{port}: Verbose error: {err:?}"); } } trace!("X.X.X.X:{port}: Finished data forwarding"); } async fn setup_outer_tls_config(args: &Args) -> Result { // Ensure necessary paths are configured let cert_file = args .cert .as_ref() .context("TLS certificate path must be provided when TLS is enabled")?; let private_key_file = args .private_key .as_ref() .context("TLS private key path must be provided when TLS is enabled")?; let tls_config = Tls::init(cert_file.clone(), private_key_file.clone()).await?; Ok(tls_config) } #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); let log_level = match (args.quiet, args.verbose) { (0, 0) => "signal_tlsd=info", (0, 1) => "info,signal_tlsd=debug", (0, 2) => "debug,signal_tlsd=trace", (0, _) => "trace", (1, _) => "signal_tlsd=warn", _ => "off", }; env_logger::init_from_env(Env::default().default_filter_or(log_level)); // Load TLS certificate and private key let tls_config = if !args.no_tls { Some(setup_outer_tls_config(&args).await?) } else { None }; // Setup forwarding rules let mut rules = if !args.allow.is_empty() { Rules::from_iter(args.allow) } else { Rules::from_iter(rules::SIGNAL_HOSTS.iter().copied()) }; rules.set_fallback(args.fallback); let rules = Arc::new(rules); tokio::select! { // The main daemon err = async { info!("Binding to address: {:?}", args.bind); let listener = TcpListener::bind(&args.bind) .await .with_context(|| format!("Failed to bind to address: {:?}", args.bind))?; info!("Listening for connections..."); loop { let (stream, addr) = match listener.accept().await { Ok(accept) => accept, Err(err) => { warn!("Failed to accept incoming connection: {err:#}"); continue; }, }; let port = addr.port(); let tls_config = tls_config.as_ref().map(Tls::rustls_config); let rules = rules.clone(); tokio::spawn(async move { debug!("X.X.X.X:{port}: Accepted new TCP connection"); accept(stream, port, tls_config, &rules).await; debug!("X.X.X.X:{port}: Connection has been closed"); }); } } => err, // SIGHUP for certificate reload _ = signals::sighup(tls_config.as_ref()) => Ok(()), // SIGTERM/SIGINT handling for pid1 compatibility _ = signals::sigterm() => Ok(()), } } signal-tlsd-0.1.1/src/readahead.rs000064400000000000000000000042151046102023000151010ustar 00000000000000use crate::errors::*; use std::pin::Pin; use std::task::Poll; use tokio::io::{self, AsyncRead, AsyncWrite, ReadBuf}; const BUF_SIZE: usize = 1 << 14; pub struct ReadAhead { stream: S, buf: [u8; BUF_SIZE], cursor: usize, } impl ReadAhead { pub fn new(stream: S) -> Self { Self { stream, buf: [0; BUF_SIZE], cursor: 0, } } pub fn buffered(&self) -> &[u8] { &self.buf[..self.cursor] } pub fn into_inner(self) -> S { self.stream } } impl AsyncRead for ReadAhead { fn poll_read( mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { let before = buf.filled().len(); if let Poll::Ready(x) = Pin::new(&mut self.stream).poll_read(cx, buf) { let buf = buf.filled(); let new = &buf[before..]; if !new.is_empty() { let cursor = self.cursor; let buffered = &mut self.buf[cursor..]; let Some(dest) = buffered.get_mut(..new.len()) else { return Poll::Ready(Err(io::Error::other("buffer full"))); }; dest.copy_from_slice(new); self.cursor += new.len(); } Poll::Ready(x) } else { Poll::Pending } } } impl AsyncWrite for ReadAhead { fn poll_write( self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>, buf: &[u8], ) -> Poll> { // Do not allow writing any TLS handshake errors trace!("Intercepted attempted write of {} bytes", buf.len()); Poll::Ready(Ok(buf.len())) } fn poll_flush( mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { Pin::new(&mut self.stream).poll_flush(cx) } fn poll_shutdown( mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { Pin::new(&mut self.stream).poll_shutdown(cx) } } signal-tlsd-0.1.1/src/rules.rs000064400000000000000000000103111046102023000143270ustar 00000000000000use std::collections::BTreeSet; // List taken from https://github.com/signalapp/Signal-TLS-Proxy/blob/main/data/nginx-relay/nginx.conf pub const SIGNAL_HOSTS: &[&str] = &[ "chat.signal.org", "storage.signal.org", "cdn.signal.org", "cdn2.signal.org", "cdn3.signal.org", "cdsi.signal.org", "contentproxy.signal.org", "grpc.chat.signal.org", "sfu.voip.signal.org", "svr2.signal.org", "svrb.signal.org", "updates.signal.org", "updates2.signal.org", ]; #[derive(Debug, PartialEq)] pub struct Rules { restricted_to: Option>, fallback: Option, } impl Rules { pub fn allowed(&self, server_name: &str) -> bool { if let Some(set) = &self.restricted_to { set.contains(server_name) } else { true } } pub fn set_fallback(&mut self, fallback: Option) { self.fallback = fallback; } pub fn fallback(&self) -> Option<&str> { self.fallback.as_deref() } } impl> FromIterator for Rules { fn from_iter>(iter: T) -> Self { let mut rules = Self { restricted_to: Some(Default::default()), fallback: None, }; for dest in iter { let dest = dest.into(); match dest.as_str() { // Lift any restrictions "*" => { rules.restricted_to = None; } // Counts as custom filter to prevent default allowlist, // but doesn't actually allow anything "-" => (), _ => { if let Some(set) = &mut rules.restricted_to { set.insert(dest); } } } } rules } } #[cfg(test)] mod tests { use super::*; #[test] fn rules_from_iter() { let rules = Rules::from_iter(["example.com", "example.org"]); assert_eq!( rules, Rules { restricted_to: Some( ["example.com".to_string(), "example.org".to_string()] .into_iter() .collect() ), fallback: None, } ); assert!(rules.allowed("example.com")); assert!(!rules.allowed("example.xyz")); } #[test] fn rules_from_iter_empty() { let rules = Rules::from_iter(Vec::::new()); assert_eq!( rules, Rules { restricted_to: Some(Default::default()), fallback: None, } ); assert!(!rules.allowed("example.com")); } #[test] fn rules_allow_all() { let rules = Rules::from_iter(["a", "*", "b"]); assert_eq!( rules, Rules { restricted_to: None, fallback: None, } ); assert!(rules.allowed("example.com")); assert!(rules.allowed("example.xyz")); } #[test] fn rules_disallow_all() { // Prevent the default set from being used, but don't actually // allow anything. This routes everything to the fallback, effectively // becoming a TLS offloader. let rules = Rules::from_iter(["-"]); assert_eq!( rules, Rules { restricted_to: Some(Default::default()), fallback: None, } ); assert!(!rules.allowed("example.com")); assert!(!rules.allowed("example.xyz")); } #[test] fn rules_edgecase_disallow_all_mixed_with_allow() { // This doesn't make much sense, but define this anyway let rules = Rules::from_iter(["example.com", "-", "example.org"]); assert_eq!( rules, Rules { restricted_to: Some( ["example.com".to_string(), "example.org".to_string()] .into_iter() .collect() ), fallback: None, } ); assert!(rules.allowed("example.com")); assert!(!rules.allowed("example.xyz")); } } signal-tlsd-0.1.1/src/signals.rs000064400000000000000000000024711046102023000146450ustar 00000000000000use crate::errors::*; use crate::tls::Tls; use std::future; use tokio::task::JoinSet; // Handle shutdown signals so we can run this as pid1 pub async fn sigterm() { let mut set = JoinSet::new(); // On ctrl-c, shutdown set.spawn(async { let _ = tokio::signal::ctrl_c().await; }); #[cfg(unix)] { // On SIGTERM, shutdown use tokio::signal::unix; if let Ok(mut signal) = unix::signal(unix::SignalKind::terminate()) { set.spawn(async move { signal.recv().await; }); } } set.join_next().await; } pub async fn sighup(tls: Option<&Tls>) { #[cfg(unix)] { use tokio::signal::unix; if let Ok(mut signals) = unix::signal(unix::SignalKind::hangup()) { while signals.recv().await.is_some() { if let Some(tls) = tls { info!("Received SIGHUP, reloading TLS certificate"); if let Err(err) = tls.reload().await { warn!("Failed to reload TLS certificate: {err:#}"); } else { debug!("TLS certificate reloaded successfully"); } } } } } // Reload signals not supported, wait indefinitely future::pending().await } signal-tlsd-0.1.1/src/tls.rs000064400000000000000000000040241046102023000140030ustar 00000000000000use crate::errors::*; use arc_swap::ArcSwap; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use tokio::fs; use tokio_rustls::rustls::ServerConfig; use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}; pub struct Tls { pub config: ArcSwap, pub cert_file: PathBuf, pub private_key_file: PathBuf, } impl Tls { pub async fn init(cert_file: PathBuf, private_key_file: PathBuf) -> Result { let config = load_from_disk(&cert_file, &private_key_file).await?; Ok(Self { config: ArcSwap::new(Arc::new(config)), cert_file, private_key_file, }) } pub fn rustls_config(&self) -> Arc { self.config.load_full() } pub async fn reload(&self) -> Result<()> { let new = load_from_disk(&self.cert_file, &self.private_key_file).await?; self.config.store(Arc::new(new)); Ok(()) } } pub async fn load_from_disk(cert_file: &Path, private_key_file: &Path) -> Result { // Read from disk let cert = fs::read(&cert_file) .await .with_context(|| format!("Failed to read TLS certificate file: {cert_file:?}"))?; let private_key = fs::read(&private_key_file) .await .with_context(|| format!("Failed to read TLS private key file: {private_key_file:?}"))?; // Parse file contents let certs = CertificateDer::pem_slice_iter(&cert) .map(|cert| cert.map_err(Error::from)) .collect::>>() .with_context(|| format!("Failed to parse TLS certificate from PEM file: {cert_file:?}"))?; let private_key = PrivateKeyDer::from_pem_slice(&private_key).with_context(|| { format!("Failed to parse TLS private key from PEM file: {private_key_file:?}") })?; // Finalize TLS config let config = ServerConfig::builder() .with_no_client_auth() .with_single_cert(certs, private_key) .context("Failed to create TLS server config")?; Ok(config) }