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..4621b9c --- /dev/null +++ b/.github/workflows/libssl.yaml @@ -0,0 +1,156 @@ +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: + rust: + - stable + - beta + - nightly + os: [ubuntu-latest] + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install build dependencies + run: sudo apt-get update && sudo apt-get install -y openssl libssl3 libssl-dev lld + + - name: Install ${{ matrix.rust }} toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + + - run: make 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 build dependencies + run: sudo apt-get update && sudo apt-get install -y openssl libssl3 libssl-dev lld + - run: export VALGRIND="valgrind -q" + - run: make test + + docs: + name: Check for documentation errors + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install build dependencies + run: sudo apt-get update && sudo apt-get install -y openssl libssl3 libssl-dev lld + + - 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 src/entry.rs formatting + run: ./admin/format-entry --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@stable + with: + 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..8fb6aec --- /dev/null +++ b/rustls-libssl/Cargo.lock @@ -0,0 +1,454 @@ +# 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 = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +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", + "rustls-pemfile", +] + +[[package]] +name = "rustls-pemfile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[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..3e35a3b --- /dev/null +++ b/rustls-libssl/Cargo.toml @@ -0,0 +1,16 @@ +[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" +rustls-pemfile = "2" 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..c6d8522 --- /dev/null +++ b/rustls-libssl/Makefile @@ -0,0 +1,64 @@ +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/client target/constants target/ciphers target/$(PROFILE)/libssl.so.3 + +test: all + ${CARGO} test --locked + +integration: all + ${CARGO} test --locked -- --ignored + +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/client: target/client.o + $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) + +target/constants: target/constants.o + $(CC) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs openssl) + +target/ciphers: target/ciphers.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/admin/format-entry b/rustls-libssl/admin/format-entry new file mode 100755 index 0000000..177280c --- /dev/null +++ b/rustls-libssl/admin/format-entry @@ -0,0 +1,18 @@ +#!/bin/sh + +# rustfmt cannot generally format the inside of macro invocations, +# because they can invent their own syntax. +# +# in the case of the `entry!` macro in src/entry.rs, we specifically +# try and keep the syntax the same as a rust function definition. +# +# that means we can trick rustfmt into formatting it by replacing +# `entry!` with `mod entry`, and then restore it back afterwards. + +sed -i -e 's/^entry! {/mod entry {/g' src/entry.rs +sed -i -e 's/^entry_stub! {/mod entry_stub {/g' src/entry.rs +cargo fmt "$@" +rc=$? +sed -i -e 's/^mod entry {/entry! {/g' src/entry.rs +sed -i -e 's/^mod entry_stub {/entry_stub! {/g' src/entry.rs +exit $rc diff --git a/rustls-libssl/admin/progress.py b/rustls-libssl/admin/progress.py new file mode 100755 index 0000000..8b0e0b2 --- /dev/null +++ b/rustls-libssl/admin/progress.py @@ -0,0 +1,207 @@ +#!/usr/bin/python3 + +import subprocess + +TESTS_CLIENT = """ +SSL_read +SSL_CTX_free +SSL_set1_host +SSL_CTX_set_verify +SSL_free +SSL_set_fd +OPENSSL_init_ssl +SSL_new +SSL_connect +SSL_CTX_new +TLS_method +SSL_write +SSL_CTX_load_verify_file +""" + +CURL = """ +BIO_f_ssl +OPENSSL_init_ssl +SSL_alert_desc_string_long +SSL_CIPHER_get_name +SSL_connect +SSL_ctrl +SSL_CTX_add_client_CA +SSL_CTX_check_private_key +SSL_CTX_ctrl +SSL_CTX_free +SSL_CTX_get_cert_store +SSL_CTX_load_verify_dir +SSL_CTX_load_verify_file +SSL_CTX_new +SSL_CTX_sess_set_new_cb +SSL_CTX_set_alpn_protos +SSL_CTX_set_cipher_list +SSL_CTX_set_ciphersuites +SSL_CTX_set_default_passwd_cb +SSL_CTX_set_default_passwd_cb_userdata +SSL_CTX_set_keylog_callback +SSL_CTX_set_msg_callback +SSL_CTX_set_next_proto_select_cb +SSL_CTX_set_options +SSL_CTX_set_post_handshake_auth +SSL_CTX_set_srp_password +SSL_CTX_set_srp_username +SSL_CTX_set_verify +SSL_CTX_use_certificate_chain_file +SSL_CTX_use_certificate_file +SSL_CTX_use_certificate +SSL_CTX_use_PrivateKey_file +SSL_CTX_use_PrivateKey +SSL_free +SSL_get0_alpn_selected +SSL_get1_peer_certificate +SSL_get_certificate +SSL_get_current_cipher +SSL_get_error +SSL_get_ex_data +SSL_get_peer_cert_chain +SSL_get_privatekey +SSL_get_shutdown +SSL_get_verify_result +SSL_get_version +SSL_new +SSL_pending +SSL_read +SSL_SESSION_free +SSL_set_bio +SSL_set_connect_state +SSL_set_ex_data +SSL_set_fd +SSL_set_session +SSL_shutdown +SSL_write +TLS_client_method +""" + +NGINX = """ +d2i_SSL_SESSION +i2d_SSL_SESSION +OPENSSL_init_ssl +SSL_CIPHER_description +SSL_CIPHER_find +SSL_CIPHER_get_name +SSL_clear_options +SSL_ctrl +SSL_CTX_callback_ctrl +SSL_CTX_clear_options +SSL_CTX_ctrl +SSL_CTX_free +SSL_CTX_get_cert_store +SSL_CTX_get_client_CA_list +SSL_CTX_get_ex_data +SSL_CTX_get_max_early_data +SSL_CTX_get_options +SSL_CTX_get_timeout +SSL_CTX_get_verify_callback +SSL_CTX_get_verify_depth +SSL_CTX_get_verify_mode +SSL_CTX_load_verify_locations +SSL_CTX_new +SSL_CTX_remove_session +SSL_CTX_sess_set_get_cb +SSL_CTX_sess_set_new_cb +SSL_CTX_sess_set_remove_cb +SSL_CTX_set_alpn_protos +SSL_CTX_set_alpn_select_cb +SSL_CTX_set_cert_cb +SSL_CTX_set_cipher_list +SSL_CTX_set_client_CA_list +SSL_CTX_set_ex_data +SSL_CTX_set_info_callback +SSL_CTX_set_max_early_data +SSL_CTX_set_next_protos_advertised_cb +SSL_CTX_set_options +SSL_CTX_set_session_id_context +SSL_CTX_set_timeout +SSL_CTX_set_verify_depth +SSL_CTX_set_verify +SSL_CTX_use_certificate +SSL_CTX_use_PrivateKey +SSL_do_handshake +SSL_free +SSL_get0_alpn_selected +SSL_get0_next_proto_negotiated +SSL_get1_peer_certificate +SSL_get1_session +SSL_get_certificate +SSL_get_current_cipher +SSL_get_error +SSL_get_ex_data +SSL_get_ex_data_X509_STORE_CTX_idx +SSL_get_options +SSL_get_rbio +SSL_get_servername +SSL_get_session +SSL_get_shutdown +SSL_get_verify_result +SSL_get_version +SSL_get_wbio +SSL_in_init +SSL_is_init_finished +SSL_load_client_CA_file +SSL_new +SSL_read_early_data +SSL_read +SSL_select_next_proto +SSL_SESSION_free +SSL_SESSION_get_id +SSL_session_reused +SSL_SESSION_up_ref +SSL_set_accept_state +SSL_set_connect_state +SSL_set_ex_data +SSL_set_fd +SSL_set_options +SSL_set_quiet_shutdown +SSL_set_session +SSL_set_shutdown +SSL_set_SSL_CTX +SSL_set_verify_depth +SSL_set_verify +SSL_shutdown +SSL_use_certificate +SSL_use_PrivateKey +SSL_write_early_data +SSL_write +TLS_method +""" + + +def probe(): + defined = set() + for line in subprocess.check_output(['nm', '--extern-only', 'target/debug/libssl.so'], encoding='utf8').splitlines(): + if ' T ' in line: + addr, _, name = line.split() + + if name[0] != '_': + defined.add(name) + + return defined + + +def report(required_syms, usecase, already_implemented): + required_syms = set([x.strip() for x in required_syms.strip().splitlines()]) + + if required_syms <= already_implemented: + print(usecase, 'support is complete') + return + + additional = required_syms - already_implemented + print(usecase, 'support %g%% complete' % (len(required_syms & already_implemented) / len(required_syms) * 100)) + + print(usecase, 'additionally requires:') + for add in sorted(required_syms - already_implemented): + print(' *', add) + + +if __name__ == '__main__': + implemented = probe() + + report(TESTS_CLIENT, "tests/client", implemented) + report(CURL, "curl", implemented) + report(NGINX, "nginx", implemented) diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs new file mode 100644 index 0000000..82b7815 --- /dev/null +++ b/rustls-libssl/build.rs @@ -0,0 +1,131 @@ +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] = &[ + "BIO_f_ssl", + "OPENSSL_init_ssl", + "SSL_alert_desc_string_long", + "SSL_alert_desc_string", + "SSL_CIPHER_description", + "SSL_CIPHER_find", + "SSL_CIPHER_get_bits", + "SSL_CIPHER_get_id", + "SSL_CIPHER_get_protocol_id", + "SSL_CIPHER_get_name", + "SSL_CIPHER_get_version", + "SSL_CIPHER_standard_name", + "SSL_clear_options", + "SSL_connect", + "SSL_ctrl", + "SSL_CTX_add_client_CA", + "SSL_CTX_check_private_key", + "SSL_CTX_clear_options", + "SSL_CTX_ctrl", + "SSL_CTX_free", + "SSL_CTX_get_cert_store", + "SSL_CTX_get_options", + "SSL_CTX_load_verify_dir", + "SSL_CTX_load_verify_file", + "SSL_CTX_new", + "SSL_CTX_sess_set_new_cb", + "SSL_CTX_set_alpn_protos", + "SSL_CTX_set_cipher_list", + "SSL_CTX_set_ciphersuites", + "SSL_CTX_set_default_passwd_cb", + "SSL_CTX_set_default_passwd_cb_userdata", + "SSL_CTX_set_keylog_callback", + "SSL_CTX_set_msg_callback", + "SSL_CTX_set_next_proto_select_cb", + "SSL_CTX_set_options", + "SSL_CTX_set_post_handshake_auth", + "SSL_CTX_set_srp_password", + "SSL_CTX_set_srp_username", + "SSL_CTX_set_verify", + "SSL_CTX_up_ref", + "SSL_CTX_use_certificate", + "SSL_CTX_use_certificate_chain_file", + "SSL_CTX_use_certificate_file", + "SSL_CTX_use_PrivateKey", + "SSL_CTX_use_PrivateKey_file", + "SSL_free", + "SSL_get0_alpn_selected", + "SSL_get0_peer_certificate", + "SSL_get0_verified_chain", + "SSL_get1_peer_certificate", + "SSL_get_certificate", + "SSL_get_current_cipher", + "SSL_get_error", + "SSL_get_ex_data", + "SSL_get_options", + "SSL_get_peer_cert_chain", + "SSL_get_privatekey", + "SSL_get_shutdown", + "SSL_get_verify_result", + "SSL_get_version", + "SSL_has_pending", + "SSL_is_server", + "SSL_new", + "SSL_pending", + "SSL_read", + "SSL_SESSION_free", + "SSL_set0_rbio", + "SSL_set0_wbio", + "SSL_set1_host", + "SSL_set_accept_state", + "SSL_set_alpn_protos", + "SSL_set_bio", + "SSL_set_connect_state", + "SSL_set_ex_data", + "SSL_set_fd", + "SSL_set_options", + "SSL_set_post_handshake_auth", + "SSL_set_session", + "SSL_set_shutdown", + "SSL_shutdown", + "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..08b44df --- /dev/null +++ b/rustls-libssl/src/bio.rs @@ -0,0 +1,326 @@ +use core::ffi::{c_char, c_int, c_long, c_void, CStr}; +use std::io; + +// nb. cannot use any BIO types from openssl_sys: it doesn't +// have the internal type for BIO_METHOD, and once we provide +// it here their opaque type doesn't match ours. + +/// Safe, owning wrapper around an OpenSSL BIO pair. +/// +/// This owns references to both BIOs, even if they +/// are the same pointer. +pub struct Bio { + read: *mut BIO, + write: *mut BIO, +} + +impl Bio { + /// Use a pre-existing file descriptor. + /// + /// Does not (and cannot) validate the file descriptor. + pub fn new_fd_no_close(fd: c_int) -> Self { + let (read, write) = unsafe { + let bio = BIO_new_fd(fd, 0); + BIO_up_ref(bio); + (bio, bio) + }; + Self { read, write } + } + + /// Use a pair of raw BIO pointers. + /// + /// Absent pointers are silently replaced with a `BIO_s_null()`. + /// `Some(ptr::null_mut())` is illegal. + /// + /// The caller donates their references, even if they point to the same + /// object. In other words: if rbio and wbio are both `Some` and contain + /// the same pointer, the underlying reference count must be at least 2! + pub fn new_pair(rbio: Option<*mut BIO>, wbio: Option<*mut BIO>) -> Self { + let read = rbio.unwrap_or_else(|| unsafe { BIO_new(BIO_s_null()) }); + let write = wbio.unwrap_or_else(|| unsafe { BIO_new(BIO_s_null()) }); + Self { read, write } + } + + /// Update this object with a pair of raw BIO pointers. + /// + pub fn update(&mut self, rbio: Option<*mut BIO>, wbio: Option<*mut BIO>) { + match (rbio, wbio) { + (Some(rbio), Some(wbio)) => { + // See `man SSL_set_bio` for the overcomplex ownership rules when both + // `rbio` and `wbio` are `Some`: + // + // If neither the rbio or wbio have changed from their + // previous values then nothing is done. + if rbio == self.read && wbio == self.write { + return; + } + + // If the rbio and wbio parameters are different and both are + // different to their previously set values then one reference + // is consumed for the rbio and one reference is consumed for + // the wbio. + if rbio != wbio && rbio != self.read && wbio != self.write { + self.set_read(rbio); + self.set_write(wbio); + return; + } + + // If the rbio and wbio parameters are the same and the rbio + // is not the same as the previously set value then one reference + // is consumed. + if rbio == wbio && rbio != self.read { + unsafe { + BIO_up_ref(rbio); + } + self.set_read(rbio); + self.set_write(wbio); + return; + } + + // If the rbio and wbio parameters are the same and the rbio + // is the same as the previously set value, then no additional + // references are consumed. + if rbio == wbio && rbio == self.read { + // (er, what about self.write though?) + return; + } + + // If the rbio and wbio parameters are different and the rbio + // is the same as the previously set value then one reference + // is consumed for the wbio and no references are consumed for + // the rbio. + if rbio != wbio && rbio == self.read { + self.set_write(wbio); + return; + } + + // If the rbio and wbio parameters are different and the wbio + // is the same as the previously set value and the old rbio and + // wbio values were the same as each other then one reference + // is consumed for the rbio and no references are consumed for + // the wbio. + if rbio != wbio && wbio == self.write && self.read == self.write { + self.set_read(rbio); + return; + } + + // If the rbio and wbio parameters are different and the wbio + // is the same as the previously set value and the old rbio and + // wbio values were different to each other, then one reference + // is consumed for the rbio and one reference is consumed for the wbio. + if rbio != wbio && wbio == self.write && self.read != self.write { + self.set_read(rbio); + self.set_write(wbio); + } + } + (Some(rbio), None) => { + self.set_read(rbio); + } + (None, Some(wbio)) => { + self.set_write(wbio); + } + (None, None) => {} + } + } + + /// Sets `write` to `wbio`. + /// + /// Frees the old `write` if needed. + /// Consumes the `wbio` reference unconditionally. + /// + /// `wbio` must be non-NULL. + fn set_write(&mut self, wbio: *mut BIO) { + if wbio != self.write { + unsafe { BIO_free_all(self.write) }; + self.write = wbio; + } else { + unsafe { BIO_free_all(wbio) }; + } + } + + /// Sets `read` to `rbio`. + /// + /// Frees the old `read` if needed. + /// Consumes the `rbio` reference unconditionally. + /// + /// `rbio` must be non-NULL. + fn set_read(&mut self, rbio: *mut BIO) { + if rbio != self.read { + unsafe { BIO_free_all(self.read) }; + self.read = rbio; + } else { + unsafe { BIO_free_all(rbio) }; + } + } + + pub fn read_would_block(&self) -> bool { + bio_should_retry(self.read) + } + + pub fn write_would_block(&self) -> bool { + bio_should_retry(self.write) + } +} + +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.read, + buf.as_mut_ptr() as *mut c_void, + buf.len(), + &mut read_bytes, + ) + }; + + match rc { + 1 => Ok(read_bytes), + _ => { + if bio_in_eof(self.read) { + Ok(0) + } else if bio_should_retry(self.read) { + Err(io::ErrorKind::WouldBlock.into()) + } else { + 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.write, + 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.read); + BIO_free_all(self.write); + } + } +} + +static NAME: &CStr = crate::constants::constant_cstr!("ssl"); +const BIO_TYPE_SSL: i32 = 0x0200 | 7; + +pub static SSL_BIO_METHOD: bio_method_st = bio_method_st { + type_: BIO_TYPE_SSL, + name: NAME.as_ptr(), + bwrite: None, + bwrite_old: None, + bread: None, + bread_old: None, + bputs: None, + bgets: None, + ctrl: None, + create: None, + destroy: None, + callback_ctrl: None, +}; + +// This is a public interface between libcrypto and libssl, but is +// defined in `internal/bio.h`. Hmm. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct bio_method_st { + pub type_: c_int, + pub name: *const c_char, + pub bwrite: Option< + unsafe extern "C" fn( + arg1: *mut BIO, + arg2: *const c_char, + arg3: usize, + arg4: *mut usize, + ) -> c_int, + >, + pub bwrite_old: + Option c_int>, + pub bread: Option< + unsafe extern "C" fn( + arg1: *mut BIO, + arg2: *mut c_char, + arg3: usize, + arg4: *mut usize, + ) -> c_int, + >, + pub bread_old: + Option c_int>, + pub bputs: Option c_int>, + pub bgets: + Option c_int>, + pub ctrl: Option< + unsafe extern "C" fn( + arg1: *mut BIO, + arg2: c_int, + arg3: c_long, + arg4: *mut c_void, + ) -> c_long, + >, + pub create: Option c_int>, + pub destroy: Option c_int>, + pub callback_ctrl: + Option c_long>, +} + +unsafe impl Send for bio_method_st {} +unsafe impl Sync for bio_method_st {} + +#[allow(non_camel_case_types)] +pub type BIO_info_cb = + Option c_int>; + +#[repr(C)] +pub struct OpaqueBio { + _private: [u8; 0], +} + +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub type BIO = OpaqueBio; +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub type BIO_METHOD = bio_method_st; + +fn bio_should_retry(b: *const BIO) -> bool { + const BIO_SHOULD_RETRY: c_int = 0x08; + unsafe { BIO_test_flags(b, BIO_SHOULD_RETRY) != 0 } +} + +fn bio_in_eof(b: *const BIO) -> bool { + const BIO_IN_EOF: c_int = 0x800; + unsafe { BIO_test_flags(b, BIO_IN_EOF) != 0 } +} + +extern "C" { + fn BIO_new(meth: *const BIO_METHOD) -> *mut BIO; + fn BIO_free_all(b: *mut BIO); + fn BIO_new_fd(fd: c_int, close_flag: c_int) -> *mut BIO; + fn BIO_read_ex(b: *mut BIO, data: *mut c_void, dlen: usize, readbytes: *mut usize) -> c_int; + fn BIO_write_ex(b: *mut BIO, data: *const c_void, dlen: usize, written: *mut usize) -> c_int; + fn BIO_up_ref(b: *mut BIO) -> c_int; + fn BIO_test_flags(b: *const BIO, flags: c_int) -> c_int; + fn BIO_s_null() -> *const BIO_METHOD; +} diff --git a/rustls-libssl/src/constants.rs b/rustls-libssl/src/constants.rs new file mode 100644 index 0000000..f53991a --- /dev/null +++ b/rustls-libssl/src/constants.rs @@ -0,0 +1,100 @@ +use core::ffi::{c_int, CStr}; + +use rustls::AlertDescription; + +macro_rules! constant_cstr { + ($v:literal) => { + // safety: this is safe by construction, because $v is a compile-time + // constant (that we require does not contain any NUL characters), + // and the interior `concat!` arranges for a NUL to appear at the end. + unsafe { &::core::ffi::CStr::from_bytes_with_nul_unchecked(concat!($v, "\0").as_bytes()) } + }; +} + +pub(crate) use constant_cstr; + +pub fn alert_desc_to_long_string(value: c_int) -> &'static CStr { + match AlertDescription::from(value as u8) { + AlertDescription::CloseNotify => constant_cstr!("close notify"), + AlertDescription::UnexpectedMessage => constant_cstr!("unexpected_message"), + AlertDescription::BadRecordMac => constant_cstr!("bad record mac"), + AlertDescription::DecryptionFailed => constant_cstr!("decryption failed"), + AlertDescription::RecordOverflow => constant_cstr!("record overflow"), + AlertDescription::DecompressionFailure => constant_cstr!("decompression failure"), + AlertDescription::HandshakeFailure => constant_cstr!("handshake failure"), + AlertDescription::NoCertificate => constant_cstr!("no certificate"), + AlertDescription::BadCertificate => constant_cstr!("bad certificate"), + AlertDescription::UnsupportedCertificate => constant_cstr!("unsupported certificate"), + AlertDescription::CertificateRevoked => constant_cstr!("certificate revoked"), + AlertDescription::CertificateExpired => constant_cstr!("certificate expired"), + AlertDescription::CertificateUnknown => constant_cstr!("certificate unknown"), + AlertDescription::IllegalParameter => constant_cstr!("illegal parameter"), + AlertDescription::UnknownCA => constant_cstr!("unknown CA"), + AlertDescription::AccessDenied => constant_cstr!("access denied"), + AlertDescription::DecodeError => constant_cstr!("decode error"), + AlertDescription::DecryptError => constant_cstr!("decrypt error"), + AlertDescription::ExportRestriction => constant_cstr!("export restriction"), + AlertDescription::ProtocolVersion => constant_cstr!("protocol version"), + AlertDescription::InsufficientSecurity => constant_cstr!("insufficient security"), + AlertDescription::InternalError => constant_cstr!("internal error"), + AlertDescription::UserCanceled => constant_cstr!("user canceled"), + AlertDescription::NoRenegotiation => constant_cstr!("no renegotiation"), + AlertDescription::UnsupportedExtension => constant_cstr!("unsupported extension"), + AlertDescription::CertificateUnobtainable => constant_cstr!("certificate unobtainable"), + AlertDescription::UnrecognisedName => constant_cstr!("unrecognized name"), + AlertDescription::BadCertificateStatusResponse => { + constant_cstr!("bad certificate status response") + } + AlertDescription::BadCertificateHashValue => constant_cstr!("bad certificate hash value"), + AlertDescription::UnknownPSKIdentity => constant_cstr!("unknown PSK identity"), + AlertDescription::NoApplicationProtocol => constant_cstr!("no application protocol"), + // these not supported by openssl: + // AlertDescription::InappropriateFallback => constant_cstr!("inappropriate fallback"), + // AlertDescription::MissingExtension => constant_cstr!("missing extension"), + // AlertDescription::CertificateRequired => constant_cstr!("certificate required"), + _ => constant_cstr!("unknown"), + } +} + +pub fn alert_desc_to_short_string(value: c_int) -> &'static CStr { + match AlertDescription::from(value as u8) { + AlertDescription::CloseNotify => constant_cstr!("CN"), + AlertDescription::UnexpectedMessage => constant_cstr!("UM"), + AlertDescription::BadRecordMac => constant_cstr!("BM"), + AlertDescription::DecryptionFailed => constant_cstr!("DC"), + AlertDescription::RecordOverflow => constant_cstr!("RO"), + AlertDescription::DecompressionFailure => constant_cstr!("DF"), + AlertDescription::HandshakeFailure => constant_cstr!("HF"), + AlertDescription::NoCertificate => constant_cstr!("NC"), + AlertDescription::BadCertificate => constant_cstr!("BC"), + AlertDescription::UnsupportedCertificate => constant_cstr!("UC"), + AlertDescription::CertificateRevoked => constant_cstr!("CR"), + AlertDescription::CertificateExpired => constant_cstr!("CE"), + AlertDescription::CertificateUnknown => constant_cstr!("CU"), + AlertDescription::IllegalParameter => constant_cstr!("IP"), + AlertDescription::UnknownCA => constant_cstr!("CA"), + AlertDescription::AccessDenied => constant_cstr!("AD"), + AlertDescription::DecodeError => constant_cstr!("DE"), + AlertDescription::DecryptError => constant_cstr!("CY"), + AlertDescription::ExportRestriction => constant_cstr!("ER"), + AlertDescription::ProtocolVersion => constant_cstr!("PV"), + AlertDescription::InsufficientSecurity => constant_cstr!("IS"), + AlertDescription::InternalError => constant_cstr!("IE"), + AlertDescription::UserCanceled => constant_cstr!("US"), + AlertDescription::NoRenegotiation => constant_cstr!("NR"), + AlertDescription::UnsupportedExtension => constant_cstr!("UE"), + AlertDescription::CertificateUnobtainable => constant_cstr!("CO"), + AlertDescription::UnrecognisedName => constant_cstr!("UN"), + AlertDescription::BadCertificateStatusResponse => { + constant_cstr!("BR") + } + AlertDescription::BadCertificateHashValue => constant_cstr!("BH"), + AlertDescription::UnknownPSKIdentity => constant_cstr!("UP"), + // these not supported by openssl: + // AlertDescription::NoApplicationProtocol => constant_cstr!("no application protocol"), + // AlertDescription::InappropriateFallback => constant_cstr!("inappropriate fallback"), + // AlertDescription::MissingExtension => constant_cstr!("missing extension"), + // AlertDescription::CertificateRequired => constant_cstr!("certificate required"), + _ => constant_cstr!("UK"), + } +} diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs new file mode 100644 index 0000000..16acd1b --- /dev/null +++ b/rustls-libssl/src/entry.rs @@ -0,0 +1,1194 @@ +//! 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, ptr}; +use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_void}; +use std::sync::Mutex; +use std::{fs, io, path::PathBuf}; + +use openssl_sys::{ + stack_st_X509, OPENSSL_malloc, EVP_PKEY, X509, X509_STORE, X509_STORE_CTX, + X509_V_ERR_UNSPECIFIED, +}; + +use crate::bio::{Bio, BIO, BIO_METHOD}; +use crate::error::{ffi_panic_boundary, Error, MysteriouslyOppositeReturnValue}; +use crate::ffi::{ + free_arc, to_arc_mut_ptr, try_clone_arc, try_mut_slice_int, try_ref_from_ptr, try_slice, + 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 } } + }; +} + +macro_rules! entry_stub { + (pub fn $name:ident($($args:tt)*);) => { + #[no_mangle] + pub extern "C" fn $name($($args)*) { + ffi_panic_boundary! { + Error::not_supported(stringify!($name)).raise().into() + } + } + }; + (pub fn $name:ident($($args:tt)*) -> $ret:ty;) => { + #[no_mangle] + pub extern "C" fn $name($($args)*) -> $ret { + ffi_panic_boundary! { + Error::not_supported(stringify!($name)).raise().into() + } + } + }; +} + +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}"); + C_INT_SUCCESS + } +} + +entry! { + pub fn _SSL_alert_desc_string_long(value: c_int) -> *const c_char { + crate::constants::alert_desc_to_long_string(value).as_ptr() as *const c_char + } +} + +entry! { + pub fn _SSL_alert_desc_string(value: c_int) -> *const c_char { + crate::constants::alert_desc_to_short_string(value).as_ptr() as *const c_char + } +} + +entry! { + pub fn _BIO_f_ssl() -> *const BIO_METHOD { + &crate::bio::SSL_BIO_METHOD + } +} + +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_up_ref(ctx: *mut SSL_CTX) -> c_int { + let ctx = try_clone_arc!(ctx); + mem::forget(ctx.clone()); + C_INT_SUCCESS + } +} + +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 ctx = try_clone_arc!(ctx); + ctx.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 ctx = try_clone_arc!(ctx); + ctx.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 ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|mut ctx| ctx.set_options(op)) + .unwrap_or_default() + } +} + +macro_rules! num_enum { + ($enum_vis:vis enum $enum_name:ident + { $( $enum_var:ident = $enum_val:expr ),* $(,)? } + ) => { + #[derive(Debug, PartialEq, Clone, Copy)] + $enum_vis enum $enum_name { + $( $enum_var),* + } + + impl From<$enum_name> for c_int { + fn from(item: $enum_name) -> Self { + match item { + $( $enum_name::$enum_var => $enum_val),* + } + } + } + + impl TryFrom for $enum_name { + type Error = (); + fn try_from(i: c_int) -> Result { + match i { + $( $enum_val => Ok(Self::$enum_var), )* + _ => Err(()), + } + } + } + } +} + +num_enum! { + enum SslCtrl { + Mode = 33, + SetMsgCallbackArg = 16, + SetTlsExtHostname = 55, + SetMaxProtoVersion = 124, + } +} + +entry! { + pub fn _SSL_CTX_ctrl( + _ctx: *mut SSL_CTX, + cmd: c_int, + larg: c_long, + _parg: *mut c_void, + ) -> c_long { + match SslCtrl::try_from(cmd) { + Ok(SslCtrl::Mode) => { + log::warn!("unimplemented SSL_CTX_set_mode()"); + 0 + } + Ok(SslCtrl::SetMsgCallbackArg) => { + log::warn!("unimplemented SSL_CTX_set_msg_callback_arg()"); + 0 + } + Ok(SslCtrl::SetMaxProtoVersion) => { + log::warn!("unimplemented SSL_CTX_set_max_proto_version()"); + 1 + } + Ok(SslCtrl::SetTlsExtHostname) => { + // not a defined operation in the OpenSSL API + 0 + } + Err(()) => { + log::warn!("unimplemented _SSL_CTX_ctrl(..., {cmd}, {larg}, ...)"); + 0 + } + } + } +} + +entry! { + pub fn _SSL_CTX_set_verify(ctx: *mut SSL_CTX, mode: c_int, callback: SSL_verify_cb) { + let ctx = try_clone_arc!(ctx); + + if callback.is_some() { + // support verify callbacks would mean we need to fully use + // the openssl certificate verifier, because X509_STORE and + // X509_STORE_CTX are both in libcrypto. + return Error::not_supported("verify callback").raise().into(); + } + + ctx.lock() + .ok() + .map(|mut ctx| ctx.set_verify(crate::VerifyMode::from(mode))) + .unwrap_or_default(); + } +} + +pub type SSL_verify_cb = + Option c_int>; + +entry! { + pub fn _SSL_CTX_get_cert_store(ctx: *const SSL_CTX) -> *mut X509_STORE { + let ctx = try_clone_arc!(ctx); + ctx.lock() + .ok() + .map(|ctx| ctx.get_x509_store()) + .unwrap_or(ptr::null_mut()) + } +} + +fn load_verify_files(ctx: &Mutex, file_names: impl Iterator) -> c_int { + let mut certs = Vec::new(); + for file_name in file_names { + let mut file_reader = match fs::File::open(file_name.clone()) { + Ok(content) => io::BufReader::new(content), + Err(err) => return Error::from_io(err).raise().into(), + }; + + for cert in rustls_pemfile::certs(&mut file_reader) { + match cert { + Ok(cert) => certs.push(cert), + Err(err) => { + log::trace!("Failed to parse {file_name:?}: {err:?}"); + return Error::from_io(err).raise().into(); + } + }; + } + } + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ctx| ctx.add_trusted_certs(certs)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } +} + +entry! { + pub fn _SSL_CTX_load_verify_file(ctx: *mut SSL_CTX, ca_file: *const c_char) -> c_int { + let ctx = try_clone_arc!(ctx); + let ca_file = try_str!(ca_file); + let path_buf = PathBuf::from(ca_file); + load_verify_files(ctx.as_ref(), [path_buf].into_iter()) + } +} + +entry! { + pub fn _SSL_CTX_load_verify_dir(ctx: *mut SSL_CTX, ca_dir: *const c_char) -> c_int { + let ctx = try_clone_arc!(ctx); + let ca_dir = try_str!(ca_dir); + + let entries = match fs::read_dir(ca_dir) { + Ok(iter) => iter, + Err(err) => return Error::from_io(err).raise().into(), + } + .filter_map(|entry| entry.ok()) + .map(|dir_entry| dir_entry.path()); + + load_verify_files(ctx.as_ref(), entries) + } +} + +entry! { + pub fn _SSL_CTX_set_alpn_protos( + ctx: *mut SSL_CTX, + protos: *const c_uchar, + protos_len: c_uint, + ) -> MysteriouslyOppositeReturnValue { + let ctx = try_clone_arc!(ctx); + let slice = try_slice!(protos, protos_len); + + let alpn = match crate::parse_alpn(slice) { + Some(alpn) => alpn, + None => { + // nb. openssl doesn't add anything to the error stack + // in this case. + return Error::bad_data("invalid alpn protocols").raise().into(); + } + }; + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ctx| ctx.set_alpn_offer(alpn)) + { + Err(e) => e.raise().into(), + Ok(()) => MysteriouslyOppositeReturnValue::success(), + } + } +} + +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_up_ref(ssl: *mut SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + mem::forget(ssl.clone()); + C_INT_SUCCESS + } +} + +entry! { + pub fn _SSL_free(ssl: *mut SSL) { + free_arc(ssl); + } +} + +entry! { + pub fn _SSL_ctrl(ssl: *mut SSL, cmd: c_int, larg: c_long, parg: *mut c_void) -> c_long { + let ssl = try_clone_arc!(ssl); + + match SslCtrl::try_from(cmd) { + Ok(SslCtrl::Mode) => { + log::warn!("unimplemented SSL_set_mode()"); + 0 + } + Ok(SslCtrl::SetMsgCallbackArg) => { + log::warn!("unimplemented SSL_set_msg_callback_arg()"); + 0 + } + Ok(SslCtrl::SetMaxProtoVersion) => { + log::warn!("unimplemented SSL_set_max_proto_version()"); + 1 + } + Ok(SslCtrl::SetTlsExtHostname) => { + let hostname = try_str!(parg as *const c_char); + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_sni_hostname(hostname)) + .unwrap_or_default() as c_long + } + Err(()) => { + log::warn!("unimplemented _SSL_ctrl(..., {cmd}, {larg}, ...)"); + 0 + } + } + } +} + +entry! { + pub fn _SSL_set_ex_data(_ssl: *mut SSL, _idx: c_int, _data: *mut c_void) -> c_int { + 0 + } +} + +entry! { + pub fn _SSL_get_ex_data(_ssl: *const SSL, _idx: c_int) -> *mut c_void { + ptr::null_mut() + } +} + +entry! { + pub fn _SSL_get_options(ssl: *const SSL) -> u64 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_options()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_clear_options(ssl: *mut SSL, op: u64) -> u64 { + let ssl = try_clone_arc!(ssl); + ssl.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 ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_options(op)) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_set_alpn_protos( + ssl: *mut SSL, + protos: *const c_uchar, + protos_len: c_uint, + ) -> MysteriouslyOppositeReturnValue { + let ssl = try_clone_arc!(ssl); + let slice = try_slice!(protos, protos_len); + + let alpn = match crate::parse_alpn(slice) { + Some(alpn) => alpn, + None => { + // nb. openssl doesn't add anything to the error stack + // in this case. + return Error::bad_data("invalid alpn protocols").raise().into(); + } + }; + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.set_alpn_offer(alpn)) + { + Err(e) => e.raise().into(), + Ok(()) => MysteriouslyOppositeReturnValue::success(), + } + } +} + +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_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_set_fd(ssl: *mut SSL, fd: c_int) -> c_int { + let ssl = try_clone_arc!(ssl); + let bio = Bio::new_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_set_bio(ssl: *mut SSL, rbio: *mut BIO, wbio: *mut BIO) { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_bio_pair(Some(rbio), Some(wbio))) + .unwrap_or_default(); + } +} + +entry! { + pub fn _SSL_set0_rbio(ssl: *mut SSL, rbio: *mut BIO) { + let ssl = try_clone_arc!(ssl); + if rbio.is_null() { + return; + } + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_bio_pair(Some(rbio), None)) + .unwrap_or_default(); + } +} + +entry! { + pub fn _SSL_set0_wbio(ssl: *mut SSL, wbio: *mut BIO) { + let ssl = try_clone_arc!(ssl); + if wbio.is_null() { + return; + } + ssl.lock() + .ok() + .map(|mut ssl| ssl.set_bio_pair(None, Some(wbio))) + .unwrap_or_default(); + } +} + +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(()) => C_INT_SUCCESS, + } + } +} + +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_shutdown(ssl: *mut SSL) -> c_int { + const ERROR: c_int = -1; + let ssl = try_clone_arc!(ssl, ERROR); + + match ssl + .lock() + .map_err(|_| Error::cannot_lock()) + .and_then(|mut ssl| ssl.try_shutdown()) + .map_err(|err| err.raise()) + { + Err(_e) => ERROR, + Ok(result) => match result { + crate::ShutdownResult::Sent => 0 as c_int, + crate::ShutdownResult::Received => 1 as c_int, + }, + } + } +} + +entry! { + pub fn _SSL_get_shutdown(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + + ssl.lock().map(|ssl| ssl.get_shutdown()).unwrap_or_default() + } +} + +entry! { + pub fn _SSL_set_shutdown(ssl: *mut SSL, flags: c_int) { + let ssl = try_clone_arc!(ssl); + + ssl.lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.set_shutdown(flags)) + .map_err(|err| err.raise()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_pending(ssl: *const SSL) -> c_int { + let ssl = try_clone_arc!(ssl); + + ssl.lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.get_pending_plaintext() as c_int) + .map_err(|err| err.raise()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_has_pending(ssl: *const SSL) -> c_int { + (_SSL_pending(ssl) > 0) as c_int + } +} + +entry! { + pub fn _SSL_get_error(ssl: *const SSL, _ret_code: c_int) -> c_int { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ssl| ssl.get_error() as c_int) + .map_err(|err| err.raise()) + .unwrap_or_default() + } +} + +entry! { + pub fn _SSL_get0_alpn_selected(ssl: *const SSL, data: *mut *const c_uchar, len: *mut c_uint) { + if data.is_null() || len.is_null() { + return; + } + + let ssl = try_clone_arc!(ssl); + + match ssl.lock().ok().and_then(|mut ssl| { + ssl.get_agreed_alpn().map(|proto| { + unsafe { + // nb. alpn protocols are limited to 255 octets + ptr::write(len, proto.len() as u32); + ptr::write(data, proto.as_ptr()); + }; + }) + }) { + Some(()) => {} + None => unsafe { + println!("ALPN none"); + ptr::write(len, 0); + ptr::write(data, ptr::null()); + }, + } + } +} + +entry! { + pub fn _SSL_get_peer_cert_chain(ssl: *const SSL) -> *mut stack_st_X509 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|mut ssl| ssl.get_peer_cert_chain().map(|x509| x509.pointer())) + .unwrap_or_else(ptr::null_mut) + } +} + +entry! { + pub fn _SSL_get0_verified_chain(ssl: *const SSL) -> *mut stack_st_X509 { + _SSL_get_peer_cert_chain(ssl) + } +} + +entry! { + pub fn _SSL_get0_peer_certificate(ssl: *const SSL) -> *mut X509 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|mut ssl| ssl.get_peer_cert().map(|x509| x509.borrow_ref())) + .unwrap_or_else(ptr::null_mut) + } +} + +entry! { + pub fn _SSL_get1_peer_certificate(ssl: *const SSL) -> *mut X509 { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|mut ssl| ssl.get_peer_cert().map(|x509| x509.up_ref())) + .unwrap_or_else(ptr::null_mut) + } +} + +entry! { + pub fn _SSL_get_current_cipher(ssl: *const SSL) -> *const SSL_CIPHER { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|ssl| ssl.get_negotiated_cipher_suite_id()) + .and_then(crate::SslCipher::find_by_id) + .map(|cipher| cipher as *const SSL_CIPHER) + .unwrap_or_else(ptr::null) + } +} + +entry! { + pub fn _SSL_get_version(ssl: *const SSL) -> *const c_char { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .and_then(|ssl| ssl.get_negotiated_cipher_suite_id()) + .and_then(crate::SslCipher::find_by_id) + .map(|cipher| cipher.version.as_ptr()) + .unwrap_or_else(ptr::null) + } +} + +entry! { + pub fn _SSL_get_verify_result(ssl: *const SSL) -> c_long { + let ssl = try_clone_arc!(ssl); + ssl.lock() + .ok() + .map(|ssl| ssl.get_last_verification_result()) + .unwrap_or(X509_V_ERR_UNSPECIFIED as i64) + } +} + +impl Castable for SSL { + type Ownership = OwnershipArc; + type RustType = Mutex; +} + +type SSL_CIPHER = crate::SslCipher; + +entry! { + pub fn _SSL_CIPHER_find(_ssl: *const SSL, ptr: *const c_uchar) -> *const SSL_CIPHER { + let slice = try_slice!(ptr, 2); + let id = (slice[0] as u16) << 8 | (slice[1] as u16); + crate::SslCipher::find_by_id(rustls::CipherSuite::from(id)) + .map(|cipher| cipher as *const SSL_CIPHER) + .unwrap_or_else(ptr::null) + } +} + +entry! { + pub fn _SSL_CIPHER_get_bits(cipher: *const SSL_CIPHER, alg_bits: *mut c_int) -> c_int { + let cipher = try_ref_from_ptr!(cipher); + let bits = cipher.bits as c_int; + if !alg_bits.is_null() { + unsafe { ptr::write(alg_bits, bits) }; + } + bits + } +} + +entry! { + pub fn _SSL_CIPHER_get_version(cipher: *const SSL_CIPHER) -> *const c_char { + let cipher = try_ref_from_ptr!(cipher); + cipher.version.as_ptr() + } +} + +entry! { + pub fn _SSL_CIPHER_get_name(cipher: *const SSL_CIPHER) -> *const c_char { + let cipher = try_ref_from_ptr!(cipher); + cipher.openssl_name.as_ptr() + } +} + +entry! { + pub fn _SSL_CIPHER_standard_name(cipher: *const SSL_CIPHER) -> *const c_char { + let cipher = try_ref_from_ptr!(cipher); + cipher.standard_name.as_ptr() + } +} + +entry! { + pub fn _SSL_CIPHER_get_id(cipher: *const SSL_CIPHER) -> u32 { + let cipher = try_ref_from_ptr!(cipher); + cipher.openssl_id() + } +} + +entry! { + pub fn _SSL_CIPHER_get_protocol_id(cipher: *const SSL_CIPHER) -> u16 { + let cipher = try_ref_from_ptr!(cipher); + cipher.protocol_id() + } +} + +entry! { + pub fn _SSL_CIPHER_description( + cipher: *const SSL_CIPHER, + mut buf: *mut c_char, + mut size: c_int, + ) -> *mut c_char { + let cipher = try_ref_from_ptr!(cipher); + let required_len = cipher.description.to_bytes_with_nul().len(); + + if buf.is_null() { + // safety: `required_len` is a compile-time constant, and is + // a reasonable quantity to ask `OPENSSL_malloc` for. + // In C cast rules, any `*mut c_void` can be viewed as a + // `*mut c_char`. + let allocd = unsafe { OPENSSL_malloc(required_len) as *mut c_char }; + if allocd.is_null() { + return allocd; + } + buf = allocd; + size = required_len as i32; + } else if size < (required_len as i32) { + return ptr::null_mut(); + } + + unsafe { + ptr::copy_nonoverlapping(cipher.description.as_ptr(), buf, required_len as usize); + }; + buf + } +} + +impl Castable for SSL_CIPHER { + type Ownership = OwnershipRef; + type RustType = SSL_CIPHER; +} + +/// Normal OpenSSL return value convention success indicator. +/// +/// Compare [`crate::ffi::MysteriouslyOppositeReturnValue`]. +const C_INT_SUCCESS: c_int = 1; + +// --- unimplemented stubs below here --- + +// things we support and should be able to implement to +// some extent: + +entry_stub! { + pub fn _SSL_get_certificate(_ssl: *const SSL) -> *mut X509; +} + +entry_stub! { + pub fn _SSL_get_privatekey(_ssl: *const SSL) -> *mut EVP_PKEY; +} + +entry_stub! { + pub fn _SSL_set_session(_ssl: *mut SSL, _session: *mut SSL_SESSION) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_set_keylog_callback(_ctx: *mut SSL_CTX, _cb: SSL_CTX_keylog_cb_func); +} + +pub type SSL_CTX_keylog_cb_func = + Option; + +entry_stub! { + pub fn _SSL_CTX_add_client_CA(_ctx: *mut SSL_CTX, _x: *mut X509) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_check_private_key(_ctx: *const SSL_CTX) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_sess_set_new_cb(_ctx: *mut SSL_CTX, _new_session_cb: SSL_CTX_new_session_cb); +} + +pub type SSL_CTX_new_session_cb = + Option c_int>; + +entry_stub! { + pub fn _SSL_CTX_set_cipher_list(_ctx: *mut SSL_CTX, _s: *const c_char) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_set_ciphersuites(_ctx: *mut SSL_CTX, _s: *const c_char) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_use_PrivateKey(_ctx: *mut SSL_CTX, _pkey: *mut EVP_PKEY) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_use_PrivateKey_file( + _ctx: *mut SSL_CTX, + _file: *const c_char, + _type: c_int, + ) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_use_certificate(_ctx: *mut SSL_CTX, _x: *mut X509) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_use_certificate_chain_file(_ctx: *mut SSL_CTX, _file: *const c_char) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_use_certificate_file( + _ctx: *mut SSL_CTX, + _file: *const c_char, + _type_: c_int, + ) -> c_int; +} + +pub struct SSL_SESSION; + +entry_stub! { + pub fn _SSL_SESSION_free(_sess: *mut SSL_SESSION); +} + +// no individual message logging + +entry_stub! { + pub fn _SSL_CTX_set_msg_callback(_ctx: *mut SSL_CTX, _cb: SSL_CTX_msg_cb_func); +} + +pub type SSL_CTX_msg_cb_func = Option< + unsafe extern "C" fn( + write_p: c_int, + version: c_int, + content_type: c_int, + buf: *const c_void, + len: usize, + ssl: *mut SSL, + arg: *mut c_void, + ), +>; + +// no NPN (obsolete precursor to ALPN) + +entry_stub! { + pub fn _SSL_CTX_set_next_proto_select_cb( + _ctx: *mut SSL_CTX, + _cb: SSL_CTX_npn_select_cb_func, + _arg: *mut c_void, + ); +} + +pub type SSL_CTX_npn_select_cb_func = Option< + unsafe extern "C" fn( + s: *mut SSL, + out: *mut *mut c_uchar, + outlen: *mut c_uchar, + in_: *const c_uchar, + inlen: c_uint, + arg: *mut c_void, + ) -> c_int, +>; + +// no password-protected key loading + +entry_stub! { + pub fn _SSL_CTX_set_default_passwd_cb(_ctx: *mut SSL_CTX, _cb: pem_password_cb); +} + +pub type pem_password_cb = Option< + unsafe extern "C" fn( + buf: *mut c_char, + size: c_int, + rwflag: c_int, + userdata: *mut c_void, + ) -> c_int, +>; + +entry_stub! { + pub fn _SSL_CTX_set_default_passwd_cb_userdata(_ctx: *mut SSL_CTX, _u: *mut c_void); +} + +// no SRP + +entry_stub! { + pub fn _SSL_CTX_set_srp_password(_ctx: *mut SSL_CTX, _password: *mut c_char) -> c_int; +} + +entry_stub! { + pub fn _SSL_CTX_set_srp_username(_ctx: *mut SSL_CTX, _name: *mut c_char) -> c_int; +} + +// no post-handshake auth + +entry_stub! { + pub fn _SSL_CTX_set_post_handshake_auth(_ctx: *mut SSL_CTX, _val: c_int); +} + +entry_stub! { + pub fn _SSL_set_post_handshake_auth(_s: *mut SSL, _val: c_int); +} + +// --------------------- + +#[cfg(test)] +mod tests { + use super::*; + use core::ptr; + + #[test] + fn test_SSL_CTX_new_null() { + assert!(_SSL_CTX_new(ptr::null()).is_null()); + } + + #[test] + fn test_SSL_new_null() { + assert!(_SSL_new(ptr::null_mut()).is_null()); + } + + #[test] + fn test_SSL_up_ref_null() { + assert_eq!(_SSL_up_ref(ptr::null_mut()), 0); + } + + #[test] + fn test_SSL_free() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert!(!ctx.is_null()); + let ssl = _SSL_new(ctx); + assert!(!ssl.is_null()); + _SSL_free(ssl); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_free_after_up_ref() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert!(!ctx.is_null()); + let ssl = _SSL_new(ctx); + assert!(!ssl.is_null()); + assert_eq!(_SSL_up_ref(ssl), 1); + _SSL_free(ssl); // ref 2 + _SSL_free(ssl); // ref 1 + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_CTX_set_alpn_protos_works() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert_eq!( + _SSL_CTX_set_alpn_protos(ctx, b"\x05hello" as *const u8, 6).0, + 0i32 + ); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_CTX_set_alpn_protos_null_ctx() { + assert_eq!( + _SSL_CTX_set_alpn_protos(ptr::null_mut(), b"\x05hello" as *const u8, 6).0, + 1i32 + ); + } + + #[test] + fn test_SSL_CTX_set_alpn_protos_null_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert_eq!(_SSL_CTX_set_alpn_protos(ctx, ptr::null(), 6).0, 1i32); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_CTX_set_alpn_protos_invalid_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + assert_eq!( + _SSL_CTX_set_alpn_protos(ctx, b"\x05hell" as *const u8, 5).0, + 1i32 + ); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_set_alpn_protos_works() { + let ctx = _SSL_CTX_new(_TLS_method()); + let ssl = _SSL_new(ctx); + assert_eq!( + _SSL_set_alpn_protos(ssl, b"\x05hello" as *const u8, 6).0, + 0i32 + ); + _SSL_free(ssl); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_set_alpn_protos_null_ssl() { + assert_eq!( + _SSL_set_alpn_protos(ptr::null_mut(), b"\x05hello" as *const u8, 6).0, + 1i32 + ); + } + + #[test] + fn test_SSL_set_alpn_protos_null_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + let ssl = _SSL_new(ctx); + assert_eq!(_SSL_set_alpn_protos(ssl, ptr::null(), 6).0, 1i32); + _SSL_free(ssl); + _SSL_CTX_free(ctx); + } + + #[test] + fn test_SSL_set_alpn_protos_invalid_proto() { + let ctx = _SSL_CTX_new(_TLS_method()); + let ssl = _SSL_new(ctx); + assert_eq!( + _SSL_set_alpn_protos(ssl, b"\x05hell" as *const u8, 5).0, + 1i32 + ); + _SSL_free(ssl); + _SSL_CTX_free(ctx); + } +} diff --git a/rustls-libssl/src/error.rs b/rustls-libssl/src/error.rs new file mode 100644 index 0000000..ade2e91 --- /dev/null +++ b/rustls-libssl/src/error.rs @@ -0,0 +1,214 @@ +use core::ffi::{c_int, c_long}; +use core::ptr; +use std::ffi::CString; + +use openssl_sys::{ERR_new, ERR_set_error, ERR_RFLAGS_OFFSET, ERR_RFLAG_FATAL}; + +// See openssl/err.h for the source of these magic numbers. + +#[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 bad_data(hint: &str) -> Self { + Self { + lib: Lib::Ssl, + reason: Reason::OperationFailed, + 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 { + log::error!("raising {self:?}"); + let cstr = CString::new( + self.string + .clone() + .unwrap_or_else(|| format!("{:?}", self.reason)), + ) + .unwrap(); + #[cfg(not(miri))] + unsafe { + ERR_new(); + ERR_set_error(self.lib as c_int, self.reason as c_int, cstr.as_ptr()); + } + #[cfg(miri)] + eprintln!( + "miri: not calling ERR_new(); ERR_set_error({:?}, {:?}, {:?});", + self.lib, self.reason, cstr + ); + self + } +} + +// These conversions determine how errors are reported from entry point +// functions. + +impl From for *const T { + fn from(_: Error) -> Self { + ptr::null() + } +} + +impl From for *mut T { + fn from(_: Error) -> Self { + ptr::null_mut() + } +} + +impl From for c_int { + fn from(_: Error) -> Self { + // for typical OpenSSL functions (return 0 on error) + 0 + } +} + +impl From for MysteriouslyOppositeReturnValue { + fn from(_: Error) -> Self { + // for a small subset of OpenSSL functions (return 1 on error) + MysteriouslyOppositeReturnValue::error() + } +} + +impl From for c_long { + fn from(_: Error) -> Self { + // ditto + 0 + } +} + +impl From for u64 { + fn from(_: Error) -> Self { + // for options functions (return 0 on error) + 0 + } +} + +impl From for u32 { + fn from(_: Error) -> Self { + // for `SSL_CIPHER_get_id` + 0 + } +} + +impl From for u16 { + fn from(_: Error) -> Self { + // for `SSL_CIPHER_get_protocol_id` + 0 + } +} + +impl From for () { + fn from(_: Error) { + // 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; + +/// An entry point that yields this type marks it as one where +/// `0` is returned on success, `1` on error. +/// +/// It has the same representation as `c_int`. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct MysteriouslyOppositeReturnValue(pub c_int); + +impl MysteriouslyOppositeReturnValue { + pub fn error() -> Self { + Self(1) + } + + pub fn success() -> Self { + Self(0) + } +} diff --git a/rustls-libssl/src/ffi.rs b/rustls-libssl/src/ffi.rs new file mode 100644 index 0000000..d19ad08 --- /dev/null +++ b/rustls-libssl/src/ffi.rs @@ -0,0 +1,451 @@ +//! 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; + +macro_rules! try_slice { + ( $ptr:expr, $count:expr ) => { + if $ptr.is_null() { + return $crate::error::Error::null_pointer().raise().into(); + } else { + unsafe { ::core::slice::from_raw_parts($ptr, $count as usize) } + } + }; +} + +pub(crate) use try_slice; + +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..0fa54cc --- /dev/null +++ b/rustls-libssl/src/lib.rs @@ -0,0 +1,746 @@ +use core::ffi::{c_int, CStr}; +use std::io::{ErrorKind, Read, Write}; +use std::sync::{Arc, Mutex}; + +use openssl_sys::{ + SSL_ERROR_NONE, SSL_ERROR_SSL, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, X509_STORE, + X509_V_ERR_UNSPECIFIED, +}; +pub use rustls::crypto::ring as provider; +use rustls::{ + pki_types::{CertificateDer, ServerName}, + CipherSuite, ClientConfig, ClientConnection, Connection, RootCertStore, +}; + +mod bio; +#[macro_use] +mod constants; +#[allow( + // relax naming convention lints for openssl API + non_camel_case_types, + non_snake_case, + clippy::upper_case_acronyms, + // false positives on extern entrypoints + dead_code, +)] +mod entry; +#[macro_use] +#[allow(unused_macros, dead_code, unused_imports)] +mod ffi; +mod error; +mod verifier; +mod x509; + +/// `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) -> ConnMode { + match ( + self.client_versions.is_empty(), + self.server_versions.is_empty(), + ) { + (true, false) => ConnMode::Server, + (false, true) => ConnMode::Client, + (_, _) => ConnMode::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, +}; + +/// `SSL_CIPHER` underlying type. +/// +/// # Lifetime +/// Functions that return `SSL_CIPHER` give static-lifetime pointers. +pub struct SslCipher { + rustls: &'static rustls::SupportedCipherSuite, + pub bits: usize, + pub openssl_name: &'static CStr, + pub standard_name: &'static CStr, + pub version: &'static CStr, + pub description: &'static CStr, +} + +impl SslCipher { + pub fn find_by_id(id: CipherSuite) -> Option<&'static SslCipher> { + match id { + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => { + Some(&TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + } + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => { + Some(&TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + } + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => { + Some(&TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) + } + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => { + Some(&TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) + } + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => { + Some(&TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + } + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => { + Some(&TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) + } + CipherSuite::TLS13_AES_128_GCM_SHA256 => Some(&TLS13_AES_128_GCM_SHA256), + CipherSuite::TLS13_AES_256_GCM_SHA384 => Some(&TLS13_AES_256_GCM_SHA384), + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some(&TLS13_CHACHA20_POLY1305_SHA256), + _ => None, + } + } + + pub fn protocol_id(&self) -> u16 { + self.rustls.suite().get_u16() + } + + pub fn openssl_id(&self) -> u32 { + 0x03000000u32 | (self.protocol_id() as u32) + } +} + +static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + bits: 128, + openssl_name: constant_cstr!("ECDHE-ECDSA-AES128-GCM-SHA256"), + standard_name: constant_cstr!("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), + version: constant_cstr!("TLSv1.2"), + description: constant_cstr!("ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD\n"), +}; + +static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + bits: 256, + openssl_name: constant_cstr!("ECDHE-ECDSA-AES256-GCM-SHA384"), + standard_name: constant_cstr!("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"), + version: constant_cstr!("TLSv1.2"), + description: constant_cstr!("ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD\n"), +}; + +static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + bits: 256, + openssl_name: constant_cstr!("ECDHE-ECDSA-CHACHA20-POLY1305"), + standard_name: constant_cstr!("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"), + version: constant_cstr!("TLSv1.2"), + description: constant_cstr!("ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD\n"), +}; + +static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + bits: 128, + openssl_name: constant_cstr!("ECDHE-RSA-AES128-GCM-SHA256"), + standard_name: constant_cstr!("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"), + version: constant_cstr!("TLSv1.2"), + description: constant_cstr!("ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD\n"), +}; + +static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + bits: 256, + openssl_name: constant_cstr!("ECDHE-RSA-AES256-GCM-SHA384"), + standard_name: constant_cstr!("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + version: constant_cstr!("TLSv1.2"), + description: constant_cstr!("ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD\n"), +}; + +static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + bits: 256, + openssl_name: constant_cstr!("ECDHE-RSA-CHACHA20-POLY1305"), + standard_name: constant_cstr!("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"), + version: constant_cstr!("TLSv1.2"), + description: constant_cstr!("ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD\n"), +}; + +static TLS13_AES_128_GCM_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS13_AES_128_GCM_SHA256, + bits: 128, + openssl_name: constant_cstr!("TLS_AES_128_GCM_SHA256"), + standard_name: constant_cstr!("TLS_AES_128_GCM_SHA256"), + version: constant_cstr!("TLSv1.3"), + description: constant_cstr!("TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD\n"), +}; + +static TLS13_AES_256_GCM_SHA384: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS13_AES_256_GCM_SHA384, + bits: 256, + openssl_name: constant_cstr!("TLS_AES_256_GCM_SHA384"), + standard_name: constant_cstr!("TLS_AES_256_GCM_SHA384"), + version: constant_cstr!("TLSv1.3"), + description: constant_cstr!("TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD\n"), +}; + +static TLS13_CHACHA20_POLY1305_SHA256: SslCipher = SslCipher { + rustls: &provider::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, + bits: 256, + openssl_name: constant_cstr!("TLS_CHACHA20_POLY1305_SHA256"), + standard_name: constant_cstr!("TLS_CHACHA20_POLY1305_SHA256"), + version: constant_cstr!("TLSv1.3"), + description: constant_cstr!("TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD\n"), +}; + +pub struct SslContext { + method: &'static SslMethod, + raw_options: u64, + verify_mode: VerifyMode, + verify_roots: RootCertStore, + verify_x509_store: x509::OwnedX509Store, + alpn: Vec>, +} + +impl SslContext { + fn new(method: &'static SslMethod) -> Self { + Self { + method, + raw_options: 0, + verify_mode: VerifyMode::default(), + verify_roots: RootCertStore::empty(), + verify_x509_store: x509::OwnedX509Store::new(), + alpn: vec![], + } + } + + 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 set_verify(&mut self, mode: VerifyMode) { + self.verify_mode = mode; + } + + fn add_trusted_certs( + &mut self, + certs: Vec>, + ) -> Result<(), error::Error> { + for c in certs { + self.verify_roots + .add(c) + .map_err(error::Error::from_rustls)?; + } + Ok(()) + } + + fn get_x509_store(&self) -> *mut X509_STORE { + self.verify_x509_store.pointer() + } + + fn set_alpn_offer(&mut self, alpn: Vec>) { + self.alpn = alpn; + } +} + +/// Parse the ALPN wire format (which is used in the openssl API) +/// to rustls's internal representation. +/// +/// For an empty `slice`, returns `Some(vec![])`. +/// For a slice with invalid contents, returns `None`. Note that +/// +pub fn parse_alpn(mut slice: &[u8]) -> Option>> { + let mut out = vec![]; + + while !slice.is_empty() { + let len = *slice.first()? as usize; + if len == 0 { + return None; + } + let body = slice.get(1..1 + len)?; + out.push(body.to_vec()); + slice = &slice[1 + len..]; + } + + Some(out) +} + +struct Ssl { + ctx: Arc>, + raw_options: u64, + mode: ConnMode, + verify_mode: VerifyMode, + verify_server_name: Option>, + verify_roots: RootCertStore, + alpn: Vec>, + sni_server_name: Option>, + bio: Option, + conn: Option, + verifier: Option>, + peer_cert: Option, + peer_cert_chain: Option, + shutdown_flags: ShutdownFlags, +} + +impl Ssl { + fn new(ctx: Arc>) -> Self { + // extract and copy items from the `SslContext` that may + // be mutated separately at the level of a single `Ssl` + let (raw_options, verify_mode, verify_roots, alpn, mode) = ctx + .lock() + .ok() + .map(|ctx| { + ( + ctx.raw_options, + ctx.verify_mode, + ctx.verify_roots.clone(), + ctx.alpn.clone(), + ctx.method.mode(), + ) + }) + .unwrap_or(( + 0, + VerifyMode::default(), + RootCertStore::empty(), + vec![], + ConnMode::Unknown, + )); + + Self { + ctx, + raw_options, + mode, + verify_mode, + verify_server_name: None, + verify_roots, + alpn, + sni_server_name: None, + bio: None, + conn: None, + verifier: None, + peer_cert: None, + peer_cert_chain: None, + shutdown_flags: ShutdownFlags::default(), + } + } + + 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 set_alpn_offer(&mut self, alpn: Vec>) { + self.alpn = alpn; + } + + 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 = ConnMode::Client; + } + + fn server_mode(&mut self) { + self.mode = ConnMode::Server; + } + + fn is_server(&self) -> bool { + self.mode == ConnMode::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_sni_hostname(&mut self, hostname: &str) -> bool { + match ServerName::try_from(hostname).ok() { + Some(server_name) => { + self.sni_server_name = Some(server_name.to_owned()); + true + } + None => false, + } + } + + fn set_bio(&mut self, bio: bio::Bio) { + self.bio = Some(bio); + } + + fn set_bio_pair(&mut self, rbio: Option<*mut bio::BIO>, wbio: Option<*mut bio::BIO>) { + if let Some(bio) = &mut self.bio { + bio.update(rbio, wbio); + } else { + self.bio = Some(bio::Bio::new_pair(rbio, wbio)); + } + } + + fn init_client_conn(&mut self) -> Result<(), error::Error> { + // 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 provider = Arc::new(provider::default_provider()); + let verifier = Arc::new(verifier::ServerVerifier::new( + self.verify_roots.clone().into(), + provider.clone(), + self.verify_mode, + &self.verify_server_name, + )); + self.verifier = Some(verifier.clone()); + + let mut config = ClientConfig::builder_with_provider(provider) + .with_protocol_versions(method.client_versions) + .expect("bad versions") + .dangerous() + .with_custom_certificate_verifier(verifier) + .with_no_client_auth(); + + config.alpn_protocols = self.alpn.clone(); + + 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 connect(&mut self) -> Result<(), error::Error> { + self.client_mode(); + if self.conn.is_none() { + self.init_client_conn()?; + } + self.try_io() + } + + 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, read_count) = loop { + let late_err = self.try_io(); + + match &mut self.conn { + Some(ref mut conn) => match conn.reader().read(slice) { + Ok(read) => break (late_err, read), + Err(err) if err.kind() == ErrorKind::WouldBlock => { + // no data available, go around again. + continue; + } + Err(err) => { + return Err(error::Error::from_io(err)); + } + }, + None => break (late_err, 0), + }; + }; + + if read_count > 0 { + Ok(read_count) + } 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) => { + match conn.complete_io(bio) { + Ok(_) => {} + Err(e) => { + return Err(error::Error::from_io(e)); + } + }; + let io_state = conn + .process_new_packets() + .map_err(error::Error::from_rustls)?; + if io_state.peer_has_closed() { + self.shutdown_flags.set_received(); + } + Ok(()) + } + None => Ok(()), + } + } + + fn get_error(&mut self) -> c_int { + match &mut self.conn { + Some(ref mut conn) => { + if let Err(e) = conn.process_new_packets() { + error::Error::from_rustls(e).raise(); + return SSL_ERROR_SSL; + } + + if let Some(bio) = self.bio.as_ref() { + if bio.read_would_block() { + return SSL_ERROR_WANT_READ; + } else if bio.write_would_block() { + return SSL_ERROR_WANT_WRITE; + } + } + + SSL_ERROR_NONE + } + None => SSL_ERROR_SSL, + } + } + + fn try_shutdown(&mut self) -> Result { + if !self.shutdown_flags.is_sent() { + match &mut self.conn { + Some(ref mut conn) => conn.send_close_notify(), + None => (), + }; + + self.shutdown_flags.set_sent(); + } + + self.try_io()?; + Ok(if self.shutdown_flags.is_received() { + ShutdownResult::Received + } else { + ShutdownResult::Sent + }) + } + + fn get_shutdown(&self) -> i32 { + self.shutdown_flags.0 + } + + fn set_shutdown(&mut self, flags: i32) { + self.shutdown_flags.set(flags); + } + + fn get_pending_plaintext(&mut self) -> usize { + self.conn + .as_mut() + .and_then(|conn| { + let io_state = conn.process_new_packets().ok()?; + Some(io_state.plaintext_bytes_to_read()) + }) + .unwrap_or_default() + } + + fn get_agreed_alpn(&mut self) -> Option<&[u8]> { + self.conn.as_ref().and_then(|conn| conn.alpn_protocol()) + } + + fn init_peer_cert(&mut self) -> bool { + let conn = match &self.conn { + Some(conn) => conn, + None => return false, + }; + + let certs = match conn.peer_certificates() { + Some(certs) => certs, + None => return false, + }; + + let mut stack = x509::OwnedX509Stack::empty(); + for (i, cert) in certs.iter().enumerate() { + let converted = match x509::OwnedX509::parse_der(cert.as_ref()) { + Some(converted) => converted, + None => return false, + }; + + if i == 0 { + if !self.is_server() { + // See docs for `SSL_get_peer_cert_chain`: + // "If called on the client side, the stack also contains + // the peer's certificate; if called on the server side, the peer's + // certificate must be obtained separately" + stack.push(&converted); + } + self.peer_cert = Some(converted); + } else { + stack.push(&converted); + } + } + + self.peer_cert_chain = Some(stack); + true + } + + fn get_peer_cert(&mut self) -> Option<&x509::OwnedX509> { + if self.peer_cert.is_none() { + self.init_peer_cert(); + } + self.peer_cert.as_ref() + } + + fn get_peer_cert_chain(&mut self) -> Option<&x509::OwnedX509Stack> { + if self.peer_cert_chain.is_none() { + self.init_peer_cert(); + } + self.peer_cert_chain.as_ref() + } + + fn get_negotiated_cipher_suite_id(&self) -> Option { + self.conn + .as_ref() + .and_then(|conn| conn.negotiated_cipher_suite()) + .map(|suite| suite.suite()) + } + + fn get_last_verification_result(&self) -> i64 { + if let Some(verifier) = &self.verifier { + verifier.last_result() + } else { + X509_V_ERR_UNSPECIFIED as i64 + } + } +} + +#[derive(Default)] +struct Want { + read: bool, + write: bool, +} + +#[derive(PartialEq)] +enum ConnMode { + Unknown, + Client, + Server, +} + +#[repr(i32)] +enum ShutdownResult { + Sent = 0, + Received = 1, +} + +#[derive(Default)] +struct ShutdownFlags(i32); + +impl ShutdownFlags { + const SENT: i32 = 1; + const RECEIVED: i32 = 2; + + fn is_sent(&self) -> bool { + self.0 & ShutdownFlags::SENT == ShutdownFlags::SENT + } + + fn is_received(&self) -> bool { + self.0 & ShutdownFlags::RECEIVED == ShutdownFlags::RECEIVED + } + + fn set_sent(&mut self) { + self.0 |= ShutdownFlags::SENT; + } + + fn set_received(&mut self) { + self.0 |= ShutdownFlags::RECEIVED; + } + + fn set(&mut self, flags: i32) { + self.0 |= flags & (ShutdownFlags::SENT | ShutdownFlags::RECEIVED); + } +} + +#[derive(Default, Debug, Clone, Copy)] +pub struct VerifyMode(i32); + +impl VerifyMode { + const _NONE: i32 = 0x0; + const PEER: i32 = 0x1; + const FAIL_IF_NO_PEER_CERT: i32 = 0x2; + // other flags not mentioned here are not implemented. + + pub fn client_must_verify_server(&self) -> bool { + self.0 & VerifyMode::PEER == VerifyMode::PEER + } + + pub fn server_must_verify_client(&self) -> bool { + let bitmap = VerifyMode::PEER | VerifyMode::FAIL_IF_NO_PEER_CERT; + self.0 & bitmap == bitmap + } + + pub fn server_should_verify_client_but_allow_anon(&self) -> bool { + self.0 & (VerifyMode::PEER | VerifyMode::FAIL_IF_NO_PEER_CERT) == VerifyMode::PEER + } +} + +impl From for VerifyMode { + fn from(i: i32) -> Self { + Self(i) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_parse_alpn() { + assert_eq!(Some(vec![]), parse_alpn(&[])); + assert_eq!(Some(vec![b"hi".to_vec()]), parse_alpn(&b"\x02hi"[..])); + assert_eq!( + Some(vec![b"hi".to_vec(), b"world".to_vec()]), + parse_alpn(&b"\x02hi\x05world"[..]) + ); + + assert_eq!(None, parse_alpn(&[0])); + assert_eq!(None, parse_alpn(&[1])); + assert_eq!(None, parse_alpn(&[1, 1, 1])); + assert_eq!(None, parse_alpn(&[255])); + } +} diff --git a/rustls-libssl/src/verifier.rs b/rustls-libssl/src/verifier.rs new file mode 100644 index 0000000..61d4e51 --- /dev/null +++ b/rustls-libssl/src/verifier.rs @@ -0,0 +1,164 @@ +use core::sync::atomic::{AtomicI64, Ordering}; +use std::sync::Arc; + +use openssl_sys::{ + X509_V_ERR_CERT_HAS_EXPIRED, X509_V_ERR_CERT_NOT_YET_VALID, X509_V_ERR_CERT_REVOKED, + X509_V_ERR_HOSTNAME_MISMATCH, X509_V_ERR_INVALID_PURPOSE, + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, X509_V_ERR_UNSPECIFIED, X509_V_OK, +}; + +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, + CertificateError, DigitallySignedStruct, Error, RootCertStore, SignatureScheme, +}; + +use crate::VerifyMode; + +/// 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>, + + mode: VerifyMode, + + last_result: AtomicI64, +} + +impl ServerVerifier { + pub fn new( + root_store: Arc, + provider: Arc, + mode: VerifyMode, + hostname: &Option>, + ) -> Self { + Self { + root_store, + provider, + verify_hostname: hostname.clone(), + mode, + last_result: AtomicI64::new(X509_V_ERR_UNSPECIFIED as i64), + } + } + + pub fn last_result(&self) -> i64 { + self.last_result.load(Ordering::Acquire) + } + + fn verify_server_cert_inner( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + now: UnixTime, + ) -> Result<(), Error> { + 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(()) + } +} + +impl ServerCertVerifier for ServerVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _ignored_server_name: &ServerName<'_>, + _ocsp_response: &[u8], + now: UnixTime, + ) -> Result { + let result = self.verify_server_cert_inner(end_entity, intermediates, now); + + let openssl_rv = match &result { + Ok(()) => X509_V_OK, + Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)) => { + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY + } + Err(Error::InvalidCertificate(CertificateError::NotValidYet)) => { + X509_V_ERR_CERT_NOT_YET_VALID + } + Err(Error::InvalidCertificate(CertificateError::Expired)) => { + X509_V_ERR_CERT_HAS_EXPIRED + } + Err(Error::InvalidCertificate(CertificateError::Revoked)) => X509_V_ERR_CERT_REVOKED, + Err(Error::InvalidCertificate(CertificateError::InvalidPurpose)) => { + X509_V_ERR_INVALID_PURPOSE + } + Err(Error::InvalidCertificate(CertificateError::NotValidForName)) => { + X509_V_ERR_HOSTNAME_MISMATCH + } + // TODO: more mappings can go here + Err(_) => X509_V_ERR_UNSPECIFIED, + }; + self.last_result.store(openssl_rv as i64, Ordering::Release); + + // Call it success if it succeeded, or the `mode` says not to care. + if openssl_rv == X509_V_OK || !self.mode.client_must_verify_server() { + Ok(ServerCertVerified::assertion()) + } else { + Err(result.unwrap_err()) + } + } + + 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/src/x509.rs b/rustls-libssl/src/x509.rs new file mode 100644 index 0000000..7f65d9c --- /dev/null +++ b/rustls-libssl/src/x509.rs @@ -0,0 +1,127 @@ +use core::ffi::{c_int, c_long, c_void}; +use core::ptr; + +use openssl_sys::{ + d2i_X509, stack_st_X509, OPENSSL_sk_new_null, OPENSSL_sk_push, X509_STORE_free, X509_STORE_new, + X509_free, OPENSSL_STACK, X509, X509_STORE, +}; + +/// Safe, owning wrapper around an OpenSSL `STACK_OF(X509)` object. +pub struct OwnedX509Stack { + raw: *mut stack_st_X509, +} + +impl OwnedX509Stack { + pub fn empty() -> Self { + Self { + raw: unsafe { OPENSSL_sk_new_null() as *mut stack_st_X509 }, + } + } + + pub fn push(&mut self, cert: &OwnedX509) { + unsafe { + OPENSSL_sk_push( + self.raw as *mut OPENSSL_STACK, + cert.up_ref() as *const c_void, + ); + } + } + + /// Leaks our pointer to the caller. + /// + /// We retain ownership. The caller could modify the returned + /// object under our feet; that is an inherent property of the + /// OpenSSL `SSL_get_peer_cert_chain` API. + pub fn pointer(&self) -> *mut stack_st_X509 { + self.raw + } +} + +impl Drop for OwnedX509Stack { + fn drop(&mut self) { + unsafe { + OPENSSL_sk_free(self.raw as *mut OPENSSL_STACK); + } + } +} + +/// Safe, owning wrapper around an OpenSSL X509 object. +pub struct OwnedX509 { + raw: *mut X509, +} + +impl OwnedX509 { + /// Create a new one, from parsing DER certificate data. + pub fn parse_der(data: &[u8]) -> Option { + let raw = unsafe { + d2i_X509( + ptr::null_mut(), + &mut data.as_ptr() as *mut *const u8, + data.len() as c_long, + ) + }; + + if raw.is_null() { + None + } else { + Some(Self { raw }) + } + } + + /// Give out our reference. + /// + /// This DOES NOT take a reference. See `SSL_get0_peer_certificate`. + pub fn borrow_ref(&self) -> *mut X509 { + self.raw + } + + /// Give out a new reference. + /// + /// See `SSL_get1_peer_certificate`. + pub fn up_ref(&self) -> *mut X509 { + unsafe { + if !self.raw.is_null() { + X509_up_ref(self.raw); + } + } + self.raw + } +} + +impl Drop for OwnedX509 { + fn drop(&mut self) { + unsafe { + X509_free(self.raw); + } + } +} + +pub struct OwnedX509Store { + raw: *mut X509_STORE, +} + +impl OwnedX509Store { + pub fn new() -> Self { + Self { + raw: unsafe { X509_STORE_new() }, + } + } + + pub fn pointer(&self) -> *mut X509_STORE { + self.raw + } +} + +impl Drop for OwnedX509Store { + fn drop(&mut self) { + unsafe { + X509_STORE_free(self.raw); + } + } +} + +extern "C" { + /// XXX: these missing from openssl-sys(?) investigate why that is. + fn OPENSSL_sk_free(st: *mut OPENSSL_STACK); + fn X509_up_ref(x: *mut X509) -> c_int; +} diff --git a/rustls-libssl/test-ca/rsa/ca.cert b/rustls-libssl/test-ca/rsa/ca.cert new file mode 100644 index 0000000..5dac1c9 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/ca.cert @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFTCCAv2gAwIBAgIUZBEaAuV4ORnPH4GxeJGyEiqXUN8wDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMB4XDTIzMTIyMTE3MjMxNFoX +DTMzMTIxODE3MjMxNFowGjEYMBYGA1UEAwwPcG9ueXRvd24gUlNBIENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzvK/b5WhfthXBMVIHboJJuR9XuG9 ++ioSrlzwT9DW7QV31UpgBUZyf1nvT7CmDplNiWZtpqSdJ9pjskBIj5dv4m5cX8A9 +fK1IATdkd6j5/c2ZFkqi5k9iPeJa5rZY6SoGKgvBEr/Y5oiO8HZJZOFetafSr6zV +WRAsKlagrmiNS0oiWC0P0yPVWZyhlHHbtYrHtF/CuWEJ9HqzUk9KeTPwgjfphlYJ +YM0bCZzqN8TEbWPksU1WnmU15YbTgjwI0bNjUXA7W9LmMvbW7EXFJ2+LI+oiF3mk +TQEXqhfdTL9NtqAikD+cfAM1y5e5QSpi8dQuexBteFtXphRZzFk8M9DVKHyngKTH +/QZo6B4Gj9VPrNRPlbPkpbnu8JWD7hO/22VLU4YhghsdwQ/833pfokdV89NMoLo4 +JOUzbTTGtjH0bq6LWTMtLifuQ4H0D1WLtdy/EGgKptnTaeYaXNYT7+v+NNcBHaW8 +W3Orbx0s9IXgQnZTk1u03RbRdIxNxqm+HYEM8gT6S9IUymNZkzDCfZC0bC/saevd +zVE2xpZmuLOfhDl+EcalDYNPrM72+NzkAwRPFGec+bcUEhBxhvxpav+SxDiRC1gD +43qFU7hVfuqVH/EFp0lR3I3Xo8TZ5OIgEyJ5vQH5Ne1+C+sqdCqdGoqf1TZuIE80 +ZwKYcMnRwDXpiGsCAwEAAaNTMFEwHQYDVR0OBBYEFEIQj1cHn3me0sRqu6KjbEb2 +kb8rMB8GA1UdIwQYMBaAFEIQj1cHn3me0sRqu6KjbEb2kb8rMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggIBAEKDnm8x+NOaWNrPNH5kIlGD0tGdDJvw +/KvBZmkMcgFkb4zhbbeUmlu0j5CX4+6Lck0uw62zRgP4Nw3x36W3DL7pmoV4MYHC +djNG1ruNoojvgZyyGgMaSabto0nSHSE9opAorrSXB9RoOv2WcBuQSBNl72eaCQ1F +4kAYjKN6ZwPxEdTsdEmqWyUyEPy6E5kNoM0jW1uI2ZBxzbIOYeePvQ3muUSIMtmC +jShiEOOpmYpzENsAMouY3ZN+CWVS5kB5umnYSviQlAVEKSjC764FD9vMLL+rNhfP +fz+y6EhKcnnYy7mdXIRY73uh5eMyCLUO0yr2Y2ophhD8D79f2w7KtYjaSKfAch0L +lETe9Ch+fGDxUCph3J1IuR/3n01ZjB47WXu/yDZ6s7SHGXIgPaptzP+nZkDnmlZX +bvjB5s6r4U2spuqeLxrwd/1Jin7It+LOYLVmkihpbta9+/KKiXOuSYN1rSiQ2XKp +n1ZN0XxhcZzsALklBIU+Lm11b8gPVS7rXqll/sDmaAH9Iw+AXwUYjCb62Gy58yzu +uk3Q+msRr3oVI9bBhmEXmZxyENYJrw305qOlI3+tHBoJLUSP6zQ214aEu4trJr5K +kmbF7DZRG9MSBXeRk7e5ojK13xI1/XCjgIOTkGxF4rEFbVwhc0B8zS/2x3zw0fkE +M4J0J+gz0QYr +-----END CERTIFICATE----- diff --git a/rustls-libssl/test-ca/rsa/end.cert b/rustls-libssl/test-ca/rsa/end.cert new file mode 100644 index 0000000..fc36be9 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/end.cert @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEGDCCAoCgAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u +eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMTIyMTE3MjMxNVoX +DTI5MDYxMjE3MjMxNVowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDI1plwQmA+rr6Evlvn2hzIB/zYKlx +Tm14SPzomxpaf3OpzXzHuOn34yVvU1vTDijUl/YJbcnx052m0075SYeuW08VQB/p +zjhLrFp1ULSD272IddbB88T8Jq/VZ5dAxBB1q6Tm0vGBYQ8eIcmJv+fJQTbTXHKQ +KPHQRmVyqXVkwvwcgEJi9gpOcEEpJ6SDGdyGh1ck3wGnhrSpmsu+hwUVi7las+Ka +QUlvkmD+UGQKo8Ta91Xu8ja25QLTpjVcYxbi719rtA6I9DpX4+3aeIy/0s1xk0Xi +yMXIjSl3dn47gp3IZFRCECuoTdOwOZ9+y9ENnpHvZ84jCBXddU09qheJAgMBAAGj +gdYwgdMwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFAUefby7 +fPnK64BnGdIizLlWDkbPMEIGA1UdIwQ7MDmAFJCh13RFfpFyZ5LRR+A6ndRRvvGa +oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswUwYDVR0RBEwwSoIO +dGVzdHNlcnZlci5jb22HBMYzZAGCFXNlY29uZC50ZXN0c2VydmVyLmNvbYcQIAEN +uAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBgQC2gxGT +c3aayDPR5/ICnuK8aVSsM67xRsGsWvzBxdyyXaB+qSGxa+sCkw8guuAp2W7MrrO/ +zu7yhXDI2nyav6ZP6NvLABFYZ6pXokV/Hj4rQpCDVxvvDVh/E/rW/wx6z580zfdo +auHqUCD9QfR97nENwtGv7ESLN6qFeU0CsJd2GE3y3pnadlpCW3AYHeLX4crm7poj +SfG1F4ipqM1i0knQN86KBzG5PO49azGJGmcsu5lPFQgTRvvR7W+Niq/kIQPu00AW +so8aSB1gp2lypUGHqwsrd51yrQ374jKvXSDt1ptc0xDI9004TuYre9XQsyjxq4Qn +VjmpnKLCbumkrlELf93oomWxT5g6Nb/vu1TUgsrdWgDAW0AZ+rfcWorrZAjbvYw9 +4MJ1PdAmeg+sk51xoh2KF7syHPaMcNCaoSBtXrB5LqrAxixdmB6ORM/KlQU7h9A7 +X+WysEwMowV2MsGr8VsHxYTOpoiE8nUPfnRmbrGPpUU24bzvqTxtxxqQU14= +-----END CERTIFICATE----- diff --git a/rustls-libssl/test-ca/rsa/end.key b/rustls-libssl/test-ca/rsa/end.key new file mode 100644 index 0000000..cb236b6 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/end.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDI1plwQmA+rr6 +Evlvn2hzIB/zYKlxTm14SPzomxpaf3OpzXzHuOn34yVvU1vTDijUl/YJbcnx052m +0075SYeuW08VQB/pzjhLrFp1ULSD272IddbB88T8Jq/VZ5dAxBB1q6Tm0vGBYQ8e +IcmJv+fJQTbTXHKQKPHQRmVyqXVkwvwcgEJi9gpOcEEpJ6SDGdyGh1ck3wGnhrSp +msu+hwUVi7las+KaQUlvkmD+UGQKo8Ta91Xu8ja25QLTpjVcYxbi719rtA6I9DpX +4+3aeIy/0s1xk0XiyMXIjSl3dn47gp3IZFRCECuoTdOwOZ9+y9ENnpHvZ84jCBXd +dU09qheJAgMBAAECggEABbzZYJqPc/prWwUJzo1qXdA5AEf8U3eR4nKK9S/yU2zh +8sE3BQxb3M0SAbb6wTbuXmnlcxuGT5UAUrJt5QiTc739kktjZNWKdDcqJb7sv9/L +L+L/II7RYPSmQOkd2mqpbTxRyfOz5DD9Z85ohaNd5l4Dha13NOPvUEdxnjB7Yi4I +YbUYZ/Zq6MUosFRObS0XPBvk6d+zDI3WUZatVTNfuS7fqI1BvkXs5EkV34DQXgQs +LKb1LFAoEZoh64UnfkONIT9oG4OTbbaQ9gCbfQPpKw07GuaMdtjp0QD0yldOKvL4 +V4crSZyj2f3LnPCqCjUwcz6quKSUqUgosVApH+JCcQKBgQDpaGXy3laYTK3zbauh ++eHY5Ia+7fM2ETZx4LfwAYA1K5E84T98swpO571lZVrbOKjBukpUrbHdOcEVcBRH +BTvCh1vL1AXXWR0cCvWtp/WAbu90rDqgu6mxaD+V8wGq29URTWOCRG1WA7zeNHPB +0XAZPLQVeqeSvHGLSqyPf4aoHQKBgQDWBqvl5To7P4ZT6VFl18v0EB3zgzpRuyC3 +xKKz5mGw4tuvspdMU2XaOGQsRl5emMijGeII7JUweHbBdkq44S2FZ9wyn4+I+8Oi +Atu4Nce06ARnw+5RRcJlSs/LrExfOtxF3xp8EQqpL/jEO03n7G5cwcFwuWTKoVTI +0RwcuU4JXQKBgDBMCvRzb2W6UDBT3DT7GPGhcARoBnCEpUhxIH6IQPg/mKEJVvK9 +tX9YUod9row4MCtOGf1lp61IOxztgTSk75W0HpmRuNezt+NKnUWewJ0f12rEDKmf +y2BLWwTzMMAjFvaqldGpyRoIUfeE0QMlDFYcioL7S1uApNoWzJgw4jM9AoGBANIk +osuLku1xphbl08JHbD4rRP1AMBbnwWwuagJxhiID3OhaVivfBvaIv/Ko9Se0o+th +EorooGODJDc4So3Uqrl+DLq36Fr7uE5uuAXa6Ec8OHcZ7flmoUSLfBPjDOnEBVul +f3+py+nq7Drgb9H0VzhEFgb0QX6jgXfbudqKJ5ERAoGAR5Q6wQEoT9VT53nuKprl +3K/6agd+4wlpVQW0W1LImdrJHRHUXO7KJe7Bo5rtjL3lw8dCl3olHlLPJKg9frMn +ZWvJ2t0zca18S76rNcsPew2BecJxNRFlGwdcE1BBA2p/yzBhsZbIO7eqfh+dK5va +rnlrPNbWDhylxMaU4/CoU7k= +-----END PRIVATE KEY----- diff --git a/rustls-libssl/test-ca/rsa/inter.cert b/rustls-libssl/test-ca/rsa/inter.cert new file mode 100644 index 0000000..c46d110 --- /dev/null +++ b/rustls-libssl/test-ca/rsa/inter.cert @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEwDCCAqigAwIBAgIBezANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9wb255 +dG93biBSU0EgQ0EwHhcNMjMxMjIxMTcyMzE1WhcNMzMxMjE4MTcyMzE1WjAsMSow +KAYDVQQDDCFwb255dG93biBSU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwggGiMA0G +CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDC9IRIszo5zkkxlz+pkU8otrZ2Jwlf +BQ4j5heoPkpmvFss2fDElZZCVhZt54ehk2+oRuZhfgldfmuT0KCQEMnx8iN7K+pk +2LgaAVGzT4X/NBv+qamgkzRu9UvrS5NrlWutHsPPRt2TldVVJ1UEiLuWrrFMQwi+ +JATjgBHz6PhhD+UnPszZM/SJaBmtMXT99rO/sS6aaQhkZJCSDVVOnnecXafshkEF +tlMkKDRTTxxTOiTGu2NSH5MMzB3F952AiG8ZDONRSyBtxh/kpRV6+idO/4ufIQ3w +ZUPjLlRZIF9cDIGJXRU+cjYvMSV6yPzM2rP+67dPS9N7gQS1AFiMOlLQRbp3Sz9e +R6eetX/ggaHPcIzNv+pLp0L4+8PINZWhcJnZUlgkNR9Gg25mdPC6BLpWH20NH37V +VfSs40ytxHyw5QRokwwjcGUmlzXSJf0R+eUhXkJAmR+bgKbQKRbCW6M+byNdphfu +c3R2irNvRbYkwTOP3FvFkcC+cYyMIHyKihMCAwEAAaN/MH0wHQYDVR0OBBYEFJCh +13RFfpFyZ5LRR+A6ndRRvvGaMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEF +BQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAfBgNVHSMEGDAWgBRCEI9X +B595ntLEaruio2xG9pG/KzANBgkqhkiG9w0BAQsFAAOCAgEAxyqRDyGCkF07q/2P +44Mkwg+olQdT7moiO7V7MauwLZmCNqkr4hQcAk1NKi1QdYw/xCgd/x7PQ/INVMYN +oAG/xxr4nh0whygSsPGk9LkzoeG4dfeVv7tbsYw+4o7wU9kgCM1v0c2vMskyHh3F +vdMJV+5hWZqHZLUOZY1l9ziJysz/aSD4WpMtXdwT5fFgbJ8zggcMADkIESSBPrK5 +ykjFqFnoryK938IUw8fHEdU5ZdjM+1li4Q6P3YT6ovY9aA9gXbD/xb4mUb5kG+ug +tmGV+MDvi6Qgyt1O9ZgaW0tLdbjdxzTjEgU0KwUDpK6AZ9ebcyL5PGj3JA15ZPvS +36AHH/3N+u3w1Poyxb8NxyOgNY7AX3hRQax9G1/43F3VZ1C991xVrwWL++mRD+Ai +5FhMKjZ258+8DKgYaT2JIExwNWA5taafmR2CKpxgVWSFLha/WogJH3kyyTJHXLjU +Bm5qvwqWAvS3Px+WkSbtqFKRDCs+oaj2wGGuwxqEEEriMJ26AC3Si2n9k0a17TOj +lezKgblBHlpokEgcqOkRDB8k1g/Hkx7eRX4RlBRJ4PVRFT6qSTyy3dESsWhb7Sz2 ++uB8SQIYH+5QXwD3MpNrg2BILQYtcciPiGmLNyQB3ZvJUKcj0n63CjxAfcSnbkUF +AnF6iUVbZu9AMRaBDiRdNLGnBms= +-----END CERTIFICATE----- diff --git a/rustls-libssl/tests/ciphers.c b/rustls-libssl/tests/ciphers.c new file mode 100644 index 0000000..de8fbf1 --- /dev/null +++ b/rustls-libssl/tests/ciphers.c @@ -0,0 +1,48 @@ +/** + * Exercises SSL_CIPHER functions like `SSL_CIPHER_get_protocol_id` + */ + +#include + +#include + +static void cipher(SSL *ssl, uint16_t protocol_id) { + const uint8_t id_bytes[2] = { + (protocol_id & 0xff00) >> 8, + protocol_id & 0xff, + }; + const SSL_CIPHER *const cipher = SSL_CIPHER_find(ssl, id_bytes); + if (!cipher) { + printf("Nothing found for 0x%04x\n", protocol_id); + return; + } + printf("Found for 0x%04x\n", protocol_id); + printf("openssl_id=0x%08x protocol_id=0x%08x bits=%d\n", + SSL_CIPHER_get_id(cipher), SSL_CIPHER_get_protocol_id(cipher), + SSL_CIPHER_get_bits(cipher, NULL)); + printf("name='%s' standard_name='%s' version='%s'\n", + SSL_CIPHER_get_name(cipher), SSL_CIPHER_standard_name(cipher), + SSL_CIPHER_get_version(cipher)); + char *desc = SSL_CIPHER_description(cipher, NULL, 0); + printf("desc='%s'\n", desc); + OPENSSL_free(desc); +} + +int main(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + SSL *ssl = SSL_new(ctx); + // We only care about SSL_CIPHERs representing suites implemented + // by rustls: this is not exhaustive. + cipher(ssl, 0xc02b); // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + cipher(ssl, 0xc02c); // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + cipher(ssl, 0xcca9); // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + cipher(ssl, 0xc02f); // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + cipher(ssl, 0xc030); // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + cipher(ssl, 0xcca8); // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + cipher(ssl, 0x1301); // TLS13_AES_128_GCM_SHA256 + cipher(ssl, 0x1302); // TLS13_AES_256_GCM_SHA384 + cipher(ssl, 0x1303); // TLS13_CHACHA20_POLY1305_SHA256 + SSL_free(ssl); + SSL_CTX_free(ctx); + return 0; +} diff --git a/rustls-libssl/tests/client.c b/rustls-libssl/tests/client.c new file mode 100644 index 0000000..a6e500e --- /dev/null +++ b/rustls-libssl/tests/client.c @@ -0,0 +1,141 @@ +/** + * Simple client test program. + * + * Expects to connect to an `openssl s_server -rev` server. + */ + +#include +#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 void *buf, int n) { + const uint8_t *ubuf = (const uint8_t *)buf; + printf("%s (%d bytes): ", label, n); + for (int i = 0; i < n; i++) { + printf("%02x", ubuf[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(int argc, char **argv) { + if (argc != 4) { + printf("%s \n\n", argv[0]); + return 1; + } + + const char *host = argv[1], *port = argv[2], *cacert = argv[3]; + + struct addrinfo *result = NULL; + TRACE(getaddrinfo(host, port, 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(); + if (strcmp(cacert, "insecure") != 0) { + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + dump_openssl_error_stack(); + TRACE(SSL_CTX_load_verify_file(ctx, cacert)); + dump_openssl_error_stack(); + } else { + printf("certificate verification disabled\n"); + } + TRACE(SSL_CTX_set_alpn_protos(ctx, (const uint8_t *)"\x02hi\x05world", 9)); + dump_openssl_error_stack(); + SSL *ssl = SSL_new(ctx); + dump_openssl_error_stack(); + TRACE(SSL_set1_host(ssl, host)); + dump_openssl_error_stack(); + TRACE(SSL_set_fd(ssl, sock)); + dump_openssl_error_stack(); + TRACE(SSL_connect(ssl)); + dump_openssl_error_stack(); + + // check the alpn (also sees that SSL_connect completed handshake) + const uint8_t *alpn_ptr = NULL; + unsigned int alpn_len = 0; + SSL_get0_alpn_selected(ssl, &alpn_ptr, &alpn_len); + hexdump("alpn", alpn_ptr, alpn_len); + + printf("version: %s\n", SSL_get_version(ssl)); + printf("verify-result: %ld\n", SSL_get_verify_result(ssl)); + printf("cipher: %s\n", SSL_CIPHER_standard_name(SSL_get_current_cipher(ssl))); + + // check the peer certificate and chain + X509 *cert = SSL_get1_peer_certificate(ssl); + if (cert) { + char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + printf("server subject: %s\n", name); + free(name); + } else { + printf("server cert absent\n"); + } + X509_free(cert); + + STACK_OF(X509) *chain = SSL_get_peer_cert_chain(ssl); + if (chain) { + printf("%d certs in server chain\n", sk_X509_num(chain)); + for (int i = 0; i < sk_X509_num(chain); i++) { + X509 *cert = sk_X509_value(chain, i); + char *name = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + printf(" %d: %s\n", i, name); + free(name); + } + } else { + printf("server cert chain absent\n"); + } + + // write some data and close + int wr = TRACE(SSL_write(ssl, "hello", 5)); + dump_openssl_error_stack(); + assert(wr == 5); + TRACE(SSL_shutdown(ssl)); + dump_openssl_error_stack(); + + // read back data, using SSL_pending on the way + char buf[10] = {0}; + int rd = TRACE(SSL_read(ssl, buf, 1)); + dump_openssl_error_stack(); + TRACE(SSL_pending(ssl)); + dump_openssl_error_stack(); + TRACE(SSL_has_pending(ssl)); + dump_openssl_error_stack(); + int rd2 = TRACE(SSL_read(ssl, buf + 1, sizeof(buf) - 1)); + hexdump("result", buf, rd + rd2); + assert(memcmp(buf, "olleh\n", 6) == 0); + + close(sock); + SSL_free(ssl); + SSL_CTX_free(ctx); + + printf("PASS\n\n"); + return 0; +} diff --git a/rustls-libssl/tests/constants.c b/rustls-libssl/tests/constants.c new file mode 100644 index 0000000..df08867 --- /dev/null +++ b/rustls-libssl/tests/constants.c @@ -0,0 +1,15 @@ +/** + * Exercises openssl functions like `SSL_alert_desc_string_long` + */ + +#include + +#include + +int main(void) { + for (int i = -1; i < 260; i++) { + printf("%d: '%s' '%s'\n", i, SSL_alert_desc_string(i), + SSL_alert_desc_string_long(i)); + } + return 0; +} diff --git a/rustls-libssl/tests/runner.rs b/rustls-libssl/tests/runner.rs new file mode 100644 index 0000000..8ec13c6 --- /dev/null +++ b/rustls-libssl/tests/runner.rs @@ -0,0 +1,150 @@ +use std::process::{Child, Command, Output, Stdio}; +use std::{net, thread, time}; + +/* Note: + * + * In the tests below, we are relying on the fact that cargo sets + * `LD_LIBRARY_PATH` (or equivalent) so that artifacts it built + * are preferred to others. This means processes we run from here + * that depend on OpenSSL will use our libssl.so. + * + * We set LD_LIBRARY_PATH="" to disable this where we want + * to actually use OpenSSL's libssl. + */ + +#[test] +#[ignore] +fn client() { + let _server = KillOnDrop( + Command::new("openssl") + .args(&[ + "s_server", + "-cert", + "test-ca/rsa/end.cert", + "-cert_chain", + "test-ca/rsa/inter.cert", + "-key", + "test-ca/rsa/end.key", + "-alpn", + "hello,world", + "-accept", + "localhost:4443", + "-rev", + ]) + .env("LD_LIBRARY_PATH", "") + .spawn() + .expect("failed to start openssl s_server"), + ); + + wait_for_port(4443); + + let openssl_insecure_output = Command::new("target/client") + .env("LD_LIBRARY_PATH", "") + .args(&["localhost", "4443", "insecure"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_insecure_output = Command::new("target/client") + .args(&["localhost", "4443", "insecure"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_insecure_output, rustls_insecure_output); + + let openssl_secure_output = Command::new("target/client") + .env("LD_LIBRARY_PATH", "") + .args(&["localhost", "4443", "test-ca/rsa/ca.cert"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_secure_output = Command::new("target/client") + .args(&["localhost", "4443", "test-ca/rsa/ca.cert"]) + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_secure_output, rustls_secure_output); +} + +#[test] +#[ignore] +fn constants() { + let openssl_output = Command::new("target/constants") + .env("LD_LIBRARY_PATH", "") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_output = Command::new("target/constants") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_output, rustls_output); +} + +#[test] +#[ignore] +fn ciphers() { + let openssl_output = Command::new("target/ciphers") + .env("LD_LIBRARY_PATH", "") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + let rustls_output = Command::new("target/ciphers") + .stdout(Stdio::piped()) + .output() + .map(print_output) + .unwrap(); + + assert_eq!(openssl_output, rustls_output); +} + +struct KillOnDrop(Child); + +impl Drop for KillOnDrop { + fn drop(&mut self) { + self.0.kill().expect("failed to kill subprocess"); + self.0.wait().expect("failed to wait for killed subprocess"); + } +} + +fn print_output(out: Output) -> Output { + println!("status: {:?}\n", out.status); + println!( + "stdout:\n{}\n", + String::from_utf8(out.stdout.clone()).unwrap() + ); + println!( + "stderr:\n{}\n", + String::from_utf8(out.stderr.clone()).unwrap() + ); + out +} + +/// Wait until we can connect to localhost:port. +fn wait_for_port(port: u16) -> Option<()> { + let mut count = 0; + loop { + thread::sleep(time::Duration::from_millis(500)); + if net::TcpStream::connect(("localhost", port)).is_ok() { + return Some(()); + } + println!("waiting for port {port}"); + count += 1; + if count == 10 { + return None; + } + } +}