From 7b1c7545bdfebb670ac4c967ed5a61b005b05d6e Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Fri, 5 Jan 2024 14:11:29 +0000 Subject: [PATCH] initial commit --- .github/dependabot.yml | 11 + .github/workflows/libssl.yaml | 163 ++++++++++ README.md | 10 + rustls-libssl/Cargo.lock | 437 +++++++++++++++++++++++++ rustls-libssl/Cargo.toml | 15 + rustls-libssl/LICENSE | 201 ++++++++++++ rustls-libssl/Makefile | 60 ++++ rustls-libssl/build.rs | 69 ++++ rustls-libssl/src/bio.rs | 78 +++++ rustls-libssl/src/entry.rs | 306 ++++++++++++++++++ rustls-libssl/src/error.rs | 150 +++++++++ rustls-libssl/src/ffi.rs | 439 ++++++++++++++++++++++++++ rustls-libssl/src/lib.rs | 272 ++++++++++++++++ rustls-libssl/src/verifier.rs | 112 +++++++ rustls-libssl/tests/insecure-client.c | 72 +++++ 15 files changed, 2395 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/libssl.yaml create mode 100644 README.md create mode 100644 rustls-libssl/Cargo.lock create mode 100644 rustls-libssl/Cargo.toml create mode 100644 rustls-libssl/LICENSE create mode 100644 rustls-libssl/Makefile create mode 100644 rustls-libssl/build.rs create mode 100644 rustls-libssl/src/bio.rs create mode 100644 rustls-libssl/src/entry.rs create mode 100644 rustls-libssl/src/error.rs create mode 100644 rustls-libssl/src/ffi.rs create mode 100644 rustls-libssl/src/lib.rs create mode 100644 rustls-libssl/src/verifier.rs create mode 100644 rustls-libssl/tests/insecure-client.c diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c7b634c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/libssl.yaml b/.github/workflows/libssl.yaml new file mode 100644 index 0000000..2cea07b --- /dev/null +++ b/.github/workflows/libssl.yaml @@ -0,0 +1,163 @@ +name: rustls-libssl + +permissions: + contents: read + +on: + push: + pull_request: + merge_group: + schedule: + - cron: '15 12 * * 3' + +defaults: + run: + working-directory: rustls-libssl + +jobs: + build: + name: Build+test + runs-on: ${{ matrix.os }} + strategy: + matrix: + # test a bunch of toolchains on ubuntu + cc: [clang, gcc] + rust: + - stable + - beta + - nightly + - 1.61.0 # MSRV - keep in sync with what rustls considers MSRV + os: [ubuntu-20.04] + # but only stable on macos/windows (slower platforms) + include: + - os: macos-latest + cc: clang + rust: stable + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install OpenSSL + run: sudo apt-get update && sudo apt-get install -y openssl libssl3 libssl-dev + + - name: Install ${{ matrix.rust }} toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + + - run: make CC=${{ matrix.cc }} PROFILE=release test + + valgrind: + name: Valgrind + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Install valgrind + run: sudo apt-get update && sudo apt-get install -y valgrind + - name: Install OpenSSL + run: sudo apt-get update && sudo apt-get install -y openssl libssl3 libssl-dev + - run: export VALGRIND="valgrind -q" + - run: make test + + docs: + name: Check for documentation errors + runs-on: ubuntu-20.04 + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install OpenSSL + run: sudo apt-get update && sudo apt-get install -y openssl libssl3 libssl-dev + + - name: Install rust toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: cargo doc (all features) + run: cargo doc --all-features --no-deps --workspace + env: + RUSTDOCFLAGS: -Dwarnings + + format: + name: Format + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Install rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.67.1 + components: rustfmt + - name: Check Rust formatting + run: cargo fmt --all -- --check + - name: Check C formatting + run: make format-check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Install rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.70.0 + components: clippy + - name: Check clippy + # We allow unknown lints here because sometimes the nightly job + # (below) will have a new lint that we want to suppress. + # If we suppress (e.g. #![allow(clippy::arc_with_non_send_sync)]), + # we would get an unknown-lint error from older clippy versions. + run: cargo clippy --locked --workspace -- -D warnings -A unknown-lints + + clippy-nightly-optional: + name: Clippy nightly (optional) + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Install rust toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: clippy + - name: Check clippy + run: cargo clippy --locked --workspace -- -D warnings + + clang-tidy: + name: Clang Tidy + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Clang tidy + run: clang-tidy tests/*.c -- -I src/ + + miri: + name: Miri + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install nightly Rust + uses: dtolnay/rust-toolchain@nightly + - run: rustup override set "nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" + - run: rustup component add miri + - run: cargo miri test diff --git a/README.md b/README.md new file mode 100644 index 0000000..0165df5 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# rustls-openssl-compat + +This is the planned home of several rustls ↔️ OpenSSL compatibility layers. +Currently here: + +- **rustls-libssl**: an implementation of the OpenSSL libssl ABI in terms of rustls. + +Not yet here: + +- **rustls-libcrypto**: an implementation of rustls `CryptoProvider` in terms of OpenSSL's libcrypto. diff --git a/rustls-libssl/Cargo.lock b/rustls-libssl/Cargo.lock new file mode 100644 index 0000000..6969e81 --- /dev/null +++ b/rustls-libssl/Cargo.lock @@ -0,0 +1,437 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "env_logger" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "openssl-sys" +version = "0.9.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkg-config" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-libssl" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "openssl-sys", + "rustls", +] + +[[package]] +name = "rustls-pki-types" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" + +[[package]] +name = "rustls-webpki" +version = "0.102.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/rustls-libssl/Cargo.toml b/rustls-libssl/Cargo.toml new file mode 100644 index 0000000..2fac7a5 --- /dev/null +++ b/rustls-libssl/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rustls-libssl" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[lib] +name = "ssl" +crate-type = ["cdylib"] + +[dependencies] +env_logger = "0.10" +log = "0.4" +openssl-sys = "0.9.98" +rustls = "0.22" diff --git a/rustls-libssl/LICENSE b/rustls-libssl/LICENSE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/rustls-libssl/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rustls-libssl/Makefile b/rustls-libssl/Makefile new file mode 100644 index 0000000..57af6e9 --- /dev/null +++ b/rustls-libssl/Makefile @@ -0,0 +1,60 @@ +CARGO ?= cargo +CARGOFLAGS += --locked + +CFLAGS := -Werror -Wall -Wextra -Wpedantic -g $(shell pkg-config --cflags openssl) +PROFILE := debug + +ifeq ($(CC), clang) + CFLAGS += -fsanitize=address -fsanitize=undefined + LDFLAGS += -fsanitize=address +endif + +ifeq ($(PROFILE), release) + CFLAGS += -O3 + CARGOFLAGS += --release +endif + +ifneq (,$(TARGET)) + PROFILE := $(TARGET)/$(PROFILE) + CARGOFLAGS += --target $(TARGET) +endif + +all: target/insecure-client target/$(PROFILE)/libssl.so.3 + +test: all + ${CARGO} test --locked + +integration: all + ${CARGO} test --locked -- --ignored + target/insecure-client + LD_LIBRARY_PATH=target/$(PROFILE) target/insecure-client + +target: + mkdir -p $@ + +target/$(PROFILE)/libssl.so.3: target/$(PROFILE)/libssl.so + cp -v $^ $@ + +target/$(PROFILE)/libssl.so: *.rs src/*.rs Cargo.toml + ${CARGO} build $(CARGOFLAGS) + +target/%.o: tests/%.c | target + $(CC) -o $@ -c $< $(CFLAGS) + +target/insecure-client: target/insecure-client.o + $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) + +clean: + rm -rf target + +format: + find . \ + -name '*.[c|h]' | \ + xargs clang-format -i + +format-check: + find . \ + -name '*.[c|h]' | \ + xargs clang-format --dry-run -Werror -i + +.PHONY: all clean test integration format format-check diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs new file mode 100644 index 0000000..732a0b1 --- /dev/null +++ b/rustls-libssl/build.rs @@ -0,0 +1,69 @@ +use std::{env, fs, path}; + +fn main() { + if cfg!(target_os = "linux") { + println!("cargo:rustc-cdylib-link-arg=-Wl,--soname=libssl.so.3"); + + // We require lld, because ld only supports one --version-script + // and rustc uses it for its own purposes (and provides no API for us). + println!("cargo:rustc-cdylib-link-arg=-fuse-ld=lld"); + + let filename = write_version_file(); + println!("cargo:rustc-cdylib-link-arg=-Wl,--version-script={filename}"); + + for symbol in ENTRYPOINTS { + // Rename underscore-prefixed symbols (produced by rust code) to + // unprefixed symbols (manipulated by our version file). + println!( + "cargo:rustc-cdylib-link-arg=-Wl,--defsym={}=_{}", + symbol, symbol + ); + } + } +} + +fn write_version_file() -> String { + let out_dir = env::var("OUT_DIR").unwrap(); + let dest = path::Path::new(&out_dir).join("versions.map"); + + let mut content = String::new(); + content.push_str("OPENSSL_3.0.0 {\n"); + content.push_str(" global:\n"); + for e in ENTRYPOINTS { + content.push_str(&format!(" {e};\n")); + } + content.push_str(" local:\n"); + content.push_str(" *;\n"); + content.push_str("};\n"); + + fs::write(&dest, content).unwrap(); + println!("cargo:rerun-if-changed=build.rs"); + dest.to_str().unwrap().to_string() +} + +const ENTRYPOINTS: &[&str] = &[ + "OPENSSL_init_ssl", + "SSL_clear_options", + "SSL_connect", + "SSL_CTX_clear_options", + "SSL_CTX_free", + "SSL_CTX_get_options", + "SSL_CTX_new", + "SSL_CTX_set_options", + "SSL_free", + "SSL_get_options", + "SSL_is_server", + "SSL_new", + "SSL_read", + "SSL_set1_host", + "SSL_set_accept_state", + "SSL_set_connect_state", + "SSL_set_fd", + "SSL_set_options", + "SSL_up_ref", + "SSL_want", + "SSL_write", + "TLS_client_method", + "TLS_method", + "TLS_server_method", +]; diff --git a/rustls-libssl/src/bio.rs b/rustls-libssl/src/bio.rs new file mode 100644 index 0000000..e22734e --- /dev/null +++ b/rustls-libssl/src/bio.rs @@ -0,0 +1,78 @@ +use core::ffi::{c_int, c_void}; +use std::io; + +use openssl_sys::{BIO_free_all, BIO}; + +/// Safe, owning wrapper around an OpenSSL BIO. +pub struct Bio { + raw: *mut BIO, +} + +impl Bio { + pub fn from_fd_no_close(fd: c_int) -> Self { + Self { + raw: unsafe { BIO_new_fd(fd, 0) }, + } + } +} + +impl io::Read for Bio { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut read_bytes = 0; + let rc = unsafe { + BIO_read_ex( + self.raw, + buf.as_mut_ptr() as *mut c_void, + buf.len(), + &mut read_bytes, + ) + }; + + match rc { + 1 => Ok(read_bytes), + _ => Err(io::Error::other("BIO_read_ex failed")), + } + } +} + +impl io::Write for Bio { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut written_bytes = 0; + let rc = unsafe { + BIO_write_ex( + self.raw, + buf.as_ptr() as *const c_void, + buf.len(), + &mut written_bytes, + ) + }; + + match rc { + 1 => Ok(written_bytes), + _ => Err(io::Error::other("BIO_write_ex failed")), + } + } + + fn flush(&mut self) -> io::Result<()> { + // nb. BIO_flush "in some cases it is used to signal EOF and + // that no more data will be written." so is not a good match. + Ok(()) + } +} + +impl Drop for Bio { + fn drop(&mut self) { + unsafe { + BIO_free_all(self.raw); + } + } +} + +extern "C" { + /// XXX: missing from openssl-sys(?) investigate why that is. + fn BIO_new_fd(fd: c_int, close_flag: c_int) -> *mut BIO; + /// XXX: missing from openssl-sys(?) investigate why that is. + fn BIO_read_ex(b: *mut BIO, data: *mut c_void, dlen: usize, readbytes: *mut usize) -> c_int; + /// XXX: missing from openssl-sys(?) investigate why that is. + fn BIO_write_ex(b: *mut BIO, data: *const c_void, dlen: usize, written: *mut usize) -> c_int; +} diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs new file mode 100644 index 0000000..47a97cb --- /dev/null +++ b/rustls-libssl/src/entry.rs @@ -0,0 +1,306 @@ +//! This file contains all the libssl entrypoints that we implement. +//! +//! It should mainly be concerned with mapping these calls up to +//! the safe APIs implemented elsewhere. + +use core::mem; +use std::os::raw::{c_char, c_int, c_void}; +use std::sync::Mutex; + +use crate::bio::Bio; +use crate::error::{ffi_panic_boundary, Error}; +use crate::ffi::{ + free_arc, to_arc_mut_ptr, try_clone_arc, try_mut_slice_int, try_ref_from_ptr, try_slice_int, + try_str, Castable, OwnershipArc, OwnershipRef, +}; + +/// Makes a entry function definition. +/// +/// The body is wrapped in `ffi_panic_boundary`, the name is `#[no_mangle]`, +/// and is `extern "C"`. +/// +/// See also `build.rs`: +/// +/// - the name should start with `_` to support the linker-renaming and symbol +/// versioning happening there, +/// - the name should appear in the list of all entry points there. +macro_rules! entry { + (pub fn $name:ident($($args:tt)*) $body:block) => { + #[no_mangle] + pub extern "C" fn $name($($args)*) { ffi_panic_boundary! { $body } } + }; + (pub fn $name:ident($($args:tt)*) -> $ret:ty $body:block) => { + #[no_mangle] + pub extern "C" fn $name($($args)*) -> $ret { ffi_panic_boundary! { $body } } + }; + +} + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub struct OpenSslInitSettings; +type OPENSSL_INIT_SETTINGS = OpenSslInitSettings; + +entry! { + pub fn _OPENSSL_init_ssl(_opts: u64, + settings: *const OPENSSL_INIT_SETTINGS) -> c_int { + if !settings.is_null() { + return Error::not_supported("settings").raise().into(); + } + env_logger::init(); + log::trace!("OPENSSL_init_ssl in rustls-libssl {VERSION}"); + 0 + } +} + +type SSL_METHOD = crate::SslMethod; + +entry! { + pub fn _TLS_method() -> *const SSL_METHOD { + &crate::TLS_METHOD + } +} + +entry! { + pub fn _TLS_server_method() -> *const SSL_METHOD { + &crate::TLS_SERVER_METHOD + } +} + +entry! { + pub fn _TLS_client_method() -> *const SSL_METHOD { + &crate::TLS_CLIENT_METHOD + } +} + +impl Castable for SSL_METHOD { + type Ownership = OwnershipRef; + type RustType = SSL_METHOD; +} + +type SSL_CTX = crate::SslContext; + +entry! { + pub fn _SSL_CTX_new(meth: *const SSL_METHOD) -> *mut SSL_CTX { + let method = try_ref_from_ptr!(meth); + to_arc_mut_ptr(Mutex::new(crate::SslContext::new(method))) + } +} + +entry! { + pub fn _SSL_CTX_free(ctx: *mut SSL_CTX) { + free_arc(ctx); + } +} + +entry! { + pub fn _SSL_CTX_get_options(ctx: *const SSL_CTX) -> u64 { + let mutex = try_clone_arc!(ctx); + mutex + .lock() + .ok() + .map(|ctx| ctx.get_options()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_CTX_clear_options(ctx: *mut SSL_CTX, op: u64) -> u64 { + let mutex = try_clone_arc!(ctx); + mutex + .lock() + .ok() + .map(|mut ctx| ctx.clear_options(op)) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_CTX_set_options(ctx: *mut SSL_CTX, op: u64) -> u64 { + let mutex = try_clone_arc!(ctx); + mutex + .lock() + .ok() + .map(|mut ctx| ctx.set_options(op)) + .unwrap_or_default() + } +} + +impl Castable for SSL_CTX { + type Ownership = OwnershipArc; + type RustType = Mutex; +} + +type SSL = crate::Ssl; + +entry! { + pub fn _SSL_new(ctx: *mut SSL_CTX) -> *mut SSL { + let ctx = try_clone_arc!(ctx); + to_arc_mut_ptr(Mutex::new(crate::Ssl::new(ctx))) + } +} + +entry! { + pub fn _SSL_get_options(ssl: *const SSL) -> u64 { + let mutex = try_clone_arc!(ssl); + mutex + .lock() + .ok() + .map(|ssl| ssl.get_options()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_clear_options(ssl: *mut SSL, op: u64) -> u64 { + let mutex = try_clone_arc!(ssl); + mutex + .lock() + .ok() + .map(|mut ssl| ssl.clear_options(op)) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_set_options(ssl: *mut SSL, op: u64) -> u64 { + let mutex = try_clone_arc!(ssl); + mutex + .lock() + .ok() + .map(|mut ssl| ssl.set_options(op)) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_want(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + let want = ssl.lock().ok().map(|ssl| ssl.want()).unwrap_or_default(); + + if want.read { + SSL_READING + } else if want.write { + SSL_WRITING + } else { + SSL_NOTHING + } + } +} + +pub const SSL_NOTHING: i32 = 1; +pub const SSL_WRITING: i32 = 2; +pub const SSL_READING: i32 = 3; + +entry! { + pub fn _SSL_set_connect_state(ssl: *mut SSL) { + let ssl = try_clone_arc!(ssl); + let _ = ssl.lock().ok().map(|mut ssl| ssl.client_mode()); + } +} + +entry! { + pub fn _SSL_set_accept_state(ssl: *mut SSL) { + let ssl = try_clone_arc!(ssl); + let _ = ssl.lock().ok().map(|mut ssl| ssl.server_mode()); + } +} + +entry! { + pub fn _SSL_is_server(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.is_server()) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_up_ref(ssl: *mut SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + mem::forget(ssl.clone()); + C_INT_SUCCESS + } +} + +entry! { + pub fn _SSL_set1_host(ssl: *mut SSL, hostname: *const c_char) -> c_int { + let ssl = try_clone_arc!(ssl); + let hostname = try_str!(hostname); + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_verify_hostname(hostname)) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_connect(ssl: *mut SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + + match ssl.lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.connect()) + .map_err(|err| err.raise()) { + Err(e) => e.into(), + Ok(()) => 0, + } + } +} + +entry! { + pub fn _SSL_write(ssl: *mut SSL, buf: *const c_void, num: c_int) -> c_int { + const ERROR: c_int = -1; + let ssl = try_clone_arc!(ssl, ERROR); + let slice = try_slice_int!(buf as *const u8, num, ERROR); + + match ssl.lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.write(slice)) + .map_err(|err| err.raise()) { + Err(_e) => ERROR, + Ok(written) => written as c_int, + } + } +} + +entry! { + pub fn _SSL_read(ssl: *mut SSL, buf: *mut c_void, num: c_int) -> c_int { + const ERROR: c_int = -1; + let ssl = try_clone_arc!(ssl, ERROR); + let slice = try_mut_slice_int!(buf as *mut u8, num, ERROR); + + match ssl.lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.read(slice)) + .map_err(|err| err.raise()) { + Err(_e) => ERROR, + Ok(read) => read as c_int, + } + } +} + +entry! { + pub fn _SSL_set_fd(ssl: *mut SSL, fd: c_int) -> c_int { + let ssl = try_clone_arc!(ssl); + let bio = Bio::from_fd_no_close(fd); + ssl.lock() + .ok() + .map(|mut ssl| { ssl.set_bio(bio); true } ) + .unwrap_or_default() as c_int + } +} + +entry! { + pub fn _SSL_free(ssl: *mut SSL) { + free_arc(ssl); + } +} + +impl Castable for SSL { + type Ownership = OwnershipArc; + type RustType = Mutex; +} + +const C_INT_SUCCESS: c_int = 1; diff --git a/rustls-libssl/src/error.rs b/rustls-libssl/src/error.rs new file mode 100644 index 0000000..3dee9b8 --- /dev/null +++ b/rustls-libssl/src/error.rs @@ -0,0 +1,150 @@ +use core::ffi::c_int; +use core::ptr; +use std::ffi::CString; + +use openssl_sys::{ERR_new, ERR_set_error, ERR_RFLAGS_OFFSET, ERR_RFLAG_FATAL}; + +#[derive(Copy, Clone, Debug)] +#[repr(i32)] +enum Lib { + /// This is `ERR_LIB_SSL`. + Ssl = 20, + + /// This is `ERR_LIB_USER`. + User = 128, +} + +const ERR_RFLAG_COMMON: i32 = 0x2i32 << ERR_RFLAGS_OFFSET; + +#[derive(Copy, Clone, Debug)] +#[repr(i32)] +enum Reason { + PassedNullParameter = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 258, + InternalError = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 259, + UnableToGetWriteLock = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 272, + OperationFailed = (ERR_RFLAG_FATAL as i32) | ERR_RFLAG_COMMON | 263, + Unsupported = ERR_RFLAG_COMMON | 268, +} + +#[derive(Debug)] +pub struct Error { + lib: Lib, + reason: Reason, + string: Option, +} + +impl Error { + pub fn unexpected_panic() -> Self { + Self { + lib: Lib::Ssl, + reason: Reason::InternalError, + string: None, + } + } + + pub fn null_pointer() -> Self { + Self { + lib: Lib::Ssl, + reason: Reason::PassedNullParameter, + string: None, + } + } + + pub fn cannot_lock() -> Self { + Self { + lib: Lib::Ssl, + reason: Reason::UnableToGetWriteLock, + string: None, + } + } + + pub fn not_supported(hint: &str) -> Self { + Self { + lib: Lib::Ssl, + reason: Reason::Unsupported, + string: Some(hint.to_string()), + } + } + + pub fn from_rustls(err: rustls::Error) -> Self { + Self { + lib: Lib::User, + reason: Reason::OperationFailed, + string: Some(err.to_string()), + } + } + + pub fn from_io(err: std::io::Error) -> Self { + Self { + lib: Lib::User, + reason: Reason::OperationFailed, + string: Some(err.to_string()), + } + } + + /// Add this error to the openssl error stack. + pub fn raise(self) -> Self { + dbg!(&self); + let cstr = CString::new( + self.string + .clone() + .unwrap_or_else(|| format!("{:?}", self.reason)), + ) + .unwrap(); + unsafe { + ERR_new(); + ERR_set_error(self.lib as c_int, self.reason as c_int, cstr.as_ptr()); + } + self + } +} + +impl Into<*const T> for Error { + fn into(self) -> *const T { + ptr::null() + } +} + +impl Into<*mut T> for Error { + fn into(self) -> *mut T { + ptr::null_mut() + } +} + +impl Into for Error { + fn into(self) -> c_int { + // for typical OpenSSL functions (return 0 on error) + 0 + } +} + +impl Into for Error { + fn into(self) -> u64 { + // for options functions (return 0 on error) + 0 + } +} + +impl Into<()> for Error { + fn into(self) -> () { + // for void functions (return early on error) + () + } +} + +#[macro_export] +macro_rules! ffi_panic_boundary { + ( $($tt:tt)* ) => { + match ::std::panic::catch_unwind( + ::std::panic::AssertUnwindSafe(|| { + $($tt)* + })) { + Ok(ret) => ret, + Err(_) => return $crate::error::Error::unexpected_panic() + .raise() + .into(), + } + } +} + +pub(crate) use ffi_panic_boundary; diff --git a/rustls-libssl/src/ffi.rs b/rustls-libssl/src/ffi.rs new file mode 100644 index 0000000..9585a8c --- /dev/null +++ b/rustls-libssl/src/ffi.rs @@ -0,0 +1,439 @@ +//! Violently borrowed from rustls-ffi. +//! +//! TODO: undo that. + +use core::ffi::{c_char, CStr}; +use std::mem; +use std::sync::Arc; + +/// Used to mark that pointer to a [`Castable`]'s underlying `Castable::RustType` is provided +/// to C code as a pointer to a `Box`. +pub(crate) struct OwnershipBox; + +/// Used to mark that a pointer to a [`Castable`]'s underlying `Castable::RustType` is provided +/// to C code as a pointer to an `Arc`. +pub(crate) struct OwnershipArc; + +/// Used to mark that a pointer to a [`Castable`]'s underlying `Castable::RustType` is provided +/// to C code as a pointer to a reference, `&Castable::RustType`. +pub(crate) struct OwnershipRef; + +/// A trait for marking the type of a pointer to a [`Castable`]'s underlying `Castable::RustType` +/// that is provided to C code, either a [`OwnershipBox`] when it is a pointer to a `Box<_>`, +/// a [`OwnershipArc`] when it is a pointer to an `Arc<_>`, or a [`OwnershipRef`] when it is a +/// pointer to a `&_`. +pub(crate) trait OwnershipMarker {} + +impl OwnershipMarker for OwnershipBox {} + +impl OwnershipMarker for OwnershipArc {} + +impl OwnershipMarker for OwnershipRef {} + +/// `Castable` represents the relationship between a snake case type (like [`client::rustls_client_config`]) +/// and the corresponding Rust type (like [`rustls::ClientConfig`]), specified as the associated type +/// `RustType`. Each `Castable` also has an associated type `Ownership` specifying one of the +/// [`OwnershipMarker`] types, [`OwnershipBox`], [`OwnershipArc`] or [`OwnershipRef`]. +/// +/// An implementation of `Castable` that uses [`OwnershipBox`] indicates that when we give C code +/// a pointer to the relevant `RustType` `T`, that it is actually a `Box`. An +/// implementation of `Castable` that uses [`OwnershipArc`] means that when we give C code a +/// pointer to the relevant type, that it is actually an `Arc`. Lastly an implementation of +/// `Castable` that uses [`OwnershipRef`] means that when we give C code a pointer to the relevant +/// type, that it is actually a `&T`. +/// +/// By using an associated type on `Castable` to communicate this we can use the type system to +/// guarantee that a single type can't implement `Castable` for more than one [`OwnershipMarker`], +/// since this would be a conflicting trait implementation and rejected by the compiler. +/// +/// This trait allows us to avoid using `as` in most places, and ensures that when we cast, we're +/// preserving const-ness, and casting between the correct types. Implementing this is required in +/// order to use `try_ref_from_ptr!` or `try_mut_from_ptr!` and several other helpful cast-related +/// conversion helpers. +pub(crate) trait Castable { + /// Indicates whether to use `Box` or `Arc` when giving a pointer to C code for the underlying + /// `RustType`. + type Ownership: OwnershipMarker; + + /// The underlying Rust type that we are casting to and from. + type RustType; +} + +/// Convert a const pointer to a [`Castable`] to a const pointer to its underlying +/// [`Castable::RustType`]. +/// +/// This can be used regardless of the [`Castable::Ownership`] as we can make const pointers for +/// `Box`, `Arc` and ref types. +pub(crate) fn cast_const_ptr(ptr: *const C) -> *const C::RustType +where + C: Castable, +{ + ptr as *const _ +} + +/// Convert a [`Castable`]'s underlying [`Castable::RustType`] to a constant pointer +/// to an `Arc` over the rust type. Can only be used when the `Castable` has specified a cast type +/// equal to [`OwnershipArc`]. +pub(crate) fn to_arc_const_ptr(src: C::RustType) -> *const C +where + C: Castable, +{ + Arc::into_raw(Arc::new(src)) as *const _ +} + +/// Convert a [`Castable`]'s underlying [`Castable::RustType`] to a mutable pointer +/// to an `Arc` over the rust type. Can only be used when the `Castable` has specified a cast type +/// equal to [`OwnershipArc`]. +pub(crate) fn to_arc_mut_ptr(src: C::RustType) -> *mut C +where + C: Castable, +{ + Arc::into_raw(Arc::new(src)) as *mut C +} + +/// Given a const pointer to a [`Castable`] representing an `Arc`, clone the `Arc` and return +/// the corresponding Rust type. +/// +/// The caller still owns its copy of the `Arc`. In other words, the reference count of the +/// `Arc` will be incremented by 1 by the end of this function. +/// +/// To achieve that, we need to `mem::forget` the `Arc` we get back from `into_raw`, because +/// `into_raw` _does_ take back ownership. If we called `into_raw` without `mem::forget`, at the +/// end of the function that Arc would be dropped and the reference count would be decremented, +/// potentially to 0, causing memory to be freed. +/// +/// Does nothing, returning `None`, when passed a `NULL` pointer. Can only be used when the +/// `Castable` has specified a cast type equal to [`OwnershipArc`]. +/// +/// ## Unsafety: +/// +/// If non-null, `ptr` must be a pointer that resulted from previously calling `Arc::into_raw`, +/// e.g. from using [`to_arc_const_ptr`]. +pub(crate) fn clone_arc(ptr: *const C) -> Option> +where + C: Castable, +{ + if ptr.is_null() { + return None; + } + let rs_typed = cast_const_ptr::(ptr); + let r = unsafe { Arc::from_raw(rs_typed) }; + let val = Arc::clone(&r); + mem::forget(r); + Some(val) +} + +/// Convert a mutable pointer to a [`Castable`] to an optional `Box` over the underlying rust type. +/// +/// Does nothing, returning `None`, when passed `NULL`. Can only be used when the `Castable` has +/// specified a cast type equal to [`OwnershipBox`]. +/// +/// ## Unsafety: +/// +/// If non-null, `ptr` must be a pointer that resulted from previously calling `Box::into_raw`, +/// e.g. from using [`to_boxed_mut_ptr`]. +pub(crate) fn to_box(ptr: *mut C) -> Option> +where + C: Castable, +{ + if ptr.is_null() { + return None; + } + let rs_typed = cast_mut_ptr(ptr); + unsafe { Some(Box::from_raw(rs_typed)) } +} + +/// Free a constant pointer to a [`Castable`]'s underlying [`Castable::RustType`] by +/// reconstituting an `Arc` from the raw pointer and dropping it. +/// +/// For types represented with an `Arc` on the Rust side, we offer a `_free()` +/// method to the C side that decrements the refcount and ultimately drops +/// the `Arc` if the refcount reaches 0. By contrast with `to_arc`, we call +/// `Arc::from_raw` on the input pointer, but we _don't_ clone it, because we +/// want the refcount to be lower by one when we reach the end of the function. +/// +/// Does nothing, returning `None`, when passed `NULL`. Can only be used when the `Castable` has +/// specified a cast type equal to [`OwnershipArc`]. +pub(crate) fn free_arc(ptr: *const C) +where + C: Castable, +{ + if ptr.is_null() { + return; + } + let rs_typed = cast_const_ptr(ptr); + drop(unsafe { Arc::from_raw(rs_typed) }); +} + +/// Convert a mutable pointer to a [`Castable`] to an optional `Box` over the underlying +/// [`Castable::RustType`], and immediately let it fall out of scope to be freed. +/// +/// Can only be used when the `Castable` has specified a cast type equal to [`OwnershipBox`]. +/// +/// ## Unsafety: +/// +/// If non-null, `ptr` must be a pointer that resulted from previously calling `Box::into_raw`, +/// e.g. from using [`to_boxed_mut_ptr`]. +pub(crate) fn free_box(ptr: *mut C) +where + C: Castable, +{ + to_box(ptr); +} + +/// Convert a mutable pointer to a [`Castable`] to a mutable pointer to its underlying +/// [`Castable::RustType`]. +/// +/// Can only be used when the `Castable` has specified a cast source equal to `BoxCastPtrMarker`. +pub(crate) fn cast_mut_ptr(ptr: *mut C) -> *mut C::RustType +where + C: Castable, +{ + ptr as *mut _ +} + +/// Converts a [`Castable`]'s underlying [`Castable::RustType`] to a mutable pointer +/// to a `Box` over the rust type. +/// +/// Can only be used when the `Castable` has specified a cast type equal to [`OwnershipBox`]. +pub(crate) fn to_boxed_mut_ptr(src: C::RustType) -> *mut C +where + C: Castable, +{ + Box::into_raw(Box::new(src)) as *mut _ +} + +/// Converts a [`Castable`]'s underlying [`Castable::RustType`] to a mutable pointer +/// to a `Box` over the rust type and sets the `dst` out pointer to the resulting mutable `Box` +/// pointer. See [`to_boxed_mut_ptr`] for more information. +/// +/// ## Unsafety: +/// +/// `dst` must not be `NULL`. +pub(crate) fn set_boxed_mut_ptr(dst: *mut *mut C, src: C::RustType) +where + C: Castable, +{ + unsafe { + *dst = to_boxed_mut_ptr(src); + } +} + +/// Converts a [`Castable`]'s underlying [`Castable::RustType`] to a const pointer +/// to an `Arc` over the rust type and sets the `dst` out pointer to the resulting const `Arc` +/// pointer. See [`to_arc_const_ptr`] for more information. +/// +/// ## Unsafety: +/// +/// `dst` must not be `NULL`. +pub(crate) fn set_arc_mut_ptr(dst: *mut *const C, src: C::RustType) +where + C: Castable, +{ + unsafe { + *dst = to_arc_const_ptr(src); + } +} + +/// Converts a mutable pointer to a [`Castable`] to an optional ref to the underlying +/// [`Castable::RustType`]. See [`cast_mut_ptr`] for more information. +/// +/// Does nothing, returning `None`, when passed `NULL`. Can only be used when the `Castable` has +/// specified a cast type equal to [`OwnershipBox`]. +pub(crate) fn try_from_mut<'a, C>(from: *mut C) -> Option<&'a mut C::RustType> +where + C: Castable, +{ + unsafe { cast_mut_ptr(from).as_mut() } +} + +/// If the provided pointer to a [`Castable`] is non-null, convert it to a mutable reference using +/// [`try_from_mut`]. Otherwise, return [`rustls_result::NullParameter`], or an appropriate default +/// (`false`, `0`, `NULL`) based on the context. See [`try_from_mut`] for more information. +macro_rules! try_mut_from_ptr { + ( $var:ident ) => { + match $crate::ffi::try_from_mut($var) { + Some(c) => c, + None => return $crate::panic::NullParameterOrDefault::value(), + } + }; +} + +pub(crate) use try_mut_from_ptr; + +/// Converts a const pointer to a [`Castable`] to an optional ref to the underlying +/// [`Castable::RustType`]. See [`cast_const_ptr`] for more information. +/// +/// Does nothing, returning `None` when passed `NULL`. Can be used with `Castable`'s that +/// specify a cast type of [`OwnershipArc`] as well as `Castable`'s that specify +/// a cast type of [`OwnershipBox`]. +pub(crate) fn try_from<'a, C, O>(from: *const C) -> Option<&'a C::RustType> +where + C: Castable, +{ + unsafe { cast_const_ptr(from).as_ref() } +} + +/// If the provided pointer to a [`Castable`] is non-null, convert it to a reference using +/// [`try_from`]. Otherwise, raise and return a `crate::error::Error::null_pointer()` error. +/// +/// See [`try_from`] for more information. +macro_rules! try_ref_from_ptr { + ( $var:ident ) => { + match $crate::ffi::try_from($var) { + Some(c) => c, + None => return $crate::error::Error::null_pointer().raise().into(), + } + }; +} + +pub(crate) use try_ref_from_ptr; + +/// If the provided pointer to a [`Castable`] is non-null, convert it to a reference to an `Arc` over +/// the underlying rust type using [`try_arc_from`]. +/// +/// Otherwise, raise and return a `crate::error::Error::null_pointer()` error. +/// In the two-argument version, the error code returned can be specified to +/// deal with inconsistent return value usages (eg. `SSL_read`). +/// +/// See [`try_arc_from`] for more information. +macro_rules! try_clone_arc { + ( $var:ident ) => { + match $crate::ffi::clone_arc($var) { + Some(c) => c, + None => return $crate::error::Error::null_pointer().raise().into(), + } + }; + ( $var:ident, $error_code:expr ) => { + match $crate::ffi::clone_arc($var) { + Some(c) => c, + None => { + $crate::error::Error::null_pointer().raise(); + return $error_code; + } + } + }; +} + +pub(crate) use try_clone_arc; + +/// Convert a mutable pointer to a [`Castable`] to an optional `Box` over the underlying +/// [`Castable::RustType`]. +/// +/// Does nothing, returning `None`, when passed `NULL`. Can only be used with `Castable`'s that +/// specify a cast type of [`OwnershipBox`]. +pub(crate) fn try_box_from(from: *mut C) -> Option> +where + C: Castable, +{ + to_box(from) +} + +/// If the provided pointer to a [`Castable`] is non-null, convert it to a reference to a `Box` +/// over the underlying rust type using [`try_box_from`]. +/// +/// Otherwise, raise and return a `crate::error::Error::null_pointer()` error. +/// +/// See [`try_box_from`] for more information. +macro_rules! try_box_from_ptr { + ( $var:ident ) => { + match $crate::ffi::try_box_from($var) { + Some(c) => c, + None => return $crate::error::Error::null_pointer().raise().into(), + } + }; +} + +pub(crate) use try_box_from_ptr; + +/// Makes a slice from a pointer and signed length. +/// +/// An error is returned if the pointer is null or the length is negative. +/// +/// In the three-argument version, the error code returned can be specified to +/// deal with inconsistent return value usages (eg. `SSL_read`). +macro_rules! try_slice_int { + ( $ptr:expr, $count:expr ) => { + if $ptr.is_null() || $count < 0 { + return $crate::error::Error::null_pointer().raise().into(); + } else { + unsafe { ::core::slice::from_raw_parts($ptr, $count as usize) } + } + }; + ( $ptr:expr, $count:expr, $error_code:expr ) => { + if $ptr.is_null() || $count < 0 { + $crate::error::Error::null_pointer().raise(); + return $error_code; + } else { + unsafe { ::core::slice::from_raw_parts($ptr, $count as usize) } + } + }; +} + +pub(crate) use try_slice_int; + +/// Makes a mutable slice from a pointer and signed length. +/// +/// An error is returned if the pointer is null or the length is negative. +/// +/// In the three-argument version, the error code returned can be specified to +/// deal with inconsistent return value usages (eg. `SSL_read`). +macro_rules! try_mut_slice_int { + ( $ptr:expr, $count:expr ) => { + if $ptr.is_null() || $count < 0 { + return $crate::error::Error::null_pointer().raise().into(); + } else { + unsafe { ::core::slice::from_raw_parts_mut($ptr, $count as usize) } + } + }; + ( $ptr:expr, $count:expr, $error_code:expr ) => { + if $ptr.is_null() || $count < 0 { + $crate::error::Error::null_pointer().raise(); + return $error_code; + } else { + unsafe { ::core::slice::from_raw_parts_mut($ptr, $count as usize) } + } + }; +} + +pub(crate) use try_mut_slice_int; + +pub(crate) fn string_from_cstring(s: *const c_char) -> Option { + if s.is_null() { + return None; + } + + let cstr = unsafe { CStr::from_ptr(s) }; + Some(String::from_utf8_lossy(cstr.to_bytes()).to_string()) +} + +pub(crate) fn str_from_cstring(s: *const c_char) -> Option<&'static str> { + if s.is_null() { + return None; + } + + let cstr = unsafe { CStr::from_ptr(s) }; + cstr.to_str().ok() +} + +macro_rules! try_string { + ( $ptr:expr) => { + match $crate::ffi::string_from_cstring($ptr) { + Some(s) => s, + None => return $crate::error::Error::null_pointer().raise().into(), + } + }; +} + +pub(crate) use try_string; + +macro_rules! try_str { + ( $ptr:expr) => { + match $crate::ffi::str_from_cstring($ptr) { + Some(s) => s, + None => return $crate::error::Error::null_pointer().raise().into(), + } + }; +} + +pub(crate) use try_str; diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs new file mode 100644 index 0000000..23eb486 --- /dev/null +++ b/rustls-libssl/src/lib.rs @@ -0,0 +1,272 @@ +use std::io::{Read, Write}; +use std::sync::{Arc, Mutex}; + +pub use rustls::crypto::ring as provider; +use rustls::{pki_types::ServerName, ClientConfig, ClientConnection, Connection, RootCertStore}; + +#[allow( + // relax naming convention lints for openssl API + non_camel_case_types, + non_snake_case, + // false positives on extern entrypoints + dead_code +)] +mod entry; + +#[macro_use] +#[allow(unused_macros, dead_code, unused_imports)] +mod ffi; + +mod bio; +mod error; +mod verifier; + +/// `SSL_METHOD` underlying type. +/// +/// # Lifetime +/// Functions that return SSL_METHOD, like `TLS_method()`, give static-lifetime pointers. +pub struct SslMethod { + client_versions: &'static [&'static rustls::SupportedProtocolVersion], + server_versions: &'static [&'static rustls::SupportedProtocolVersion], +} + +impl SslMethod { + fn mode(&self) -> Mode { + match ( + self.client_versions.is_empty(), + self.server_versions.is_empty(), + ) { + (true, false) => Mode::Server, + (false, true) => Mode::Client, + (_, _) => Mode::Unknown, + } + } +} + +static TLS_CLIENT_METHOD: SslMethod = SslMethod { + client_versions: rustls::ALL_VERSIONS, + server_versions: &[], +}; +static TLS_SERVER_METHOD: SslMethod = SslMethod { + client_versions: &[], + server_versions: rustls::ALL_VERSIONS, +}; +static TLS_METHOD: SslMethod = SslMethod { + client_versions: rustls::ALL_VERSIONS, + server_versions: rustls::ALL_VERSIONS, +}; + +pub struct SslContext { + method: &'static SslMethod, + raw_options: u64, +} + +impl SslContext { + fn new(method: &'static SslMethod) -> Self { + Self { + method, + raw_options: 0, + } + } + + fn get_options(&self) -> u64 { + self.raw_options + } + + fn set_options(&mut self, set: u64) -> u64 { + self.raw_options |= set; + self.raw_options + } + + fn clear_options(&mut self, clear: u64) -> u64 { + self.raw_options &= !clear; + self.raw_options + } +} + +struct Ssl { + ctx: Arc>, + raw_options: u64, + mode: Mode, + verify_server_name: Option>, + sni_server_name: Option>, + bio: Option, + conn: Option, +} + +impl Ssl { + fn new(ctx: Arc>) -> Self { + let (raw_options, mode) = ctx + .lock() + .ok() + .map(|ctx| (ctx.raw_options, ctx.method.mode())) + .unwrap_or((0, Mode::Unknown)); + Self { + ctx, + raw_options, + mode, + verify_server_name: None, + sni_server_name: None, + bio: None, + conn: None, + } + } + + fn get_options(&self) -> u64 { + self.raw_options + } + + fn set_options(&mut self, set: u64) -> u64 { + self.raw_options |= set; + self.raw_options + } + + fn clear_options(&mut self, clear: u64) -> u64 { + self.raw_options &= !clear; + self.raw_options + } + + fn want(&self) -> Want { + match &self.conn { + Some(conn) => Want { + read: conn.wants_read(), + write: conn.wants_write(), + }, + None => Want::default(), + } + } + + fn client_mode(&mut self) { + // nb. don't fill in `conn` until the last minute. + // SSL_set_connect_state() .. SSL_set1_host() .. SSL_connect() is a valid + // sequence of calls. + self.mode = Mode::Client; + } + + fn server_mode(&mut self) { + self.mode = Mode::Server; + } + + fn is_server(&self) -> bool { + self.mode == Mode::Server + } + + fn set_verify_hostname(&mut self, hostname: &str) -> bool { + match ServerName::try_from(hostname).ok() { + Some(server_name) => { + self.verify_server_name = Some(server_name.to_owned()); + true + } + None => false, + } + } + + fn set_bio(&mut self, bio: bio::Bio) { + self.bio = Some(bio); + } + + fn connect(&mut self) -> Result<(), error::Error> { + self.client_mode(); + + // if absent, use a dummy IP address which disables SNI. + let sni_server_name = match &self.sni_server_name { + Some(sni_name) => sni_name.clone(), + None => ServerName::try_from("0.0.0.0").unwrap(), + }; + + let method = self + .ctx + .lock() + .map(|ctx| ctx.method) + .map_err(|_| error::Error::cannot_lock())?; + + let root_store = RootCertStore::empty().into(); // TODO + let provider = Arc::new(provider::default_provider()); + let verifier = + verifier::ServerVerifier::new(root_store, provider.clone(), &self.verify_server_name); + + let config = ClientConfig::builder_with_provider(provider) + .with_protocol_versions(method.client_versions) + .expect("bad versions") + .dangerous() + .with_custom_certificate_verifier(Arc::new(verifier)) + .with_no_client_auth(); + + let client_conn = ClientConnection::new(Arc::new(config), sni_server_name.clone()) + .map_err(error::Error::from_rustls)?; + + self.conn = Some(client_conn.into()); + Ok(()) + } + + fn write(&mut self, slice: &[u8]) -> Result { + let written = match &mut self.conn { + Some(ref mut conn) => conn.writer().write(slice).expect("IO error"), + None => 0, + }; + self.try_io()?; + Ok(written) + } + + fn read(&mut self, slice: &mut [u8]) -> Result { + let late_err = self.try_io(); + + let read = match &mut self.conn { + Some(ref mut conn) => conn.reader().read(slice).expect("IO error"), + None => 0, + }; + + if read > 0 { + Ok(read) + } else { + // Only raise IO errors after all data has been read. + late_err?; + Ok(0) + } + } + + fn try_io(&mut self) -> Result<(), error::Error> { + let bio = match self.bio.as_mut() { + Some(bio) => bio, + None => return Ok(()), // investigate OpenSSL behaviour without a BIO + }; + + match &mut self.conn { + Some(ref mut conn) => conn + .complete_io(bio) + .map_err(error::Error::from_io) + .map(|_| ()), + None => Ok(()), + } + } +} + +#[derive(Default)] +struct Want { + read: bool, + write: bool, +} + +#[derive(PartialEq)] +enum Mode { + Unknown, + Client, + Server, +} + +/// --------------------- + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/rustls-libssl/src/verifier.rs b/rustls-libssl/src/verifier.rs new file mode 100644 index 0000000..a1eca47 --- /dev/null +++ b/rustls-libssl/src/verifier.rs @@ -0,0 +1,112 @@ +use std::sync::Arc; + +use rustls::{ + client::{ + danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + verify_server_cert_signed_by_trust_anchor, verify_server_name, + }, + crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, + pki_types::{CertificateDer, ServerName, UnixTime}, + server::ParsedCertificate, + DigitallySignedStruct, Error, RootCertStore, SignatureScheme, +}; + +/// This is a verifier that implements the selection of bad ideas from OpenSSL: +/// +/// - that the SNI name and verified certificate server name are unrelated +/// - that the server name can be empty, and that implicitly disables hostname verification +/// - that the behaviour defaults to verifying nothing +#[derive(Debug)] +pub struct ServerVerifier { + /// The full-fledged verifier we use when not disabled. + root_store: Arc, + + provider: Arc, + + /// Expected server name. + /// + /// `None` means server name verification is disabled. + verify_hostname: Option>, + + check_cert_chain: bool, +} + +impl ServerVerifier { + pub fn new( + root_store: Arc, + provider: Arc, + hostname: &Option>, + ) -> Self { + Self { + root_store, + provider, + verify_hostname: hostname.clone(), + check_cert_chain: false, + } + } +} + +impl ServerCertVerifier for ServerVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _ignored_server_name: &ServerName<'_>, + _ocsp_response: &[u8], + now: UnixTime, + ) -> Result { + if !self.check_cert_chain { + return Ok(ServerCertVerified::assertion()); + } + + let end_entity = ParsedCertificate::try_from(end_entity)?; + + verify_server_cert_signed_by_trust_anchor( + &end_entity, + &self.root_store, + intermediates, + now, + self.provider.signature_verification_algorithms.all, + )?; + + if let Some(server_name) = &self.verify_hostname { + verify_server_name(&end_entity, server_name)?; + } + + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &self.provider.signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &self.provider.signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.provider + .signature_verification_algorithms + .supported_schemes() + } +} diff --git a/rustls-libssl/tests/insecure-client.c b/rustls-libssl/tests/insecure-client.c new file mode 100644 index 0000000..d54023a --- /dev/null +++ b/rustls-libssl/tests/insecure-client.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static int trace(int rc, const char *str) { + printf("%s: %d\n", str, rc); + return rc; +} + +#define TRACE(fn) trace((fn), #fn) + +static void hexdump(const char *label, const char *buf, int n) { + printf("%s (%d bytes): ", label, n); + for (int i = 0; i < n; i++) { + printf("%02x", (unsigned char)buf[i]); + } + printf("\n"); +} + +static void dump_openssl_error_stack(void) { + if (ERR_peek_error() != 0) { + printf("openssl error: "); + ERR_print_errors_fp(stdout); + } +} + +int main() { + struct addrinfo *result = NULL; + TRACE(getaddrinfo("localhost", "8443", NULL, &result)); + + int sock = TRACE( + socket(result->ai_family, result->ai_socktype, result->ai_protocol)); + TRACE(connect(sock, result->ai_addr, result->ai_addrlen)); + freeaddrinfo(result); + + TRACE(OPENSSL_init_ssl(0, NULL)); + dump_openssl_error_stack(); + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + dump_openssl_error_stack(); + SSL *ssl = SSL_new(ctx); + dump_openssl_error_stack(); + TRACE(SSL_set1_host(ssl, "localhost")); + dump_openssl_error_stack(); + TRACE(SSL_set_fd(ssl, sock)); + dump_openssl_error_stack(); + TRACE(SSL_connect(ssl)); + dump_openssl_error_stack(); + + int wr = TRACE(SSL_write(ssl, "hello", 5)); + dump_openssl_error_stack(); + assert(wr == 5); + char buf[10] = {0}; + int rd = TRACE(SSL_read(ssl, buf, sizeof(buf))); + dump_openssl_error_stack(); + hexdump("result", buf, rd); + + close(sock); + SSL_free(ssl); + SSL_CTX_free(ctx); + + printf("PASS\n\n"); + + return 0; +}