diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8a700862..a57d4bb8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -152,17 +152,13 @@ injects them during builds through `.github/steps/compute-versions` and ## Feature Flags and Platforms -- The `psk` feature enables TLS 1.3 pre-shared key support through OpenSSL in - `tacacsrs-networking` and propagates through `tacon`, `tacacsrs-agent`, and - `tacacsrs-agentd`. -- Default builds should remain pure Rust and avoid external OpenSSL requirements. +- Default builds use dynamically linked OpenSSL for certificate-based TLS and + TLS 1.3 pre-shared key support. - Library features must be additive: enabling a feature may add capability, but must not remove or change unrelated public API behavior. -- Use `#[cfg(feature = "psk")]` and platform `cfg`s narrowly around code that - truly needs them. -- Windows CI may build release artifacts with `psk`; Linux GNU release artifacts - build the executable Debian packages with `psk` and package - `tacacsrs-bash-plugin` without additional feature flags. +- Use platform `cfg`s narrowly around code that truly needs them. +- Windows CI packages OpenSSL runtime DLLs with release artifacts; Linux GNU + release artifacts dynamically link against the system OpenSSL packages. - `session-wrapper` is Linux x86_64-specific. On Windows, validate it through WSL with paths mapped under `/mnt//...`. diff --git a/.github/steps/generate-sbom/README.md b/.github/steps/generate-sbom/README.md index f1651b14..4ee69c1b 100644 --- a/.github/steps/generate-sbom/README.md +++ b/.github/steps/generate-sbom/README.md @@ -27,7 +27,6 @@ Generates SBOM files to comply with supply chain security requirements, includin describe: binaries target: x86_64-pc-windows-msvc target-in-filename: 'true' - features: psk ``` ## Inputs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3398724..72972eb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,10 +114,10 @@ jobs: container-registry: ghcr.io/authscaffold products: | [ - {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacon-linux-gnu-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": true}, - {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-agentd-linux-gnu-x86_64", "package_name": "tacacsrs-agentd", "package_root": "executables/tacacsrs_agentd", "artifact_name": "tacacsrs-agentd", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacacsrs-agentd", "debian_package": true}, + {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacon-linux-gnu-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": true}, + {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-agentd-linux-gnu-x86_64", "package_name": "tacacsrs-agentd", "package_root": "executables/tacacsrs_agentd", "artifact_name": "tacacsrs-agentd", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacacsrs-agentd", "debian_package": true}, {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-bash-plugin-linux-gnu-x86_64", "package_name": "tacacsrs-bash-plugin", "package_root": "libraries/tacacsrs_bash_plugin", "artifact_name": "tacacsrs_bash_plugin", "artifact_kind": "cdylib", "features": "", "sbom_describe": "all-cargo-targets", "man_page_name": "", "debian_package": true}, - {"os": "windows-latest", "target": "x86_64-pc-windows-msvc", "asset_name": "tacon-windows-msvc-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": false} + {"os": "windows-latest", "target": "x86_64-pc-windows-msvc", "asset_name": "tacon-windows-msvc-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": false} ] versions: ${{ needs.release-policy.outputs.skip-release == 'true' && '{}' || needs.compute-versions.outputs.versions || '{}' }} secrets: inherit diff --git a/.github/workflows/pullrequest_workflow.yml b/.github/workflows/pullrequest_workflow.yml index a1473125..81345847 100644 --- a/.github/workflows/pullrequest_workflow.yml +++ b/.github/workflows/pullrequest_workflow.yml @@ -47,10 +47,10 @@ jobs: skip-audit-if-no-dep-changes: true products: | [ - {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacon-linux-gnu-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": true}, - {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-agentd-linux-gnu-x86_64", "package_name": "tacacsrs-agentd", "package_root": "executables/tacacsrs_agentd", "artifact_name": "tacacsrs-agentd", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacacsrs-agentd", "debian_package": true}, + {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacon-linux-gnu-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": true}, + {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-agentd-linux-gnu-x86_64", "package_name": "tacacsrs-agentd", "package_root": "executables/tacacsrs_agentd", "artifact_name": "tacacsrs-agentd", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacacsrs-agentd", "debian_package": true}, {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-bash-plugin-linux-gnu-x86_64", "package_name": "tacacsrs-bash-plugin", "package_root": "libraries/tacacsrs_bash_plugin", "artifact_name": "tacacsrs_bash_plugin", "artifact_kind": "cdylib", "features": "", "sbom_describe": "all-cargo-targets", "man_page_name": "", "debian_package": true}, - {"os": "windows-latest", "target": "x86_64-pc-windows-msvc", "asset_name": "tacon-windows-msvc-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": false} + {"os": "windows-latest", "target": "x86_64-pc-windows-msvc", "asset_name": "tacon-windows-msvc-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": false} ] versions: ${{ needs.compute-versions.outputs.versions || '{}' }} secrets: inherit diff --git a/.github/workflows/reusable-pipeline.yml b/.github/workflows/reusable-pipeline.yml index 046e2042..e906677d 100644 --- a/.github/workflows/reusable-pipeline.yml +++ b/.github/workflows/reusable-pipeline.yml @@ -78,10 +78,10 @@ on: required: false default: | [ - {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacon-linux-gnu-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": true}, - {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-agentd-linux-gnu-x86_64", "package_name": "tacacsrs-agentd", "package_root": "executables/tacacsrs_agentd", "artifact_name": "tacacsrs-agentd", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacacsrs-agentd", "debian_package": true}, + {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacon-linux-gnu-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": true}, + {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-agentd-linux-gnu-x86_64", "package_name": "tacacsrs-agentd", "package_root": "executables/tacacsrs_agentd", "artifact_name": "tacacsrs-agentd", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacacsrs-agentd", "debian_package": true}, {"os": "ubuntu-latest", "target": "x86_64-unknown-linux-gnu", "asset_name": "tacacsrs-bash-plugin-linux-gnu-x86_64", "package_name": "tacacsrs-bash-plugin", "package_root": "libraries/tacacsrs_bash_plugin", "artifact_name": "tacacsrs_bash_plugin", "artifact_kind": "cdylib", "features": "", "sbom_describe": "all-cargo-targets", "man_page_name": "", "debian_package": true}, - {"os": "windows-latest", "target": "x86_64-pc-windows-msvc", "asset_name": "tacon-windows-msvc-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "psk", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": false} + {"os": "windows-latest", "target": "x86_64-pc-windows-msvc", "asset_name": "tacon-windows-msvc-x86_64", "package_name": "tacon", "package_root": "executables/tacon", "artifact_name": "tacon", "artifact_kind": "bin", "features": "", "sbom_describe": "binaries", "man_page_name": "tacon", "debian_package": false} ] type: string versions: @@ -369,7 +369,7 @@ jobs: target: ${{ matrix.config.target }} - name: Collect OpenSSL runtime DLLs - if: runner.os == 'Windows' && contains(matrix.config.features, 'psk') + if: runner.os == 'Windows' uses: ./.github/steps/collect-openssl-runtime - name: Package SBOMs diff --git a/Cargo.lock b/Cargo.lock index d898b4bf..631aba33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,28 +113,6 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" -[[package]] -name = "aws-lc-rs" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "axum" version = "0.8.9" @@ -233,8 +211,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -305,15 +281,6 @@ dependencies = [ "roff", ] -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - [[package]] name = "colorchoice" version = "1.0.5" @@ -485,12 +452,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "either" version = "1.16.0" @@ -621,12 +582,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 = "fsevent-sys" version = "4.1.0" @@ -724,29 +679,6 @@ dependencies = [ "slab", ] -[[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 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -755,7 +687,7 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", "rand_core", ] @@ -1106,16 +1038,6 @@ dependencies = [ "syn", ] -[[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 = "kqueue" version = "1.2.0" @@ -1698,12 +1620,6 @@ 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 = "r-efi" version = "6.0.0" @@ -1717,7 +1633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", - "getrandom 0.4.3", + "getrandom", "rand_core", ] @@ -1805,20 +1721,6 @@ dependencies = [ "thiserror", ] -[[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 = "roff" version = "1.1.1" @@ -1838,21 +1740,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - [[package]] name = "rustls-pki-types" version = "1.15.0" @@ -1862,18 +1749,6 @@ 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" @@ -2040,12 +1915,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[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.118" @@ -2268,14 +2137,11 @@ dependencies = [ "openssl-sys", "parking_lot", "rand", - "rustls-pki-types", "tacacsrs-config", "tacacsrs-flow-abstractions", "tacacsrs-messages", "tokio", "tokio-openssl", - "tokio-rustls", - "webpki-roots", ] [[package]] @@ -2323,7 +2189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2408,16 +2274,6 @@ dependencies = [ "tokio", ] -[[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 = "tokio-stream" version = "0.1.18" @@ -2643,12 +2499,6 @@ 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 = "url" version = "2.5.8" @@ -2710,24 +2560,6 @@ 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.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "winapi-util" version = "0.1.11" @@ -2743,22 +2575,13 @@ 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 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -2770,22 +2593,6 @@ 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 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - [[package]] name = "windows-targets" version = "0.53.5" @@ -2793,106 +2600,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[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_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" -[[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[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_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "windows_x86_64_msvc" version = "0.53.1" @@ -2908,12 +2667,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "writeable" version = "0.6.3" diff --git a/DEBIAN_PACKAGING.md b/DEBIAN_PACKAGING.md index e5fec27f..d4ad7ea1 100644 --- a/DEBIAN_PACKAGING.md +++ b/DEBIAN_PACKAGING.md @@ -11,12 +11,11 @@ The shared CI and release pipeline produces these Debian packages: | Package | Source crate | Install path | Notes | | --- | --- | --- | --- | -| `tacon` | `executables/tacon` | `/usr/bin/tacon` | Linux package builds enable the `psk` feature. | -| `tacacsrs-agentd` | `executables/tacacsrs_agentd` | `/usr/sbin/tacacsrs-agentd` | Linux package builds enable the `psk` feature. | +| `tacon` | `executables/tacon` | `/usr/bin/tacon` | Includes OpenSSL-backed TLS and TLS 1.3 PSK support. | +| `tacacsrs-agentd` | `executables/tacacsrs_agentd` | `/usr/sbin/tacacsrs-agentd` | Includes OpenSSL-backed TLS and TLS 1.3 PSK support. | | `tacacsrs-bash-plugin` | `libraries/tacacsrs_bash_plugin` | `/usr/lib/x86_64-linux-gnu/security/tacacsrs_bash_plugin.so` | SONiC bash execve plugin shared library package. | -Windows archives are produced separately and may enable PSK support when the -required OpenSSL toolchain is available. +Windows archives are produced separately and include the required OpenSSL runtime DLLs. ## Prerequisites @@ -37,8 +36,8 @@ WSL or another Linux environment so the outputs are GNU/Linux artifacts. Build the Linux release artifacts before invoking `cargo deb --no-build`: ```bash -cargo build --release --target x86_64-unknown-linux-gnu -p tacon --features psk -cargo build --release --target x86_64-unknown-linux-gnu -p tacacsrs-agentd --features psk +cargo build --release --target x86_64-unknown-linux-gnu -p tacon +cargo build --release --target x86_64-unknown-linux-gnu -p tacacsrs-agentd cargo build --release --target x86_64-unknown-linux-gnu -p tacacsrs-bash-plugin ``` @@ -130,7 +129,7 @@ The bash plugin package should contain: ### Missing OpenSSL headers while building Linux executable packages Install `pkg-config` and `libssl-dev`, then rebuild `tacon` and -`tacacsrs-agentd` with `--features psk`. +`tacacsrs-agentd`. ### Missing generated man page during packaging diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 30410f13..edb68bf3 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -74,9 +74,9 @@ cargo build --workspace cargo test --workspace ``` -### Building with TLS 1.3 PSK Support (Optional) +### Building with OpenSSL-backed TLS -The `tacacsrs_networking` library includes optional TLS 1.3 Pre-Shared Key support behind the `psk` feature flag. This feature depends on OpenSSL and requires additional setup. +The `tacacsrs_networking` library uses dynamically linked OpenSSL for certificate-based TLS. Optional TLS 1.3 Pre-Shared Key support remains behind the `psk` feature flag and uses additional OpenSSL APIs. #### Linux @@ -89,8 +89,8 @@ sudo apt-get install libssl-dev pkg-config # Fedora/RHEL sudo dnf install openssl-devel -# Build with PSK support -cargo build --workspace --features tacacsrs-networking/psk +# Build with OpenSSL-backed TLS and TLS 1.3 PSK support +cargo build --workspace ``` #### Windows @@ -114,10 +114,10 @@ A pre-built OpenSSL installation is required. The recommended approach is to use Adjust the path to match your vcpkg installation location. The directory must contain `include/openssl` and `lib` subdirectories. -4. Build with PSK support: +4. Build: ```powershell - cargo build --features tacacsrs-networking/psk + cargo build --workspace ``` Set `OPENSSL_DIR` permanently via **System Properties → Environment Variables** so it persists across terminals. @@ -137,11 +137,9 @@ If you prefer not to use vcpkg, you can point to any pre-built OpenSSL installat $env:OPENSSL_DIR = "C:\development\tools\openssl" $env:OPENSSL_LIB_DIR = "C:\development\tools\openssl\lib\VC\x64\MD" -cargo build --features tacacsrs-networking/psk +cargo build --workspace ``` -> **Tip:** Without the `psk` feature, the default build uses rustls (pure Rust) and requires no external dependencies. - ## Running Tests ```bash diff --git a/containers/tacacsrs-agentd/Dockerfile b/containers/tacacsrs-agentd/Dockerfile index 7cf958c6..9f50b631 100644 --- a/containers/tacacsrs-agentd/Dockerfile +++ b/containers/tacacsrs-agentd/Dockerfile @@ -29,9 +29,10 @@ ARG RUNTIME_IMAGE=docker.io/library/debian:bookworm-slim # ----------------------------------------------------------------------------- FROM ${BUILDER_IMAGE} AS base -# `pkg-config` and `libssl-dev` are required to build the `psk` feature, which -# links against the system OpenSSL. `protoc` is provided by the vendored -# `protoc-bin-vendored` crate, so it does not need to be installed here. +# `pkg-config` and `libssl-dev` are required because `tacacsrs-networking` +# links against the system OpenSSL for TLS and PSK support. `protoc` is +# provided by the vendored `protoc-bin-vendored` crate, so it does not need +# to be installed here. RUN apt-get update \ && apt-get install -y --no-install-recommends \ pkg-config \ @@ -57,7 +58,7 @@ COPY lde/containers/config/certificates/client.* ./libraries/tacacsrs_networking # test: run the agent daemon test suite # ----------------------------------------------------------------------------- FROM base AS test -ARG FEATURES=psk +ARG FEATURES= # CACHEBUST lets `make test` force a fresh test run even when the source layers # are cached (pass --build-arg CACHEBUST=$(date +%s)). ARG CACHEBUST=0 @@ -71,7 +72,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ # ----------------------------------------------------------------------------- FROM base AS build ARG PROFILE=release -ARG FEATURES=psk +ARG FEATURES= # The cache mount speeds up rebuilds but is not part of the image layer, so the # freshly built binary is copied out of the cached target dir within the same # RUN before the mount is unmounted. @@ -90,8 +91,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ # ----------------------------------------------------------------------------- FROM ${RUNTIME_IMAGE} AS runtime -# `libssl3` satisfies the OpenSSL runtime for the `psk` feature and -# `ca-certificates` provides the trust store for certificate-based TLS. +# `libssl3` satisfies the OpenSSL runtime dependency for TLS and PSK support, +# and `ca-certificates` provides the trust store for certificate-based TLS. RUN apt-get update \ && apt-get install -y --no-install-recommends \ libssl3 \ diff --git a/docs/sonic-build-guide.md b/docs/sonic-build-guide.md index e46293d7..3fa9ae5b 100644 --- a/docs/sonic-build-guide.md +++ b/docs/sonic-build-guide.md @@ -18,8 +18,8 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \ Build the SONiC-relevant artifacts from a Linux environment with GNU targets: ```bash -cargo build --release --target x86_64-unknown-linux-gnu -p tacon --features psk -cargo build --release --target x86_64-unknown-linux-gnu -p tacacsrs-agentd --features psk +cargo build --release --target x86_64-unknown-linux-gnu -p tacon +cargo build --release --target x86_64-unknown-linux-gnu -p tacacsrs-agentd cargo build --release --target x86_64-unknown-linux-gnu -p tacacsrs-bash-plugin ``` diff --git a/executables/tacacsrs_agentd/Cargo.toml b/executables/tacacsrs_agentd/Cargo.toml index bf2c97b7..e78a7420 100644 --- a/executables/tacacsrs_agentd/Cargo.toml +++ b/executables/tacacsrs_agentd/Cargo.toml @@ -26,7 +26,6 @@ tokio = { version = "1.52.3", features = ["full"] } [features] default = [] console = ["console-subscriber"] -psk = ["tacacsrs-agent/psk", "tacacsrs-cli-datastore/psk"] [build-dependencies] clap = { version = "4.6.1", features = ["derive"] } @@ -62,7 +61,3 @@ assets = [ ["debian/changelog.gz", "usr/share/doc/tacacsrs-agentd/changelog.Debian.gz", "644"], ["debian/tacacsrs-agentd.1.gz", "usr/share/man/man1/tacacsrs-agentd.1.gz", "644"], ] - -[package.metadata.deb.variants.psk] -features = ["psk"] -depends = "$auto" diff --git a/executables/tacacsrs_agentd/src/cli.rs b/executables/tacacsrs_agentd/src/cli.rs index b72260b0..61b33058 100644 --- a/executables/tacacsrs_agentd/src/cli.rs +++ b/executables/tacacsrs_agentd/src/cli.rs @@ -1,9 +1,7 @@ use std::path::PathBuf; use clap::{ArgGroup, Parser, ValueEnum}; -#[cfg(feature = "psk")] use clap::builder::TypedValueParser as _; -#[cfg(feature = "psk")] use tacacsrs_config::PskDheKeSupportedGroup; #[derive(Debug, Clone, Copy, Eq, PartialEq, ValueEnum)] @@ -18,7 +16,6 @@ pub(crate) enum ServiceMode { Both, } -#[cfg(feature = "psk")] #[derive(Debug, Clone, Copy, Eq, PartialEq, ValueEnum)] pub(crate) enum PskKeyExchange { /// Use TLS 1.3 PSK with ephemeral (EC)DHE key exchange. @@ -30,7 +27,6 @@ pub(crate) enum PskKeyExchange { PskOnly, } -#[cfg(feature = "psk")] fn psk_dhe_ke_supported_group_parser( ) -> impl clap::builder::TypedValueParser { clap::builder::PossibleValuesParser::new(PskDheKeSupportedGroup::ALLOWED_VALUES.iter().copied()) @@ -133,21 +129,17 @@ pub(crate) struct Cli { #[arg(short, long, action = clap::ArgAction::Count)] pub(crate) verbose: u8, - #[cfg(feature = "psk")] #[arg(long, value_name = "IDENTITY", requires_all = ["use_tls", "psk_key"], conflicts_with_all = ["client_certificate", "client_key"])] pub(crate) psk_identity: Option, - #[cfg(feature = "psk")] #[arg(long, value_name = "KEY", requires_all = ["use_tls", "psk_identity"], conflicts_with_all = ["client_certificate", "client_key"])] pub(crate) psk_key: Option, /// TLS 1.3 PSK key-exchange mode. - #[cfg(feature = "psk")] #[arg(long, value_enum, requires_all = ["use_tls", "psk_identity", "psk_key"], conflicts_with_all = ["client_certificate", "client_key"])] pub(crate) psk_key_exchange: Option, /// Comma-separated TLS 1.3 PSK-DHE groups in preferred order. - #[cfg(feature = "psk")] #[arg( long, value_delimiter = ',', diff --git a/executables/tacacsrs_agentd/src/main.rs b/executables/tacacsrs_agentd/src/main.rs index e624fc56..e7cf134a 100644 --- a/executables/tacacsrs_agentd/src/main.rs +++ b/executables/tacacsrs_agentd/src/main.rs @@ -14,7 +14,6 @@ use tacacsrs_cli_datastore::{ CliConfigSource, CliDatastoreInput, CliFileDatastore, CliSecurity, CliSecurityInputs, CliServerInput, }; -#[cfg(feature = "psk")] use tacacsrs_cli_datastore::{CliPskInputs, PskKeyExchangeMode, PskKeyMaterial}; use tacacsrs_config::TacacsPlusServerExt; use tacacsrs_datastore::{ConfigChange, ConfigDatastore}; @@ -24,7 +23,6 @@ mod cli; mod systemd_notify; use crate::cli::{Cli, ServiceMode}; -#[cfg(feature = "psk")] use crate::cli::PskKeyExchange; use crate::systemd_notify::SystemdNotifier; @@ -104,12 +102,10 @@ fn cli_security_from_cli(cli: &Cli) -> CliSecurity { shared_secret: cli.shared_secret.clone(), client_certificate: cli.client_certificate.clone().map(PathBuf::from), client_key: cli.client_key.clone().map(PathBuf::from), - #[cfg(feature = "psk")] psk: cli_psk_inputs(cli), }) } -#[cfg(feature = "psk")] fn cli_psk_inputs(cli: &Cli) -> Option { let identity = cli.psk_identity.as_ref()?; let key = cli.psk_key.as_ref()?; @@ -330,7 +326,6 @@ mod tests { use super::{Cli, apply_config_change, cli_datastore_input_from_cli, enabled_services_from_cli}; use tacacsrs_agent::{EnabledServices, ServiceConfig, TacacsClientService}; use tacacsrs_agent_client::IpcEndpoint; - #[cfg(feature = "psk")] use tacacsrs_config::PskDheKeSupportedGroup; use tacacsrs_config::crypto_types::PrivateKeyFormat; use tacacsrs_config::{ @@ -573,7 +568,6 @@ mod tests { assert_eq!(inline.private_key_format, Some(PrivateKeyFormat::OneAsymmetricKeyFormat)); } - #[cfg(feature = "psk")] fn tls13_epsk_groups(cli: &Cli) -> Vec { let mut root = tacacs_plus_from_cli_input(&cli_datastore_input_from_cli(cli)) .expect("PSK config should build"); @@ -586,7 +580,6 @@ mod tests { .psk_dhe_ke_groups } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_defaults_psk_to_dhe_groups() { let cli = Cli::parse_from([ @@ -606,7 +599,6 @@ mod tests { assert!(matches!(groups.get(1), Some(PskDheKeSupportedGroup::Secp256r1))); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_allows_psk_only_mode() { let cli = Cli::parse_from([ @@ -625,7 +617,6 @@ mod tests { assert!(tls13_epsk_groups(&cli).is_empty()); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_uses_custom_psk_dhe_groups() { let cli = Cli::parse_from([ @@ -647,7 +638,6 @@ mod tests { assert!(matches!(groups.get(1), Some(PskDheKeSupportedGroup::X25519))); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_rejects_psk_only_with_groups() { let cli = Cli::parse_from([ diff --git a/executables/tacon/Cargo.toml b/executables/tacon/Cargo.toml index e3b8331d..29c0f50f 100644 --- a/executables/tacon/Cargo.toml +++ b/executables/tacon/Cargo.toml @@ -26,10 +26,6 @@ tacacsrs-flows = { path = "../../libraries/tacacsrs_flows" } tacacsrs-networking = { path = "../../libraries/tacacsrs_networking" } tokio = { version = "1.52.3", features = ["full"] } -[features] -default = [] -psk = ["tacacsrs-cli-datastore/psk", "tacacsrs-networking/psk"] - [build-dependencies] clap = { version = "4.6.1", features = ["derive"] } clap_mangen = "0.3.0" @@ -59,7 +55,3 @@ assets = [ ["debian/changelog.gz", "usr/share/doc/tacon/changelog.Debian.gz", "644"], ["debian/tacon.1.gz", "usr/share/man/man1/tacon.1.gz", "644"], ] - -[package.metadata.deb.variants.psk] -features = ["psk"] -depends = "$auto" diff --git a/executables/tacon/src/cli.rs b/executables/tacon/src/cli.rs index 0c366739..304b464c 100644 --- a/executables/tacon/src/cli.rs +++ b/executables/tacon/src/cli.rs @@ -1,7 +1,5 @@ use clap::{ArgGroup, Parser, Subcommand, ValueEnum}; -#[cfg(feature = "psk")] use clap::builder::TypedValueParser as _; -#[cfg(feature = "psk")] use tacacsrs_config::PskDheKeSupportedGroup; /// Validation relaxation that loosens a specific YANG constraint. @@ -22,7 +20,6 @@ pub enum ValidationRelaxation { } /// TLS 1.3 PSK key-exchange behavior. -#[cfg(feature = "psk")] #[derive(Debug, Clone, Copy, Eq, PartialEq, ValueEnum)] pub enum PskKeyExchange { /// Use TLS 1.3 PSK with ephemeral (EC)DHE key exchange. @@ -34,7 +31,6 @@ pub enum PskKeyExchange { PskOnly, } -#[cfg(feature = "psk")] fn psk_dhe_ke_supported_group_parser( ) -> impl clap::builder::TypedValueParser { clap::builder::PossibleValuesParser::new(PskDheKeSupportedGroup::ALLOWED_VALUES.iter().copied()) @@ -106,22 +102,18 @@ pub struct Cli { pub insecure_disable_certificate_verification: bool, /// PSK identity string sent to the server during the TLS 1.3 handshake - #[cfg(feature = "psk")] #[arg(long, value_name = "IDENTITY", requires_all = ["use_tls", "psk_key"], conflicts_with_all = ["client_certificate", "client_key", "service_endpoint"])] pub psk_identity: Option, /// Base64-encoded pre-shared key for TLS 1.3 PSK authentication - #[cfg(feature = "psk")] #[arg(long, value_name = "KEY", requires_all = ["use_tls", "psk_identity"], conflicts_with_all = ["client_certificate", "client_key", "service_endpoint"])] pub psk_key: Option, /// TLS 1.3 PSK key-exchange mode. - #[cfg(feature = "psk")] #[arg(long, value_enum, requires_all = ["use_tls", "psk_identity", "psk_key"], conflicts_with_all = ["client_certificate", "client_key", "service_endpoint"])] pub psk_key_exchange: Option, /// Comma-separated TLS 1.3 PSK-DHE groups in preferred order. - #[cfg(feature = "psk")] #[arg( long, value_delimiter = ',', @@ -327,7 +319,6 @@ mod tests { assert!(result.is_err()); } - #[cfg(feature = "psk")] #[test] fn test_psk_key_exchange_mode_parses() { let result = Cli::try_parse_from([ @@ -349,7 +340,6 @@ mod tests { assert_eq!(result.unwrap().psk_key_exchange, Some(PskKeyExchange::PskOnly)); } - #[cfg(feature = "psk")] #[test] fn test_psk_key_exchange_groups_parse_comma_separated_values() { let result = Cli::try_parse_from([ @@ -379,7 +369,6 @@ mod tests { )); } - #[cfg(feature = "psk")] #[test] fn test_psk_key_exchange_groups_reject_unknown_group() { let result = Cli::try_parse_from([ diff --git a/executables/tacon/src/config.rs b/executables/tacon/src/config.rs index fc536e49..20386e62 100644 --- a/executables/tacon/src/config.rs +++ b/executables/tacon/src/config.rs @@ -5,7 +5,6 @@ use tacacsrs_cli_datastore::{ CliConfigSource, CliDatastoreInput, CliSecurity, CliSecurityInputs, CliServerInput, tacacs_plus_from_cli_input, tacacs_plus_from_file as shared_tacacs_plus_from_file, }; -#[cfg(feature = "psk")] use tacacsrs_cli_datastore::{CliPskInputs, PskKeyExchangeMode, PskKeyMaterial}; use tacacsrs_config::{ TacacsPlus, TacacsPlusServer, TacacsPlusServerExt, TacacsPlusServerType, ValidationOptions, @@ -13,7 +12,6 @@ use tacacsrs_config::{ }; use crate::cli::{Cli, Command}; -#[cfg(feature = "psk")] use crate::cli::PskKeyExchange; /// Builds a single-server [`TacacsPlus`] root from CLI flags for direct-mode connections. @@ -75,12 +73,10 @@ fn cli_security_from_cli(cli: &Cli) -> CliSecurity { shared_secret: cli.shared_secret.clone(), client_certificate: cli.client_certificate.clone().map(PathBuf::from), client_key: cli.client_key.clone().map(PathBuf::from), - #[cfg(feature = "psk")] psk: cli_psk_inputs(cli), }) } -#[cfg(feature = "psk")] fn cli_psk_inputs(cli: &Cli) -> Option { let identity = cli.psk_identity.as_ref()?; let key = cli.psk_key.as_ref()?; @@ -221,7 +217,6 @@ mod tests { use super::{render_yang_config, select_first_server_for_type, tacacs_plus_from_cli}; use crate::cli::Cli; - #[cfg(feature = "psk")] use tacacsrs_config::PskDheKeSupportedGroup; use tacacsrs_config::{TacacsPlusServerType, ValidationOptions, crypto_types::PrivateKeyFormat}; use tacacsrs_cli_datastore::tacacs_plus_from_str; @@ -567,7 +562,6 @@ mod tests { ); } - #[cfg(feature = "psk")] fn tls13_epsk_groups(cli: &Cli) -> Vec { let mut root = tacacs_plus_from_cli(cli).expect("PSK config should build"); root.server @@ -579,7 +573,6 @@ mod tests { .psk_dhe_ke_groups } - #[cfg(feature = "psk")] fn tls13_epsk_key(cli: &Cli) -> Vec { let mut root = tacacs_plus_from_cli(cli).expect("PSK config should build"); root.server @@ -594,7 +587,6 @@ mod tests { .expect("symmetric key") } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_defaults_psk_to_dhe_groups() { let cli = Cli::parse_from([ @@ -622,7 +614,6 @@ mod tests { assert!(matches!(groups.get(1), Some(PskDheKeSupportedGroup::Secp256r1))); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_decodes_base64_psk_key() { let cli = Cli::parse_from([ @@ -647,7 +638,6 @@ mod tests { assert_eq!(tls13_epsk_key(&cli), b"secret"); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_rejects_invalid_base64_psk_key() { let cli = Cli::parse_from([ @@ -676,7 +666,6 @@ mod tests { .contains("--psk-key must be standard base64-encoded PSK bytes")); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_allows_psk_only_mode() { let cli = Cli::parse_from([ @@ -703,7 +692,6 @@ mod tests { assert!(tls13_epsk_groups(&cli).is_empty()); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_uses_custom_psk_dhe_groups() { let cli = Cli::parse_from([ @@ -733,7 +721,6 @@ mod tests { assert!(matches!(groups.get(1), Some(PskDheKeSupportedGroup::X25519))); } - #[cfg(feature = "psk")] #[test] fn tacacs_plus_from_cli_rejects_psk_only_with_groups() { let cli = Cli::parse_from([ diff --git a/libraries/tacacsrs_agent/Cargo.toml b/libraries/tacacsrs_agent/Cargo.toml index 6f717db7..cb106da3 100644 --- a/libraries/tacacsrs_agent/Cargo.toml +++ b/libraries/tacacsrs_agent/Cargo.toml @@ -22,9 +22,5 @@ tokio = { version = "1.52.3", features = ["full"] } tokio-stream = { version = "0.1.18", features = ["net"] } tonic = "0.14.6" -[features] -default = [] -psk = ["tacacsrs-networking/psk"] - [lints] workspace = true diff --git a/libraries/tacacsrs_cli_datastore/Cargo.toml b/libraries/tacacsrs_cli_datastore/Cargo.toml index 9ffd4723..0a04e6a9 100644 --- a/libraries/tacacsrs_cli_datastore/Cargo.toml +++ b/libraries/tacacsrs_cli_datastore/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true [dependencies] anyhow = "1" async-trait = "0.1" -base64 = { version = "0.22", optional = true } +base64 = "0.22" futures-core = "0.3" futures-util = "0.3" log = "0.4" @@ -20,10 +20,6 @@ tacacsrs-datastore = { path = "../tacacsrs_datastore" } tokio = { version = "1.52.3", features = ["macros", "rt", "sync", "time"] } tokio-stream = "0.1" -[features] -default = [] -psk = ["dep:base64"] - [dev-dependencies] tokio = { version = "1.52.3", features = ["full"] } diff --git a/libraries/tacacsrs_cli_datastore/src/builder.rs b/libraries/tacacsrs_cli_datastore/src/builder.rs index 5e449fcb..0275297a 100644 --- a/libraries/tacacsrs_cli_datastore/src/builder.rs +++ b/libraries/tacacsrs_cli_datastore/src/builder.rs @@ -9,10 +9,8 @@ use crate::address::parse_host_port; use crate::files::{load_client_certificate, load_client_private_key}; use crate::model::{ CertKeyIdentity, CliConfigSource, CliDatastoreInput, CliSecurity, CliSecurityMode, - CliServerInput, + CliServerInput, PskKeyExchangeMode, PskKeyMaterial, }; -#[cfg(feature = "psk")] -use crate::model::{PskKeyExchangeMode, PskKeyMaterial}; /// Loads a [`TacacsPlus`] root from a YANG JSON string with the supplied validation options. /// @@ -99,7 +97,6 @@ fn server_from_cli_input( let tls_builder = tls_client_certificate_builder(builder, identity)?; tls_builder_with_optional_shared_secret(tls_builder, shared_secret, options).build() } - #[cfg(feature = "psk")] CliSecurityMode::TlsPsk { identity, key, @@ -149,7 +146,6 @@ fn tls_client_certificate_builder( )) } -#[cfg(feature = "psk")] fn psk_builder( builder: TacacsPlusServerBuilder, identity: String, diff --git a/libraries/tacacsrs_cli_datastore/src/lib.rs b/libraries/tacacsrs_cli_datastore/src/lib.rs index 52f53567..44011998 100644 --- a/libraries/tacacsrs_cli_datastore/src/lib.rs +++ b/libraries/tacacsrs_cli_datastore/src/lib.rs @@ -18,5 +18,4 @@ pub use model::{ CertKeyIdentity, CliConfigSource, CliDatastoreInput, CliSecurity, CliSecurityInputs, CliSecurityMode, CliServerInput, PskKeyExchangeMode, PskKeyMaterial, TlsServerName, }; -#[cfg(feature = "psk")] pub use model::CliPskInputs; diff --git a/libraries/tacacsrs_cli_datastore/src/model.rs b/libraries/tacacsrs_cli_datastore/src/model.rs index ccca9f4f..bacc2c74 100644 --- a/libraries/tacacsrs_cli_datastore/src/model.rs +++ b/libraries/tacacsrs_cli_datastore/src/model.rs @@ -1,11 +1,8 @@ use std::path::PathBuf; use std::time::Duration; -#[cfg(feature = "psk")] use anyhow::Context; -#[cfg(feature = "psk")] use base64::Engine as _; -#[cfg(feature = "psk")] use tacacsrs_config::PskDheKeSupportedGroup; use tacacsrs_config::{TacacsPlusServerType, ValidationOptions}; @@ -151,7 +148,6 @@ impl CliSecurity { }; } - #[cfg(feature = "psk")] if let Some(psk) = inputs.psk { return Self { mode: CliSecurityMode::TlsPsk { @@ -163,7 +159,6 @@ impl CliSecurity { shared_secret, }; } - let mode = match (inputs.client_certificate, inputs.client_key) { (Some(certificate_path), Some(private_key_path)) => { CliSecurityMode::TlsClientCertificate { @@ -196,7 +191,6 @@ pub enum CliSecurityMode { /// TLS with a client certificate identity read from disk. TlsClientCertificate { identity: CertKeyIdentity }, /// TLS 1.3 with an externally provisioned pre-shared key. - #[cfg(feature = "psk")] TlsPsk { identity: String, key: PskKeyMaterial, @@ -216,12 +210,10 @@ pub struct CliSecurityInputs { pub shared_secret: Option, pub client_certificate: Option, pub client_key: Option, - #[cfg(feature = "psk")] pub psk: Option, } /// TLS 1.3 PSK inputs collected from an executable's parsed CLI. -#[cfg(feature = "psk")] #[derive(Debug, Clone)] pub struct CliPskInputs { pub identity: String, @@ -265,7 +257,6 @@ pub enum PskKeyMaterial { } impl PskKeyMaterial { - #[cfg(feature = "psk")] pub(crate) fn into_bytes(self) -> anyhow::Result> { match self { Self::Raw(bytes) => Ok(bytes), diff --git a/libraries/tacacsrs_networking/Cargo.toml b/libraries/tacacsrs_networking/Cargo.toml index 1aee73ea..63334c41 100644 --- a/libraries/tacacsrs_networking/Cargo.toml +++ b/libraries/tacacsrs_networking/Cargo.toml @@ -16,17 +16,10 @@ async-trait = "0.1.89" rand = "0.10.1" log = "0.4.30" parking_lot = "0.12.5" -tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws_lc_rs", "logging"] } -rustls-pki-types = "1.14.1" -webpki-roots = "1.0.7" -openssl = { version = "0.10.80", optional = true } -openssl-sys = { version = "0.9.116", optional = true } -tokio-openssl = { version = "0.6.5", optional = true } -foreign-types = { version = "0.3.2", optional = true } - -[features] -default = [] -psk = ["dep:foreign-types", "dep:openssl", "dep:openssl-sys", "dep:tokio-openssl"] +openssl = "0.10.80" +openssl-sys = "0.9.116" +tokio-openssl = "0.6.5" +foreign-types = "0.3.2" [dev-dependencies] console-subscriber = "0.5.0" @@ -41,4 +34,3 @@ development = ["console-subscriber"] [[example]] name = "tls_psk_client" -required-features = ["psk"] diff --git a/libraries/tacacsrs_networking/src/establish/mod.rs b/libraries/tacacsrs_networking/src/establish/mod.rs index c8209324..cb873274 100644 --- a/libraries/tacacsrs_networking/src/establish/mod.rs +++ b/libraries/tacacsrs_networking/src/establish/mod.rs @@ -127,7 +127,6 @@ pub(crate) async fn establish_stream( // TLS-PSK must be checked before general TLS because `is_tls()` also // returns true when only PSK material is configured. - #[cfg(feature = "psk")] if crate::transport::tls_psk::server_has_psk(server) { let stream = crate::transport::tls_psk::establish_from_server(server, &address, tcp_stream).await?; diff --git a/libraries/tacacsrs_networking/src/helpers.rs b/libraries/tacacsrs_networking/src/helpers.rs index c10a9f8d..869821e2 100644 --- a/libraries/tacacsrs_networking/src/helpers.rs +++ b/libraries/tacacsrs_networking/src/helpers.rs @@ -4,8 +4,6 @@ use std::net::{SocketAddr, ToSocketAddrs}; -use tokio_rustls::rustls; - /// Resolves a hostname (with optional port) to a list of socket addresses. /// /// If no port is specified, the default TACACS+ port (49) is used. @@ -87,15 +85,6 @@ pub(crate) fn tls_server_name(server_addr: &str) -> &str { server_addr } -/// Returns the default web PKI root certificate store used for outbound TLS -/// client verification. -#[must_use] -pub(crate) fn default_root_cert_store() -> rustls::RootCertStore { - rustls::RootCertStore { - roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), - } -} - /// Returns whether `data` contains a PEM block marker. #[must_use] pub(crate) fn data_contains_pem_header(data: &[u8]) -> bool { @@ -107,7 +96,7 @@ pub(crate) fn data_contains_pem_header(data: &[u8]) -> bool { #[cfg(test)] mod tests { - use super::{data_contains_pem_header, default_root_cert_store, tls_server_name}; + use super::{data_contains_pem_header, tls_server_name}; #[test] fn test_tls_server_name_plain_hostname() { @@ -157,12 +146,6 @@ mod tests { assert_eq!(tls_server_name("host:notaport"), "host:notaport"); } - #[test] - fn test_default_root_cert_store_is_not_empty() { - let root_store = default_root_cert_store(); - assert!(!root_store.is_empty()); - } - #[test] fn test_data_contains_pem_header_accepts_plain_pem() { assert!(data_contains_pem_header(b"-----BEGIN CERTIFICATE-----\n...")); diff --git a/libraries/tacacsrs_networking/src/transport/abstractions/mod.rs b/libraries/tacacsrs_networking/src/transport/abstractions/mod.rs index 20a6c2ad..44eb8a98 100644 --- a/libraries/tacacsrs_networking/src/transport/abstractions/mod.rs +++ b/libraries/tacacsrs_networking/src/transport/abstractions/mod.rs @@ -28,7 +28,7 @@ pub(crate) trait Transport: Send + 'static { mod tests { use super::*; use tokio::net::TcpStream; - use tokio_rustls::client::TlsStream; + use tokio_openssl::SslStream; // Compile-time check that TcpStream implements Transport #[allow(dead_code)] @@ -41,7 +41,7 @@ mod tests { // Compile-time check that TlsStream implements Transport #[allow(dead_code)] - fn check_tls(s: TlsStream) { + fn check_tls(s: SslStream) { assert_transport(s); } @@ -50,11 +50,4 @@ mod tests { fn check_mock(s: crate::transport::mock::MockTransport) { assert_transport(s); } - - // Compile-time check that OpenSSL-backed SslStream implements Transport - #[cfg(feature = "psk")] - #[allow(dead_code)] - fn check_openssl(s: tokio_openssl::SslStream) { - assert_transport(s); - } } diff --git a/libraries/tacacsrs_networking/src/transport/mod.rs b/libraries/tacacsrs_networking/src/transport/mod.rs index 58446e52..6f4f1c68 100644 --- a/libraries/tacacsrs_networking/src/transport/mod.rs +++ b/libraries/tacacsrs_networking/src/transport/mod.rs @@ -5,7 +5,7 @@ //! - [`tcp`] for plain TCP transport support //! - `tls` for TLS transport support (crate-internal; constructed via //! [`crate::establish::establish_stream`]) -//! - `tls_psk` for TLS-PSK transport support (feature-gated, crate-internal; +//! - `tls_psk` for TLS-PSK transport support (crate-internal; //! constructed via [`crate::establish::establish_stream`]) pub(crate) mod abstractions; @@ -14,7 +14,6 @@ pub(crate) mod boxed; pub(crate) mod mock; pub(crate) mod tcp; pub(crate) mod tls; -#[cfg(feature = "psk")] pub(crate) mod tls_psk; pub(crate) use abstractions::Transport; diff --git a/libraries/tacacsrs_networking/src/transport/tls/config_builder.rs b/libraries/tacacsrs_networking/src/transport/tls/config_builder.rs index 51115aa6..c2b89c69 100644 --- a/libraries/tacacsrs_networking/src/transport/tls/config_builder.rs +++ b/libraries/tacacsrs_networking/src/transport/tls/config_builder.rs @@ -1,31 +1,28 @@ -use std::sync::Arc; - -use rustls_pki_types::{CertificateDer, PrivateKeyDer}; -use tokio_rustls::rustls; - -use super::danger::NoCertificateVerification; +use openssl::pkey::{PKey, Private}; +use openssl::ssl::{SslContext, SslMethod, SslVerifyMode, SslVersion}; +use openssl::x509::X509; /// Internal builder used by [`super::establish_from_server`] to assemble a -/// [`rustls::ClientConfig`] from configuration material that has already been -/// extracted from a [`tacacsrs_config::TacacsPlusServer`]. +/// [`SslContext`] from configuration material that has already been extracted +/// from a [`tacacsrs_config::TacacsPlusServer`]. /// /// This type is intentionally **not** part of the public API: all callers must /// drive TLS connection construction through /// [`crate::establish::establish_stream`], which guarantees that the YANG /// configuration model is the single source of truth for transport parameters. pub(crate) struct TlsConfigurationBuilder { - root_cert_store: rustls::RootCertStore, - certificate_chain: Option>>, - private_key: Option>, + root_certificates: Vec, + certificate_chain: Option>, + private_key: Option>, disable_certificate_verification: bool, } impl TlsConfigurationBuilder { - /// Creates a new builder seeded with the default web PKI root store and no + /// Creates a new builder seeded with OpenSSL's default trust paths and no /// client authentication. pub(crate) fn new() -> Self { Self { - root_cert_store: crate::helpers::default_root_cert_store(), + root_certificates: Vec::new(), certificate_chain: None, private_key: None, disable_certificate_verification: false, @@ -33,20 +30,20 @@ impl TlsConfigurationBuilder { } /// Replaces the trust anchors used to verify the server certificate. - pub(crate) fn with_root_certificates(mut self, root_cert_store: rustls::RootCertStore) -> Self { - self.root_cert_store = root_cert_store; + pub(crate) fn with_root_certificates(mut self, root_certificates: Vec) -> Self { + self.root_certificates = root_certificates; self } /// Sets the client authentication certificate chain and private key, - /// already decoded into the appropriate `rustls` DER variants. + /// already decoded into OpenSSL certificate and key objects. pub(crate) fn with_client_auth_der( mut self, - cert_chain: Vec>, - key_der: PrivateKeyDer<'static>, + cert_chain: Vec, + key: PKey, ) -> Self { self.certificate_chain = Some(cert_chain); - self.private_key = Some(key_der); + self.private_key = Some(key); self } @@ -57,45 +54,50 @@ impl TlsConfigurationBuilder { self } - /// Builds the `rustls` client configuration. + /// Builds the OpenSSL client context. /// /// # Errors /// /// Returns an error if a certificate chain was provided without a private - /// key, or if `rustls` rejects the supplied client authentication - /// material. - pub(crate) fn build(self) -> anyhow::Result { - let supported_tls_versions = vec![&rustls::version::TLS13]; - - let config = - rustls::ClientConfig::builder_with_protocol_versions(supported_tls_versions.as_slice()) - .with_root_certificates(self.root_cert_store); - - let mut config = match self.certificate_chain { - Some(cert_chain) => match self.private_key { - Some(key_der) => config.with_client_auth_cert(cert_chain, key_der)?, - None => { - return Err(anyhow::Error::msg("Private key not provided")); - } - }, - None => config.with_no_client_auth(), - }; + /// key, or if OpenSSL rejects the supplied TLS material. + pub(crate) fn build(self) -> anyhow::Result { + let mut builder = SslContext::builder(SslMethod::tls_client())?; - // TLS 1.2 session resumption is unconditionally disabled because the - // workspace only negotiates TLS 1.3. - config.resumption = config - .resumption - .tls12_resumption(rustls::client::Tls12Resumption::Disabled); + builder.set_min_proto_version(Some(SslVersion::TLS1_3))?; + builder.set_max_proto_version(Some(SslVersion::TLS1_3))?; if self.disable_certificate_verification { - config - .dangerous() - .set_certificate_verifier(Arc::new(NoCertificateVerification::new( - rustls::crypto::aws_lc_rs::default_provider(), - ))); + builder.set_verify(SslVerifyMode::NONE); + } else { + builder.set_verify(SslVerifyMode::PEER); + if self.root_certificates.is_empty() { + builder.set_default_verify_paths()?; + } else { + let store = builder.cert_store_mut(); + for cert in self.root_certificates { + store.add_cert(cert)?; + } + } + } + + if let Some(mut cert_chain) = self.certificate_chain { + let Some(key) = self.private_key else { + anyhow::bail!("Private key not provided"); + }; + if cert_chain.is_empty() { + anyhow::bail!("Client certificate chain not provided"); + } + + let leaf = cert_chain.remove(0); + builder.set_certificate(&leaf)?; + for cert in cert_chain { + builder.add_extra_chain_cert(cert)?; + } + builder.set_private_key(&key)?; + builder.check_private_key()?; } - Ok(config) + Ok(builder.build()) } } @@ -104,7 +106,8 @@ mod tests { use std::fs; use std::path::PathBuf; - use rustls_pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}; + use openssl::pkey::{PKey, Private}; + use openssl::x509::X509; use super::TlsConfigurationBuilder; @@ -115,21 +118,19 @@ mod tests { .join(file_name) } - fn sample_client_auth_der() -> (Vec>, PrivateKeyDer<'static>) { + fn sample_client_auth_der() -> (Vec, PKey) { let cert_pem = fs::read_to_string(sample_path("client.crt")).expect("sample cert exists"); let key_pem = fs::read_to_string(sample_path("client.key")).expect("sample key exists"); - let cert_chain = CertificateDer::pem_slice_iter(cert_pem.as_bytes()) - .collect::, _>>() - .expect("sample cert PEM should parse"); + let cert_chain = + X509::stack_from_pem(cert_pem.as_bytes()).expect("sample cert PEM should parse"); let key_der = - PrivateKeyDer::from_pem_slice(key_pem.as_bytes()).expect("sample key PEM should parse"); + PKey::private_key_from_pem(key_pem.as_bytes()).expect("sample key PEM should parse"); (cert_chain, key_der) } #[test] - #[cfg_attr(miri, ignore)] // aws-lc-rs FFI in ClientConfig crypto provider fn with_client_auth_der_accepts_valid_der() { let (cert_chain, key_der) = sample_client_auth_der(); @@ -141,14 +142,12 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] // aws-lc-rs FFI in ClientConfig crypto provider fn build_without_client_auth_succeeds() { let config = TlsConfigurationBuilder::new().build(); assert!(config.is_ok(), "unexpected error: {config:?}"); } #[test] - #[cfg_attr(miri, ignore)] // aws-lc-rs FFI in ClientConfig crypto provider fn build_with_disabled_verification_succeeds() { let config = TlsConfigurationBuilder::new() .with_certificate_verification_disabled(true) diff --git a/libraries/tacacsrs_networking/src/transport/tls/danger.rs b/libraries/tacacsrs_networking/src/transport/tls/danger.rs deleted file mode 100644 index 5e0478c4..00000000 --- a/libraries/tacacsrs_networking/src/transport/tls/danger.rs +++ /dev/null @@ -1,59 +0,0 @@ -use tokio_rustls::rustls::client::danger::HandshakeSignatureValid; -use tokio_rustls::rustls::crypto::{verify_tls13_signature, CryptoProvider}; -use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime}; -use tokio_rustls::rustls::DigitallySignedStruct; - -/// A certificate verifier that disables certificate verification. -/// -/// # Warning -/// -/// This is dangerous and should only be used for testing or in controlled environments. -/// Using this in production exposes you to man-in-the-middle attacks. -#[derive(Debug)] -pub(super) struct NoCertificateVerification(CryptoProvider); - -impl NoCertificateVerification { - pub(super) const fn new(provider: CryptoProvider) -> Self { - Self(provider) - } -} - -impl tokio_rustls::rustls::client::danger::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> Result - { - log::warn!( - target: module_path!(), - "Certificate verification disabled" - ); - Ok(tokio_rustls::rustls::client::danger::ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, - ) -> Result { - Err(tokio_rustls::rustls::Error::General("TLS 1.2 not supported".to_string())) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, - ) -> Result { - verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms) - } - - fn supported_verify_schemes(&self) -> Vec { - self.0.signature_verification_algorithms.supported_schemes() - } -} diff --git a/libraries/tacacsrs_networking/src/transport/tls/from_server.rs b/libraries/tacacsrs_networking/src/transport/tls/from_server.rs index 8c886e84..dc40ed59 100644 --- a/libraries/tacacsrs_networking/src/transport/tls/from_server.rs +++ b/libraries/tacacsrs_networking/src/transport/tls/from_server.rs @@ -2,20 +2,20 @@ //! [`TacacsPlusServer`] configuration. //! //! This module owns the translation from the YANG-derived configuration model -//! to the lower-level TLS primitives (`rustls::RootCertStore`, DER certificate -//! chains, `PrivateKeyDer`, SNI server names). It exists so the establishment +//! to the lower-level TLS primitives (OpenSSL trust stores, DER certificate +//! chains, private keys, SNI server names). It exists so the establishment //! dispatcher does not need to understand certificate or key encoding details. -use std::sync::Arc; - use anyhow::{Context, Result}; -use rustls_pki_types::{CertificateDer, PrivateKeyDer}; +use openssl::ec::EcKey; +use openssl::pkey::{PKey, Private}; +use openssl::rsa::Rsa; +use openssl::x509::X509; use tacacsrs_config::TacacsPlusServer; use tacacsrs_config::TacacsPlusServerExt; use tacacsrs_config::crypto_types::PrivateKeyFormat; use tokio::net::TcpStream; -use tokio_rustls::client::TlsStream; -use tokio_rustls::rustls; +use tokio_openssl::SslStream; use super::TlsConfigurationBuilder; use super::connect_tls; @@ -34,14 +34,13 @@ use crate::helpers::{data_contains_pem_header, tls_server_name}; /// # Errors /// /// Returns an error if any TLS material in the configuration cannot be parsed, -/// the resulting `rustls::ClientConfig` cannot be built, or the TLS handshake -/// fails. +/// the resulting OpenSSL context cannot be built, or the TLS handshake fails. pub(crate) async fn establish_from_server( server: &TacacsPlusServer, address: &str, tcp_stream: TcpStream, disable_certificate_verification: bool, -) -> Result> { +) -> Result> { let sni_name = derive_sni_name(server, address); log::debug!("Negotiating TLS handshake with {address} (SNI: {sni_name})"); @@ -59,12 +58,10 @@ pub(crate) async fn establish_from_server( builder = builder.with_certificate_verification_disabled(true); } - let tls_config = Arc::new( - builder - .build() - .inspect_err(|e| log::warn!("Failed to build TLS config for {address}: {e:#}")) - .context("Failed to build TLS configuration")?, - ); + let tls_config = builder + .build() + .inspect_err(|e| log::warn!("Failed to build TLS config for {address}: {e:#}")) + .context("Failed to build TLS configuration")?; let tls_stream = connect_tls(&tls_config, tcp_stream, sni_name) .await @@ -88,16 +85,16 @@ fn derive_sni_name<'a>(server: &'a TacacsPlusServer, address: &'a str) -> &'a st tls_server_name(address) } -/// Builds a custom [`rustls::RootCertStore`] from the server's `ca-certs` and +/// Builds custom OpenSSL trust anchors from the server's `ca-certs` and /// `ee-certs` inline definitions. Returns `Ok(None)` if no custom CA material /// is configured (in which case the default web PKI roots will be used). -fn build_root_cert_store(server: &TacacsPlusServer) -> Result> { +fn build_root_cert_store(server: &TacacsPlusServer) -> Result>> { let Some(ref sa) = server.server_authentication else { return Ok(None); }; let mut has_certs = false; - let mut root_store = rustls::RootCertStore::empty(); + let mut root_store = Vec::new(); if let Some(ref ca) = sa.ca_certs { if let Some(ref inline) = ca.inline_definition { @@ -130,7 +127,7 @@ fn build_root_cert_store(server: &TacacsPlusServer) -> Result Result>, PrivateKeyDer<'static>)>> { +) -> Result, PKey)>> { let Some(ref ci) = server.client_identity else { return Ok(None); }; @@ -156,18 +153,16 @@ fn extract_client_auth( } /// Adds a DER-encoded certificate to a root cert store. -fn add_cert_to_store(cert_data: &[u8], store: &mut rustls::RootCertStore) -> Result<()> { +fn add_cert_to_store(cert_data: &[u8], store: &mut Vec) -> Result<()> { let certs = parse_certificate_data(cert_data)?; for cert in certs { - store - .add(cert) - .map_err(|e| anyhow::anyhow!("failed to add certificate to root store: {e}"))?; + store.push(cert); } Ok(()) } /// Parses DER-encoded certificate data. -fn parse_certificate_data(data: &[u8]) -> Result>> { +fn parse_certificate_data(data: &[u8]) -> Result> { if data.is_empty() { anyhow::bail!("certificate DER data is empty"); } @@ -176,7 +171,7 @@ fn parse_certificate_data(data: &[u8]) -> Result>> { anyhow::bail!("PEM-encoded certificates are not supported; provide DER bytes"); } - Ok(vec![CertificateDer::from(data.to_vec())]) + Ok(vec![X509::from_der(data).context("failed to parse DER certificate")?]) } /// Parses DER-encoded private key data using the YANG `private-key-format` @@ -189,7 +184,7 @@ fn parse_certificate_data(data: &[u8]) -> Result>> { fn parse_private_key_data( data: &[u8], private_key_format: Option<&PrivateKeyFormat>, -) -> Result> { +) -> Result> { if data.is_empty() { anyhow::bail!("private key DER data is empty"); } @@ -199,22 +194,24 @@ fn parse_private_key_data( } if let Some(fmt) = private_key_format { - let der_bytes = data.to_vec(); - return match fmt { PrivateKeyFormat::RsaPrivateKeyFormat => { - Ok(PrivateKeyDer::Pkcs1(rustls_pki_types::PrivatePkcs1KeyDer::from(der_bytes))) + let key = Rsa::private_key_from_der(data) + .context("failed to parse DER RSA private key")?; + PKey::from_rsa(key).context("failed to convert RSA private key") } PrivateKeyFormat::EcPrivateKeyFormat => { - Ok(PrivateKeyDer::Sec1(rustls_pki_types::PrivateSec1KeyDer::from(der_bytes))) + let key = EcKey::private_key_from_der(data) + .context("failed to parse DER EC private key")?; + PKey::from_ec_key(key).context("failed to convert EC private key") } PrivateKeyFormat::OneAsymmetricKeyFormat => { - Ok(PrivateKeyDer::Pkcs8(rustls_pki_types::PrivatePkcs8KeyDer::from(der_bytes))) + PKey::private_key_from_der(data).context("failed to parse DER PKCS#8 private key") } }; } - Ok(PrivateKeyDer::Pkcs8(rustls_pki_types::PrivatePkcs8KeyDer::from(data.to_vec()))) + PKey::private_key_from_der(data).context("failed to parse DER PKCS#8 private key") } #[cfg(test)] @@ -257,8 +254,19 @@ mod tests { #[test] fn parse_certificate_data_der_bytes() { - let der = b"\x30\x82\x01\x00fake-der-cert-data"; - let result = parse_certificate_data(der); + let cert_pem = std::fs::read( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("examples") + .join("samples") + .join("client.crt"), + ) + .expect("sample cert exists"); + let der = X509::from_pem(&cert_pem) + .expect("sample cert PEM should parse") + .to_der() + .expect("sample cert should serialize as DER"); + + let result = parse_certificate_data(&der); assert!(result.is_ok()); } diff --git a/libraries/tacacsrs_networking/src/transport/tls/mod.rs b/libraries/tacacsrs_networking/src/transport/tls/mod.rs index 473282b5..5ecc8ae5 100644 --- a/libraries/tacacsrs_networking/src/transport/tls/mod.rs +++ b/libraries/tacacsrs_networking/src/transport/tls/mod.rs @@ -10,7 +10,6 @@ //! [`TacacsPlusServer`]: tacacsrs_config::TacacsPlusServer mod config_builder; -mod danger; mod from_server; #[allow(clippy::module_inception)] mod tls; @@ -18,9 +17,11 @@ mod tls; pub(crate) use config_builder::TlsConfigurationBuilder; pub(crate) use from_server::establish_from_server; -use std::net::IpAddr; -use std::sync::Arc; -use tokio_rustls::{rustls, TlsConnector}; +use anyhow::Context; +use openssl::ssl::Ssl; +use openssl::ssl::SslContext; +use tokio::net::TcpStream; +use tokio_openssl::SslStream; /// Establishes a TLS connection over an existing TCP stream. /// @@ -37,24 +38,20 @@ use tokio_rustls::{rustls, TlsConnector}; /// - The server name is neither a valid domain name nor IP address /// - The TLS handshake fails pub(crate) async fn connect_tls( - config: &Arc, - stream: tokio::net::TcpStream, + context: &SslContext, + stream: TcpStream, server_name: &str, -) -> anyhow::Result> { - let connector = TlsConnector::from(config.clone()); +) -> anyhow::Result> { + let mut ssl = Ssl::new(context).context("OpenSSL failed to allocate TLS SSL object")?; + ssl.set_hostname(server_name) + .with_context(|| format!("OpenSSL failed to set TLS SNI for {server_name}"))?; - // Try parsing as IP address first, then fall back to DNS name - let server_name = if let Ok(ip) = server_name.parse::() { - rustls::pki_types::ServerName::IpAddress(ip.into()) - } else { - rustls::pki_types::ServerName::try_from(server_name) - .map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid server name") - })? - .to_owned() - }; + let mut stream = SslStream::new(ssl, stream) + .context("OpenSSL failed to attach TLS SSL object to TCP stream")?; + + SslStream::connect(std::pin::Pin::new(&mut stream)) + .await + .context("OpenSSL TLS handshake failed")?; - let stream: tokio_rustls::client::TlsStream = - connector.connect(server_name, stream).await?; Ok(stream) } diff --git a/libraries/tacacsrs_networking/src/transport/tls/readme.md b/libraries/tacacsrs_networking/src/transport/tls/readme.md index de3c734f..938206a3 100644 --- a/libraries/tacacsrs_networking/src/transport/tls/readme.md +++ b/libraries/tacacsrs_networking/src/transport/tls/readme.md @@ -2,4 +2,4 @@ This module provides the TLS implementation of the shared `Transport` trait. -`TlsStream` is split using `tokio::io::split`, yielding read/write halves for asynchronous concurrent processing. +`tokio_openssl::SslStream` is split using `tokio::io::split`, yielding read/write halves for asynchronous concurrent processing. diff --git a/libraries/tacacsrs_networking/src/transport/tls/tls.rs b/libraries/tacacsrs_networking/src/transport/tls/tls.rs index ccff60b0..f46c7e1d 100644 --- a/libraries/tacacsrs_networking/src/transport/tls/tls.rs +++ b/libraries/tacacsrs_networking/src/transport/tls/tls.rs @@ -1,9 +1,9 @@ use tokio::net::TcpStream; -use tokio_rustls::client::TlsStream; +use tokio_openssl::SslStream; use crate::transport::abstractions::Transport; -impl Transport for TlsStream { +impl Transport for SslStream { type ReadHalf = tokio::io::ReadHalf; type WriteHalf = tokio::io::WriteHalf; diff --git a/libraries/tacacsrs_networking/src/transport/tls_psk/mod.rs b/libraries/tacacsrs_networking/src/transport/tls_psk/mod.rs index 9033fdbc..04313a9f 100644 --- a/libraries/tacacsrs_networking/src/transport/tls_psk/mod.rs +++ b/libraries/tacacsrs_networking/src/transport/tls_psk/mod.rs @@ -18,16 +18,3 @@ mod tls13_epsk; pub(crate) use config::PskClientConfig; pub(crate) use context::{EpskSupportedHashExt, PskDheKeGroups}; pub(crate) use from_server::{establish_from_server, server_has_psk}; - -use tokio::net::TcpStream; - -use crate::transport::abstractions::Transport; - -impl Transport for tokio_openssl::SslStream { - type ReadHalf = tokio::io::ReadHalf; - type WriteHalf = tokio::io::WriteHalf; - - fn split(self) -> (Self::ReadHalf, Self::WriteHalf) { - tokio::io::split(self) - } -} diff --git a/libraries/tacacsrs_networking/src/transport/tls_psk/readme.md b/libraries/tacacsrs_networking/src/transport/tls_psk/readme.md index 8fef762a..1b64e994 100644 --- a/libraries/tacacsrs_networking/src/transport/tls_psk/readme.md +++ b/libraries/tacacsrs_networking/src/transport/tls_psk/readme.md @@ -2,8 +2,8 @@ This module implements TLS 1.3 External Pre-Shared Key (EPSK) transport for TACACS+ connections, gated behind the `psk` feature flag. It uses OpenSSL (via -`openssl` and `tokio-openssl` crates) because the default `rustls` backend does -not yet support TLS 1.3 external PSKs. +`openssl` and `tokio-openssl` crates), matching the certificate-based TLS +transport backend used by the default build. ## Background: What is TLS-PSK? @@ -267,9 +267,8 @@ because `SslStream` does not support owned splitting. ## Feature Flag -This entire module is gated behind `#[cfg(feature = "psk")]`. Without it, the -build uses rustls (pure Rust) and requires no OpenSSL dependency. The feature -propagates: `tacon` → `tacacsrs-networking` → OpenSSL. +This module is always compiled because OpenSSL is the shared TLS backend for +certificate-based TLS and TLS 1.3 external PSK support. ## References