diff --git a/.github/workflows/pkg-config.yaml b/.github/workflows/pkg-config.yaml index a9ec44db..e99fc4b9 100644 --- a/.github/workflows/pkg-config.yaml +++ b/.github/workflows/pkg-config.yaml @@ -20,6 +20,7 @@ jobs: matrix: cc: [ clang, gcc ] os: [ ubuntu-latest, macos-latest ] + crypto: [ aws-lc-rs, ring ] steps: - name: Checkout sources uses: actions/checkout@v4 @@ -54,10 +55,10 @@ jobs: # that will complicate setting PKG_CONFIG_PATH/LD_LIBRARY_PATH. run: > CARGOFLAGS=--libdir=lib - make --file=Makefile.pkg-config PREFIX=${PREFIX} install + make --file=Makefile.pkg-config PREFIX=${PREFIX} CRYPTO_PROVIDER=${{ matrix.crypto }} install - name: Build the client/server examples - run: PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig make --file=Makefile.pkg-config PROFILE=debug + run: PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig make --file=Makefile.pkg-config PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} - name: Verify client is dynamically linked (Ubuntu) if: matrix.os == 'ubuntu-latest' @@ -76,4 +77,4 @@ jobs: run: LD_LIBRARY_PATH=$PREFIX/lib otool -L target/server | grep "rustls" - name: Run the integration tests - run: LD_LIBRARY_PATH=$PREFIX/lib make --file=Makefile.pkg-config PROFILE=debug integration + run: LD_LIBRARY_PATH=$PREFIX/lib make --file=Makefile.pkg-config PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} integration diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5ddba23f..5642c762 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,8 +16,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # test a bunch of toolchains on ubuntu - cc: [clang, gcc] + # test a bunch of toolchain and crypto providers on ubuntu + cc: [ clang, gcc ] + crypto: [ aws-lc-rs, ring ] rust: - stable - beta @@ -25,11 +26,12 @@ jobs: # MSRV - keep in sync with what rustls and rustls-platform-verifier # consider MSRV - 1.64.0 - os: [ubuntu-latest] - # but only stable on macos/windows (slower platforms) + os: [ ubuntu-latest ] + # but only stable, clang, and aws-lc-rs on macos (slower platform) include: - os: macos-latest cc: clang + crypto: aws-lc-rs rust: stable steps: - name: Checkout sources @@ -41,11 +43,19 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - - env: + - name: Unit tests + env: CARGO_UNSTABLE_HTTP_REGISTRY: true - run: make CC=${{ matrix.cc }} PROFILE=debug test integration + run: make CC=${{ matrix.cc }} PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} test - name: Platform verifier connect test - run: make PROFILE=debug connect-test + run: make PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} connect-test + - name: Integration tests + env: + CARGO_UNSTABLE_HTTP_REGISTRY: true + # Note: we run this after the connect-tests because the static libs test rebuilds the crate + # squashing whatever RUSTFLAGS the Makefile has set and producing a librustls_ffi.a + # for the default build config. + run: make CC=${{ matrix.cc }} PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} integration - name: Verify debug builds were using ASAN if: runner.os == 'Linux' # For 'nm' run: | @@ -54,7 +64,7 @@ jobs: - name: Build release binaries run: | make clean - make CC=${{ matrix.cc }} PROFILE=release + make CC=${{ matrix.cc }} CRYPTO_PROVIDER=${{ matrix.crypto }} PROFILE=release - name: Verify release builds were not using ASAN if: runner.os == 'Linux' # For 'nm' run: | @@ -75,18 +85,23 @@ jobs: test-windows-cmake-debug: name: Windows CMake, Debug configuration runs-on: windows-latest + strategy: + matrix: + crypto: [ aws-lc-rs, ring ] steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Install nightly rust toolchain uses: dtolnay/rust-toolchain@nightly + - name: Install NASM for aws-lc-rs + uses: ilammy/setup-nasm@v1 - name: Configure CMake - run: cmake -S . -B build + run: cmake -DCRYPTO_PROVIDER="${{ matrix.crypto }}" -S . -B build - name: Build, debug configuration run: cmake --build build --config Debug - name: Integration test, debug configuration - run: cargo test --locked --test client_server client_server_integration -- --ignored --exact + run: cargo test --no-default-features --features="${{ matrix.crypto }}" --locked --test client_server client_server_integration -- --ignored --exact env: CLIENT_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Debug\client.exe SERVER_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Debug\server.exe @@ -94,18 +109,23 @@ jobs: test-windows-cmake-release: name: Windows CMake, Release configuration runs-on: windows-latest + strategy: + matrix: + crypto: [ aws-lc-rs, ring ] steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Install nightly rust toolchain uses: dtolnay/rust-toolchain@nightly + - name: Install NASM for aws-lc-rs + uses: ilammy/setup-nasm@v1 - name: Configure CMake - run: cmake -S . -B build + run: cmake -DCRYPTO_PROVIDER="${{ matrix.crypto }}" -S . -B build - name: Build, release configuration run: cmake --build build --config Release - name: Integration test, release configuration - run: cargo test --locked --test client_server client_server_integration -- --ignored --exact + run: cargo test --no-default-features --features="${{ matrix.crypto }}" --locked --test client_server client_server_integration -- --ignored --exact env: CLIENT_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Release\client.exe SERVER_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Release\server.exe @@ -123,7 +143,7 @@ jobs: # reliably matched to CI. There can be non-semantic differences in # output between point releases of cbindgen that will fail this check # otherwise. - run: cargo install cbindgen --force --version 0.24.5 + run: cargo install cbindgen --force --version 0.27.0 - run: touch src/lib.rs - run: cbindgen --version - run: make src/rustls.h @@ -161,8 +181,11 @@ jobs: - name: Build client/server binaries run: make target/client target/server - - name: cargo test (debug; all features; -Z minimal-versions) - run: cargo -Z minimal-versions test --all-features --locked + - name: cargo test (debug; default features; -Z minimal-versions) + run: cargo -Z minimal-versions test --locked + + - name: cargo test (debug; ring; -Z minimal-versions) + run: cargo -Z minimal-versions test --no-default-features --features=ring --locked format: name: Format @@ -200,6 +223,8 @@ jobs: # 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 --all-targets -- -D warnings -A unknown-lints + - name: Check clippy (ring) + run: cargo clippy --locked --workspace --all-targets --no-default-features --features=ring -- -D warnings -A unknown-lints clippy-nightly-optional: name: Clippy nightly (optional) @@ -215,6 +240,8 @@ jobs: components: clippy - name: Check clippy (default features) run: cargo clippy --locked --workspace --all-targets -- -D warnings + - name: Check clippy (ring) + run: cargo clippy --locked --workspace --all-targets --no-default-features --features=ring -- -D warnings - name: Check clippy (all features) # We only test --all-features on nightly, because two of the features # (read_buf, core_io_borrowed_buf) require nightly. diff --git a/CHANGELOG.md b/CHANGELOG.md index d989e4c6..d8a713fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,137 @@ # Changelog +## 0.14.0 (2024-08-01) + +This release updates to [Rustls 0.23.12][] and changes the rustls-ffi API to allow +choosing a cryptography provider to use with Rustls. + +The default provider has been changed to match the Rustls default, +[`aws-lc-rs`][]. Users that wish to continue using `*ring*` as the provider may +opt-in. See the `README` for more detail on supported platforms and build +requirements. + +[Rustls 0.23.12]: https://github.com/rustls/rustls/releases/tag/v%2F0.23.12 +[`aws-lc-rs`]: https://github.com/aws/aws-lc-rs + +### Added + +* A new `rustls_crypto_provider` type has been added to represent + `rustls::CryptoProvider` instances. + * The current process-wide default crypto provider (if any) can be retrieved + with `rustls_crypto_provider_default()`. + * If rustls-ffi was built with `aws-lc-rs`, (`DEFINE_AWS_LC_RS` is true), then + `rustls_aws_lc_rs_crypto_provider()` can be used to retrieve the `aws-lc-rs` + provider. + * If rustls-ffi was built with `ring`, (`DEFINE_RING` is true), then + `rustls_ring_crypto_provider()` can be used to retrieve the `aws-lc-rs` + provider. + * Ciphersuites supported by a specific `rustls_crypto_provider` can be retrieved with + `rustls_crypto_provider_ciphersuites_len()` and `rustls_crypto_provider_ciphersuites_get()`. + * Ciphersuites supported by the current process-wide default crypto provider (if any) can + be retrieved with `rustls_default_crypto_provider_ciphersuites_len()` and + `rustls_default_crypto_provider_ciphersuites_get()`. + +* A new `RUSTLS_RESULT_NO_DEFAULT_CRYPTO_PROVIDER` `rustls_result` was added to + indicate when an operation that requires a process-wide default crypto + provider fails because no provider has been installed as the default, or + the default was not implicit based on supported provider. + +* A new `rustls_crypto_provider_builder` type has been added to customize, or + install, a crypto provider. + * `rustls_crypto_provider_builder_new_from_default` will construct a builder + based on the current process-wide default. + * `rustls_crypto_provider_builder_new_with_base` will construct a builder + based on a specified `rustls_crypto_provider`. + * Customization of supported ciphersuites can be achieved with + `rustls_crypto_provider_builder_set_cipher_suites()`. + * The default process-wide provider can be installed from a builder using + `rustls_crypto_provider_builder_build_as_default()`, if it has not already + been done. + * Or, a new `rustls_crypto_provider` instance built with + `rustls_crypto_provider_builder_build()`. + * See the function documentation for more information on recommended + workflows. + +* A new `rustls_signing_key` type has been added to represent a private key + that has been parsed by a `rustls_crypto_provider` and is ready to use for + cryptographic operations. + * Use `rustls_crypto_provider_load_key()` to load a `signing_key` from + a buffer of PEM data using a `rustls_crypto_provider`. + * Use `rustls_certified_key_build_with_signing_key()` to build + a `rustls_certified_key` with a PEM cert chain and a `rustls_signing_key`. + +* New `rustls_web_pki_client_cert_verifier_builder_new_with_provider()` and + `rustls_web_pki_server_cert_verifier_builder_new_with_provider()` + functions have been added to construct `rustls_client_cert_verifier` or + `rustls_server_cert_verifier` instances that use a specified + `rustls_crypto_provider`. + +* Support for constructing a `rustls_server_cert_verifier` that uses the + platform operating system's native certificate verification functionality was + added. See the [`rustls-platform-verifier`] crate docs for + more information on supported platforms. + * Use `rustls_platform_server_cert_verifier()` to construct a platform verifier + that uses the default crypto provider. + * Use `rustls_platform_server_cert_verifier_with_provider()` to construct a + platform verifier that uses the specified `rustls_crypto_provider`. + * The returned `rustls_server_cert_verifier` can be used with + a `rustls_client_config_builder` with + `rustls_client_config_builder_set_server_verifier()`. + +* When using `aws-lc-rs` as the crypto provider, NIST P-521 signatures are now + supported. + +[`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier + +### Changed + +* `rustls_server_config_builder_new()`, `rustls_client_config_builder_new()`, + `rustls_web_pki_client_cert_verifier_builder_new()`, and + `rustls_web_pki_server_cert_verifier_builder_new()`, and + `rustls_certified_key_build` functions now use the process + default crypto provider instead of being hardcoded to use `ring`. + +* `rustls_server_config_builder_new_custom()` and + `rustls_client_config_builder_new_custom()` no longer take custom + ciphersuites as an argument. Instead they require providing + a `rustls_crypto_provider`. + * Customizing ciphersuite support is now done at the provider level using + `rustls_crypto_provider_builder` and + `rustls_crypto_provider_builder_set_cipher_suites()`. + +* `rustls_server_config_builder_build()` and + `rustls_client_config_builder_build()` now use out-parameters for the + `rustls_server_config` or `rustls_client_config`, and return a `rustls_result`. + This allows returning an error if the build operation fails because a suitable + crypto provider was not available. + +* `rustls_client_config_builder_build()` now returns + a `RUSTLS_RESULT_NO_SERVER_CERT_VERIFIER` `rustls_result` error if a server + certificate verifier was not set instead of falling back to a verifier that + would fail all certificate validation attempts. + +* The `NoneVerifier` used if a `rustls_client_config` is constructed by + a `rustls_client_config_builder` without a verifier configured has been + changed to return an unknown issuer error instead of a bad signature error + when asked to verify a server certificate. + +* Error specificity for revoked certificates was improved. + +### Removed + +* The `ALL_CIPHER_SUITES` and `DEFAULT_CIPHER_SUITES` constants and associated + functions (`rustls_all_ciphersuites_len()`, + `rustls_all_ciphersuites_get_entry()`, `rustls_default_ciphersuites_len()` and + `rustls_default_ciphersuites_get_entry()`) have been + removed. Ciphersuite support is dictated by the `rustls_crypto_provider`. + * Use `rustls_default_supported_ciphersuites()` to retrieve + a `rustls_supported_ciphersuites` for the default `rustls_crypto_provider`. + * Use `rustls_crypto_provider_ciphersuites()` to retrieve a + `rustls_supported_ciphersuites` for a given `rustls_crypto_provider`. + * Use `rustls_supported_ciphersuites_len()` and + `rustls_supported_ciphersuites_get()` to iterate the + `rustls_supported_ciphersuites`. + ## 0.13.0 (2024-03-28) This release updates to [Rustls 0.23.4] and continues to use `*ring*` as the diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c230978..eb884b6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,38 @@ cmake_minimum_required(VERSION 3.15) project(rustls-ffi) + +set(CRYPTO_PROVIDER "aws-lc-rs" CACHE STRING "Crypto provider to use (aws-lc-rs or ring)") + +if (NOT (CRYPTO_PROVIDER STREQUAL "aws-lc-rs" OR CRYPTO_PROVIDER STREQUAL "ring")) + message(FATAL_ERROR "Invalid crypto provider specified: ${CRYPTO_PROVIDER}. Must be 'aws-lc-rs' or 'ring'.") +endif () + +set(CARGO_FEATURES --no-default-features) +if (CRYPTO_PROVIDER STREQUAL "aws-lc-rs") + list(APPEND CARGO_FEATURES --features=aws-lc-rs) +elseif (CRYPTO_PROVIDER STREQUAL "ring") + list(APPEND CARGO_FEATURES --features=ring) +endif () + add_subdirectory(tests) include(ExternalProject) set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust) ExternalProject_Add( - rustls-ffi - DOWNLOAD_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - COMMAND cargo build --locked "$,--release,-->" - # Rely on cargo checking timestamps, rather than tell CMake where every - # output is. - BUILD_ALWAYS true - INSTALL_COMMAND "" - # Run cargo test with --quiet because msbuild will treat the presence - # of "error" in stdout as an error, and we have some test functions that - # end in "_error". Quiet mode suppresses test names, so this is a - # sufficient workaround. - TEST_COMMAND cargo test --locked "$,--release,-->" --quiet + rustls-ffi + DOWNLOAD_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + COMMAND cargo build --locked ${CARGO_FEATURES} "$,--release,-->" + # Rely on cargo checking timestamps, rather than tell CMake where every + # output is. + BUILD_ALWAYS true + INSTALL_COMMAND "" + # Run cargo test with --quiet because msbuild will treat the presence + # of "error" in stdout as an error, and we have some test functions that + # end in "_error". Quiet mode suppresses test names, so this is a + # sufficient workaround. + TEST_COMMAND cargo test --locked ${CARGO_FEATURES} "$,--release,-->" --quiet ) diff --git a/Cargo.lock b/Cargo.lock index 5bdf036e..e451d65e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,18 +17,74 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "aws-lc-rs" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "base64" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bytes" version = "1.6.0" @@ -41,6 +97,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -50,12 +107,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "combine" version = "4.6.6" @@ -82,6 +168,34 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getrandom" version = "0.2.11" @@ -93,12 +207,27 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -109,6 +238,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "jni" version = "0.19.0" @@ -129,12 +267,49 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libloading" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.22" @@ -147,6 +322,28 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom8" version = "0.2.0" @@ -197,6 +394,22 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "prettyplease" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -258,12 +471,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" -version = "0.23.4" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -275,7 +508,7 @@ dependencies = [ [[package]] name = "rustls-ffi" -version = "0.13.0" +version = "0.14.0-rc1" dependencies = [ "libc", "log", @@ -290,9 +523,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -340,16 +573,17 @@ dependencies = [ [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -385,7 +619,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -432,6 +666,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "spin" version = "0.9.8" @@ -539,13 +779,25 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -714,3 +966,17 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 2df7c77a..99d4e5fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-ffi" -version = "0.13.0" +version = "0.14.0-rc1" license = "Apache-2.0 OR ISC OR MIT" readme = "README-crates.io.md" description = "Rustls bindings for non-Rust languages" @@ -12,6 +12,7 @@ links = "rustls_ffi" rust-version = "1.64" [features] +default = ["aws-lc-rs"] # Enable this feature when building as Rust dependency. It inhibits the # default behavior of capturing the global logger, which only works when # built using the Makefile, which passes -C metadata=rustls-ffi to avoid @@ -20,12 +21,14 @@ rust-version = "1.64" no_log_capture = [] read_buf = ["rustls/read_buf"] capi = [] +ring = ["rustls/ring", "webpki/ring"] +aws-lc-rs = ["rustls/aws-lc-rs", "webpki/aws_lc_rs"] [dependencies] # Keep in sync with RUSTLS_CRATE_VERSION in build.rs -rustls = { version = "0.23.4", default-features = false, features = ["ring", "std", "tls12"] } +rustls = { version = "0.23.12", default-features = false, features = ["std", "tls12"] } pki-types = { package = "rustls-pki-types", version = "1", features = ["std"] } -webpki = { package = "rustls-webpki", version = "0.102.0", default-features = false, features = ["ring", "std"] } +webpki = { package = "rustls-webpki", version = "0.102.0", default-features = false, features = ["std"] } libc = "0.2" rustls-pemfile = "2" log = "0.4.22" diff --git a/Makefile b/Makefile index c76bdc86..0199d87c 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ CARGOFLAGS += --locked CFLAGS := -Werror -Wall -Wextra -Wpedantic -g -I src/ PROFILE := release +CRYPTO_PROVIDER := aws-lc-rs DESTDIR=/usr/local ifeq ($(PROFILE), debug) @@ -26,6 +27,14 @@ ifneq (,$(TARGET)) CARGOFLAGS += --target $(TARGET) endif +ifeq ($(CRYPTO_PROVIDER), aws-lc-rs) + CFLAGS += -D DEFINE_AWS_LC_RS + CARGOFLAGS += --no-default-features --features aws-lc-rs +else ifeq ($(CRYPTO_PROVIDER), ring) + CFLAGS += -D DEFINE_RING + CARGOFLAGS += --no-default-features --features ring +endif + all: target/client target/server test: all diff --git a/Makefile.pkg-config b/Makefile.pkg-config index bfdcd3bf..fe25c925 100644 --- a/Makefile.pkg-config +++ b/Makefile.pkg-config @@ -13,6 +13,7 @@ CARGOFLAGS += --locked CFLAGS := -Werror -Wall -Wextra -Wpedantic -g -I src/ PROFILE := release +CRYPTO_PROVIDER := aws-lc-rs PREFIX=/usr/local ifeq ($(PROFILE), debug) @@ -25,6 +26,14 @@ ifeq ($(PROFILE), release) CARGOFLAGS += --release endif +ifeq ($(CRYPTO_PROVIDER), aws-lc-rs) + CFLAGS += -D DEFINE_AWS_LC_RS + CARGOFLAGS += --no-default-features --features aws-lc-rs +else ifeq ($(CRYPTO_PROVIDER), ring) + CFLAGS += -D DEFINE_RING + CARGOFLAGS += --no-default-features --features ring +endif + all: target/client target/server integration: all diff --git a/README.md b/README.md index 9c81462e..c96dce3f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,63 @@ to provide the cryptographic primitives. # Build You'll need to [install the Rust toolchain](https://rustup.rs/) (version 1.64 -or above) and a C compiler (`gcc` and `clang` should both work). +or above) and a C compiler (`gcc` and `clang` should both work). + +## Cryptography provider + +Both rustls and rustls-ffi support choosing a cryptography provider for +implementing the cryptography required for TLS. By default, both will use +[`aws-lc-rs`][], but [`*ring*`][] is available as an opt-in choice. + +It is **not** presently supported to build with both cryptography providers +activated, or with neither provider activated. + +### Choosing a provider + +#### Make + +When building with the `Makefile`, or example `Makefile.pkg-config` specify +a `CRYPTO_PROVIDER` as a makefile variable. E.g.: + +* `make` to build with the default (`aws-lc-rs`). +* `make CRYPTO_PROVIDER=aws-lc-rs` to build with `aws-lc-rs` explicitly. +* `make CRYPTO_PROVIDER=ring` to build with `*ring*`. + +#### CMake + +When building with `cmake`, specify a `CRYPTO_PROVIDER` as a cmake cache entry +variable with `-DCRYPTO_PROVIDER`. E.g.: + +* `cmake -S . -B build; cmake --build build --config Release` - to build with + the default (`aws-lc-rs`). +* `cmake -DCRYPTO_PROVIDER=aws-lc-rs -S . -B build; cmake --build build --config + Release` - to build with `aws-lc-rs` explicitly. +* `cmake -DCRYPTO_PROVIDER=ring -S . -B build; cmake --build build --config + Release` - to build with `aws-lc-rs` explicitly. + +#### Cargo-c + +When building with the experimental [`cargo-c`] support, use `--features` to +specify which provider to use. E.g.: + +* `cargo cinstall` to build with the default (`aws-lc-rs`). +* `cargo cinstall --features aws-lc-rs` to build with `aws-lc-rs` explicitly. +* `cargo cinstall --no-default-features --features ring` to build with `*ring*`. + +[`cargo-c`]: https://github.com/lu-zero/cargo-c + +### Cryptography provider build requirements + +For more information on cryptography provider builder requirements and supported +platforms see the upstream documentation: + +* [`aws-lc-rs` platforms and requirements][] +* [`*ring*` supported platforms][] + +[`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs +[`aws-lc-rs` platforms and requirements]: https://aws.github.io/aws-lc-rs/requirements/index.html +[`*ring*`]: https://crates.io/crates/ring +[`*ring*` supported platforms]: https://github.com/briansmith/ring/blob/2e8363b433fa3b3962c877d9ed2e9145612f3160/include/ring-core/target.h#L18-L64 ## Static Library diff --git a/build.rs b/build.rs index 5e4c14ee..c5370df5 100644 --- a/build.rs +++ b/build.rs @@ -8,7 +8,7 @@ use std::{env, fs, path::PathBuf}; // because doing so would require a heavy-weight deserialization lib dependency // (and it couldn't be a _dev_ dep for use in a build script) or doing brittle // by-hand parsing. -const RUSTLS_CRATE_VERSION: &str = "0.23.4"; +const RUSTLS_CRATE_VERSION: &str = "0.23.12"; fn main() { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); @@ -19,13 +19,21 @@ fn main() { println!("cargo:include={}", include_dir.to_str().unwrap()); + let rustls_crypto_provider = { + if cfg!(all(feature = "ring", not(feature = "aws-lc-rs"))) { + "ring" + } else { + "aws-lc-rs" + } + }; + let dest_path = out_dir.join("version.rs"); let mut f = File::create(dest_path).expect("Could not create file"); let pkg_version = env!("CARGO_PKG_VERSION"); writeln!( &mut f, - r#"const RUSTLS_FFI_VERSION: &str = "rustls-ffi/{}/rustls/{}";"#, - pkg_version, RUSTLS_CRATE_VERSION + r#"const RUSTLS_FFI_VERSION: &str = "rustls-ffi/{}/rustls/{}/{}";"#, + pkg_version, RUSTLS_CRATE_VERSION, rustls_crypto_provider ) .expect("Could not write file"); diff --git a/cbindgen.toml b/cbindgen.toml index 335688d7..b2bf782f 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -12,7 +12,9 @@ include = ["rustls_tls_version"] [defines] "feature = read_buf" = "DEFINE_READ_BUF" +"feature = aws-lc-rs" = "DEFINE_AWS_LC_RS" +"feature = ring" = "DEFINE_RING" [parse.expand] crates = ["rustls-ffi"] -features = ["read_buf"] +features = ["read_buf", "aws-lc-rs", "ring"] diff --git a/src/acceptor.rs b/src/acceptor.rs index edac4904..c9049e29 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -518,9 +518,9 @@ mod tests { use libc::c_char; use rustls::internal::msgs::codec::Codec; use rustls::internal::msgs::enums::AlertLevel; - use rustls::{AlertDescription, ContentType, ProtocolVersion}; + use rustls::{AlertDescription, ContentType, ProtocolVersion, SignatureScheme}; - use crate::cipher::rustls_certified_key; + use crate::cipher::{rustls_certified_key, rustls_server_cert_verifier}; use crate::client::{rustls_client_config, rustls_client_config_builder}; use crate::server::rustls_server_config_builder; @@ -641,7 +641,19 @@ mod tests { protocols_slices.len(), ); - let config = ccb::rustls_client_config_builder_build(builder); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); + + let mut config = null(); + let result = ccb::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut client_conn = null_mut(); let result = rustls_client_config::rustls_client_connection_new( config, @@ -662,6 +674,7 @@ mod tests { rustls_connection::rustls_connection_free(client_conn); rustls_client_config::rustls_client_config_free(config); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); buf } @@ -686,8 +699,11 @@ mod tests { assert_eq!(result, rustls_result::Ok); rustls_certified_key::rustls_certified_key_free(certified_key); - let config = rustls_server_config_builder::rustls_server_config_builder_build(builder); - assert_ne!(config, null()); + let mut config = null(); + let res = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(res, rustls_result::Ok); + assert!(!config.is_null()); config } @@ -734,10 +750,28 @@ mod tests { } // Sort to ensure consistent comparison signature_schemes.sort(); - assert_eq!( - &signature_schemes, - &[1025, 1027, 1281, 1283, 1537, 2052, 2053, 2054, 2055] - ); + + #[cfg_attr(feature = "ring", allow(unused_mut))] + let mut expected_schemes = vec![ + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + ]; + #[cfg(feature = "aws-lc-rs")] // aws-lc-rs also includes P-521. + expected_schemes.push(SignatureScheme::ECDSA_NISTP521_SHA512); + + let mut expected_schemes = expected_schemes + .into_iter() + .map(u16::from) + .collect::>(); + expected_schemes.sort(); + assert_eq!(signature_schemes, expected_schemes); let mut alpn = vec![]; for i in 0.. { diff --git a/src/cipher.rs b/src/cipher.rs index 725f0c1d..09626457 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -10,21 +10,22 @@ use std::sync::Arc; use pki_types::{CertificateDer, CertificateRevocationListDer}; use rustls::client::danger::ServerCertVerifier; use rustls::client::WebPkiServerVerifier; -use rustls::crypto::ring::{ALL_CIPHER_SUITES, DEFAULT_CIPHER_SUITES}; +use rustls::crypto::CryptoProvider; use rustls::server::danger::ClientCertVerifier; use rustls::server::WebPkiClientVerifier; use rustls::sign::CertifiedKey; use rustls::{DistinguishedName, RootCertStore, SupportedCipherSuite}; -use rustls_pemfile::{certs, crls, pkcs8_private_keys, rsa_private_keys}; +use rustls_pemfile::{certs, crls}; use webpki::{RevocationCheckDepth, UnknownStatusPolicy}; -use crate::error::{self, rustls_result}; +use crate::crypto_provider::{rustls_crypto_provider, rustls_signing_key}; +use crate::error::{self, map_error, rustls_result}; use crate::rslice::{rustls_slice_bytes, rustls_str}; use crate::{ - arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, ref_castable, - set_arc_mut_ptr, set_boxed_mut_ptr, to_arc_const_ptr, to_boxed_mut_ptr, try_clone_arc, - try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, - try_take, + arc_castable, box_castable, crypto_provider, ffi_panic_boundary, free_arc, free_box, + ref_castable, set_arc_mut_ptr, set_boxed_mut_ptr, to_arc_const_ptr, to_boxed_mut_ptr, + try_box_from_ptr, try_clone_arc, try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, + try_ref_from_ptr_ptr, try_slice, try_take, }; use rustls_result::{AlreadyUsed, NullParameter}; @@ -99,112 +100,6 @@ pub extern "C" fn rustls_supported_ciphersuite_get_name( } } -/// Return the length of rustls' list of supported cipher suites. -#[no_mangle] -pub extern "C" fn rustls_all_ciphersuites_len() -> usize { - ALL_CIPHER_SUITES.len() -} - -/// Get a pointer to a member of rustls' list of supported cipher suites. -/// -/// This will return non-NULL for i < rustls_all_ciphersuites_len(). -/// -/// The returned pointer is valid for the lifetime of the program and may -/// be used directly when building a ClientConfig or ServerConfig. -#[no_mangle] -pub extern "C" fn rustls_all_ciphersuites_get_entry( - i: size_t, -) -> *const rustls_supported_ciphersuite { - match ALL_CIPHER_SUITES.get(i) { - Some(cs) => cs as *const SupportedCipherSuite as *const _, - None => null(), - } -} - -/// Return the length of rustls' list of default cipher suites. -#[no_mangle] -pub extern "C" fn rustls_default_ciphersuites_len() -> usize { - DEFAULT_CIPHER_SUITES.len() -} - -/// Get a pointer to a member of rustls' list of supported cipher suites. -/// -/// This will return non-NULL for i < rustls_default_ciphersuites_len(). -/// -/// The returned pointer is valid for the lifetime of the program and may -/// be used directly when building a ClientConfig or ServerConfig. -#[no_mangle] -pub extern "C" fn rustls_default_ciphersuites_get_entry( - i: size_t, -) -> *const rustls_supported_ciphersuite { - match DEFAULT_CIPHER_SUITES.get(i) { - Some(cs) => cs as *const SupportedCipherSuite as *const _, - None => null(), - } -} - -/// Rustls' list of supported cipher suites. -/// -/// This is an array of pointers, and its length is given by -/// `RUSTLS_ALL_CIPHER_SUITES_LEN`. The pointers will always be valid. -/// The contents and order of this array may change between releases. -#[no_mangle] -pub static mut RUSTLS_ALL_CIPHER_SUITES: [*const rustls_supported_ciphersuite; 9] = [ - &rustls::crypto::ring::cipher_suite::TLS13_AES_256_GCM_SHA384 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, -]; - -/// The length of the array `RUSTLS_ALL_CIPHER_SUITES`. -#[no_mangle] -pub static RUSTLS_ALL_CIPHER_SUITES_LEN: usize = unsafe { RUSTLS_ALL_CIPHER_SUITES.len() }; - -/// Rustls' list of default cipher suites. -/// -/// This is an array of pointers, and its length is given by -/// `RUSTLS_DEFAULT_CIPHER_SUITES_LEN`. The pointers will always be valid. -/// The contents and order of this array may change between releases. -#[no_mangle] -pub static mut RUSTLS_DEFAULT_CIPHER_SUITES: [*const rustls_supported_ciphersuite; 9] = [ - &rustls::crypto::ring::cipher_suite::TLS13_AES_256_GCM_SHA384 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, -]; - -/// The length of the array `RUSTLS_DEFAULT_CIPHER_SUITES`. -#[no_mangle] -pub static RUSTLS_DEFAULT_CIPHER_SUITES_LEN: usize = unsafe { RUSTLS_DEFAULT_CIPHER_SUITES.len() }; - arc_castable! { /// The complete chain of certificates to send during a TLS handshake, /// plus a private key that matches the end-entity (leaf) certificate. @@ -215,14 +110,17 @@ arc_castable! { } impl rustls_certified_key { - /// Build a `rustls_certified_key` from a certificate chain and a private key. + /// Build a `rustls_certified_key` from a certificate chain and a private key + /// and the default process-wide crypto provider. /// /// `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing /// a series of PEM-encoded certificates, with the end-entity (leaf) /// certificate first. /// /// `private_key` must point to a buffer of `private_key_len` bytes, containing - /// a PEM-encoded private key in either PKCS#1 or PKCS#8 format. + /// a PEM-encoded private key in either PKCS#1, PKCS#8 or SEC#1 format when + /// using `aws-lc-rs` as the crypto provider. Supported formats may vary by + /// provider. /// /// On success, this writes a pointer to the newly created /// `rustls_certified_key` in `certified_key_out`. That pointer must later @@ -251,23 +149,85 @@ impl rustls_certified_key { certified_key_out: *mut *const rustls_certified_key, ) -> rustls_result { ffi_panic_boundary! { - let certified_key_out = unsafe { - match certified_key_out.as_mut() { - Some(c) => c, - None => return NullParameter, - } + let default_provider = + match crypto_provider::get_default_or_install_from_crate_features() { + Some(default_provider) => default_provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + let private_key_pem = try_slice!(private_key, private_key_len); + + let private_key_der = + match rustls_pemfile::private_key(&mut Cursor::new(private_key_pem)) { + Ok(Some(p)) => p, + _ => return rustls_result::PrivateKeyParseError, + }; + + let private_key = match default_provider + .key_provider + .load_private_key(private_key_der) + { + Ok(key) => key, + Err(e) => return map_error(e), }; - let certified_key = match rustls_certified_key::certified_key_build( + + Self::rustls_certified_key_build_with_signing_key( cert_chain, cert_chain_len, - private_key, - private_key_len, - ) { - Ok(key) => Box::new(key), - Err(rr) => return rr, + to_boxed_mut_ptr(private_key), + certified_key_out, + ) + } + } + + /// Build a `rustls_certified_key` from a certificate chain and a + /// `rustls_signing_key`. + /// + /// `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing + /// a series of PEM-encoded certificates, with the end-entity (leaf) + /// certificate first. + /// + /// `signing_key` must point to a `rustls_signing_key` loaded using a + /// `rustls_crypto_provider` and `rustls_crypto_provider_load_key()`. + /// + /// On success, this writes a pointer to the newly created + /// `rustls_certified_key` in `certified_key_out`. That pointer must later + /// be freed with `rustls_certified_key_free` to avoid memory leaks. Note that + /// internally, this is an atomically reference-counted pointer, so even after + /// the original caller has called `rustls_certified_key_free`, other objects + /// may retain a pointer to the object. The memory will be freed when all + /// references are gone. + /// + /// This function does not take ownership of any of its input pointers. It + /// parses the pointed-to data and makes a copy of the result. You may + /// free the cert_chain and private_key pointers after calling it. + /// + /// Typically, you will build a `rustls_certified_key`, use it to create a + /// `rustls_server_config` (which increments the reference count), and then + /// immediately call `rustls_certified_key_free`. That leaves the + /// `rustls_server_config` in possession of the sole reference, so the + /// `rustls_certified_key`'s memory will automatically be released when + /// the `rustls_server_config` is freed. + #[no_mangle] + pub extern "C" fn rustls_certified_key_build_with_signing_key( + cert_chain: *const u8, + cert_chain_len: size_t, + signing_key: *mut rustls_signing_key, + certified_key_out: *mut *const rustls_certified_key, + ) -> rustls_result { + ffi_panic_boundary! { + let mut cert_chain = try_slice!(cert_chain, cert_chain_len); + let signing_key = try_box_from_ptr!(signing_key); + let certified_key_out = try_ref_from_ptr_ptr!(certified_key_out); + + let parsed_chain = match certs(&mut cert_chain).collect::, _>>() { + Ok(v) => v, + Err(_) => return rustls_result::CertificateParseError, }; - let certified_key = Arc::into_raw(Arc::new(*certified_key)) as *const _; - *certified_key_out = certified_key; + + set_arc_mut_ptr( + certified_key_out, + CertifiedKey::new(parsed_chain, *signing_key), + ); rustls_result::Ok } } @@ -341,49 +301,6 @@ impl rustls_certified_key { free_arc(key); } } - - fn certified_key_build( - cert_chain: *const u8, - cert_chain_len: size_t, - private_key: *const u8, - private_key_len: size_t, - ) -> Result { - let mut cert_chain = unsafe { - if cert_chain.is_null() { - return Err(NullParameter); - } - slice::from_raw_parts(cert_chain, cert_chain_len) - }; - let private_key_der = unsafe { - if private_key.is_null() { - return Err(NullParameter); - } - slice::from_raw_parts(private_key, private_key_len) - }; - let private_key = match pkcs8_private_keys(&mut Cursor::new(private_key_der)).next() { - Some(Ok(p)) => p.into(), - Some(Err(_)) => return Err(rustls_result::PrivateKeyParseError), - None => { - let rsa_private_key = - match rsa_private_keys(&mut Cursor::new(private_key_der)).next() { - Some(Ok(p)) => p.into(), - _ => return Err(rustls_result::PrivateKeyParseError), - }; - rsa_private_key - } - }; - let signing_key = match rustls::crypto::ring::sign::any_supported_type(&private_key) { - Ok(key) => key, - Err(_) => return Err(rustls_result::PrivateKeyParseError), - }; - let parsed_chain: Result, _> = certs(&mut cert_chain).collect(); - let parsed_chain = match parsed_chain { - Ok(v) => v, - Err(_) => return Err(rustls_result::CertificateParseError), - }; - - Ok(CertifiedKey::new(parsed_chain, signing_key)) - } } box_castable! { @@ -600,6 +517,7 @@ impl rustls_client_cert_verifier { } pub(crate) struct ClientCertVerifierBuilder { + provider: Option>, roots: Arc, root_hint_subjects: Vec, crls: Vec>, @@ -624,7 +542,8 @@ box_castable! { } impl rustls_web_pki_client_cert_verifier_builder { - /// Create a `rustls_web_pki_client_cert_verifier_builder`. + /// Create a `rustls_web_pki_client_cert_verifier_builder` using the process-wide default + /// cryptography provider. /// /// Caller owns the memory and may eventually call `rustls_web_pki_client_cert_verifier_builder_free` /// to free it, whether or not `rustls_web_pki_client_cert_verifier_builder_build` was called. @@ -653,15 +572,60 @@ impl rustls_web_pki_client_cert_verifier_builder { ) -> *mut rustls_web_pki_client_cert_verifier_builder { ffi_panic_boundary! { let store = try_clone_arc!(store); - let builder = ClientCertVerifierBuilder { + to_boxed_mut_ptr(Some(ClientCertVerifierBuilder { + provider: crypto_provider::get_default_or_install_from_crate_features(), root_hint_subjects: store.subjects(), roots: store, crls: Vec::default(), revocation_depth: RevocationCheckDepth::Chain, revocation_policy: UnknownStatusPolicy::Deny, allow_unauthenticated: false, - }; - to_boxed_mut_ptr(Some(builder)) + })) + } + } + + /// Create a `rustls_web_pki_client_cert_verifier_builder` using the specified + /// cryptography provider. + /// + /// Caller owns the memory and may eventually call + /// `rustls_web_pki_client_cert_verifier_builder_free` to free it, whether or + /// not `rustls_web_pki_client_cert_verifier_builder_build` was called. + /// + /// Without further modification the builder will produce a client certificate verifier that + /// will require a client present a client certificate that chains to one of the trust anchors + /// in the provided `rustls_root_cert_store`. The root cert store must not be empty. + /// + /// Revocation checking will not be performed unless + /// `rustls_web_pki_client_cert_verifier_builder_add_crl` is used to add certificate revocation + /// lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + /// for the entire certificate chain unless + /// `rustls_web_pki_client_cert_verifier_only_check_end_entity_revocation` is used. Unknown + /// revocation status for certificates considered for revocation status will be treated as + /// an error unless `rustls_web_pki_client_cert_verifier_allow_unknown_revocation_status` is + /// used. + /// + /// Unauthenticated clients will not be permitted unless + /// `rustls_web_pki_client_cert_verifier_builder_allow_unauthenticated` is used. + /// + /// This copies the contents of the `rustls_root_cert_store`. It does not take + /// ownership of the pointed-to data. + #[no_mangle] + pub extern "C" fn rustls_web_pki_client_cert_verifier_builder_new_with_provider( + provider: *const rustls_crypto_provider, + store: *const rustls_root_cert_store, + ) -> *mut rustls_web_pki_client_cert_verifier_builder { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let store = try_clone_arc!(store); + to_boxed_mut_ptr(Some(ClientCertVerifierBuilder { + provider: Some(provider), + root_hint_subjects: store.subjects(), + roots: store, + crls: Vec::default(), + revocation_depth: RevocationCheckDepth::Chain, + revocation_policy: UnknownStatusPolicy::Deny, + allow_unauthenticated: false, + })) } } @@ -818,18 +782,19 @@ impl rustls_web_pki_client_cert_verifier_builder { verifier_out: *mut *mut rustls_client_cert_verifier, ) -> rustls_result { ffi_panic_boundary! { - if verifier_out.is_null() { - return NullParameter; - } let client_verifier_builder = try_mut_from_ptr!(builder); let client_verifier_builder = try_take!(client_verifier_builder); let verifier_out = try_mut_from_ptr_ptr!(verifier_out); - let mut builder = WebPkiClientVerifier::builder_with_provider( - client_verifier_builder.roots, - rustls::crypto::ring::default_provider().into(), - ) - .with_crls(client_verifier_builder.crls); + let builder = match client_verifier_builder.provider { + Some(provider) => WebPkiClientVerifier::builder_with_provider( + client_verifier_builder.roots, + provider, + ), + None => WebPkiClientVerifier::builder(client_verifier_builder.roots), + }; + + let mut builder = builder.with_crls(client_verifier_builder.crls); match client_verifier_builder.revocation_depth { RevocationCheckDepth::EndEntity => { builder = builder.only_check_end_entity_revocation() @@ -888,6 +853,7 @@ box_castable! { } pub(crate) struct ServerCertVerifierBuilder { + provider: Option>, roots: Arc, crls: Vec>, revocation_depth: RevocationCheckDepth, @@ -895,7 +861,8 @@ pub(crate) struct ServerCertVerifierBuilder { } impl ServerCertVerifierBuilder { - /// Create a `rustls_web_pki_server_cert_verifier_builder`. + /// Create a `rustls_web_pki_server_cert_verifier_builder` using the process-wide default + /// crypto provider. Caller owns the memory and may free it with /// /// Caller owns the memory and may free it with `rustls_web_pki_server_cert_verifier_builder_free`, /// regardless of whether `rustls_web_pki_server_cert_verifier_builder_build` was called. @@ -921,13 +888,51 @@ impl ServerCertVerifierBuilder { ) -> *mut rustls_web_pki_server_cert_verifier_builder { ffi_panic_boundary! { let store = try_clone_arc!(store); - let builder = ServerCertVerifierBuilder { + to_boxed_mut_ptr(Some(ServerCertVerifierBuilder { + provider: crypto_provider::get_default_or_install_from_crate_features(), roots: store, crls: Vec::default(), revocation_depth: RevocationCheckDepth::Chain, revocation_policy: UnknownStatusPolicy::Deny, - }; - to_boxed_mut_ptr(Some(builder)) + })) + } + } + + /// Create a `rustls_web_pki_server_cert_verifier_builder` using the specified + /// crypto provider. Caller owns the memory and may free it with + /// `rustls_web_pki_server_cert_verifier_builder_free`, regardless of whether + /// `rustls_web_pki_server_cert_verifier_builder_build` was called. + /// + /// Without further modification the builder will produce a server certificate verifier that + /// will require a server present a certificate that chains to one of the trust anchors + /// in the provided `rustls_root_cert_store`. The root cert store must not be empty. + /// + /// Revocation checking will not be performed unless + /// `rustls_web_pki_server_cert_verifier_builder_add_crl` is used to add certificate revocation + /// lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + /// for the entire certificate chain unless + /// `rustls_web_pki_server_cert_verifier_only_check_end_entity_revocation` is used. Unknown + /// revocation status for certificates considered for revocation status will be treated as + /// an error unless `rustls_web_pki_server_cert_verifier_allow_unknown_revocation_status` is + /// used. + /// + /// This copies the contents of the `rustls_root_cert_store`. It does not take + /// ownership of the pointed-to data. + #[no_mangle] + pub extern "C" fn rustls_web_pki_server_cert_verifier_builder_new_with_provider( + provider: *const rustls_crypto_provider, + store: *const rustls_root_cert_store, + ) -> *mut rustls_web_pki_server_cert_verifier_builder { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let store = try_clone_arc!(store); + to_boxed_mut_ptr(Some(ServerCertVerifierBuilder { + provider: Some(provider), + roots: store, + crls: Vec::default(), + revocation_depth: RevocationCheckDepth::Chain, + revocation_policy: UnknownStatusPolicy::Deny, + })) } } @@ -1022,18 +1027,19 @@ impl ServerCertVerifierBuilder { verifier_out: *mut *mut rustls_server_cert_verifier, ) -> rustls_result { ffi_panic_boundary! { - if verifier_out.is_null() { - return NullParameter; - } let server_verifier_builder = try_mut_from_ptr!(builder); let server_verifier_builder = try_take!(server_verifier_builder); let verifier_out = try_mut_from_ptr_ptr!(verifier_out); - let mut builder = WebPkiServerVerifier::builder_with_provider( - server_verifier_builder.roots, - rustls::crypto::ring::default_provider().into(), - ) - .with_crls(server_verifier_builder.crls); + let builder = match server_verifier_builder.provider { + Some(provider) => WebPkiServerVerifier::builder_with_provider( + server_verifier_builder.roots, + provider, + ), + None => WebPkiServerVerifier::builder(server_verifier_builder.roots), + }; + + let mut builder = builder.with_crls(server_verifier_builder.crls); match server_verifier_builder.revocation_depth { RevocationCheckDepth::EndEntity => { builder = builder.only_check_end_entity_revocation() @@ -1091,12 +1097,38 @@ impl rustls_server_cert_verifier { /// /// [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier #[no_mangle] - pub extern "C" fn rustls_platform_server_cert_verifier() -> *mut rustls_server_cert_verifier { + pub extern "C" fn rustls_platform_server_cert_verifier( + verifier_out: *mut *mut rustls_server_cert_verifier, + ) -> rustls_result { ffi_panic_boundary! { - let verifier: Arc = Arc::new( - rustls_platform_verifier::Verifier::new() - .with_provider(rustls::crypto::ring::default_provider().into()), - ); + let verifier_out = try_mut_from_ptr_ptr!(verifier_out); + let provider = match crypto_provider::get_default_or_install_from_crate_features() { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + let verifier: Arc = + Arc::new(rustls_platform_verifier::Verifier::new().with_provider(provider)); + set_boxed_mut_ptr(verifier_out, verifier); + rustls_result::Ok + } + } + + /// Create a verifier that uses the default behavior for the current platform. + /// + /// This uses [`rustls-platform-verifier`][] and the specified crypto provider. + /// + /// The verifier can be used in several `rustls_client_config` instances and must be freed by + /// the application using `rustls_server_cert_verifier_free` when no longer needed. + /// + /// [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier + #[no_mangle] + pub extern "C" fn rustls_platform_server_cert_verifier_with_provider( + provider: *const rustls_crypto_provider, + ) -> *mut rustls_server_cert_verifier { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let verifier: Arc = + Arc::new(rustls_platform_verifier::Verifier::new().with_provider(provider)); to_boxed_mut_ptr(verifier) } } @@ -1116,49 +1148,21 @@ impl rustls_server_cert_verifier { #[cfg(test)] mod tests { use super::*; - use std::str; - #[test] - fn all_cipher_suites_arrays() { - assert_eq!(RUSTLS_ALL_CIPHER_SUITES_LEN, ALL_CIPHER_SUITES.len()); - for (original, ffi) in ALL_CIPHER_SUITES - .iter() - .zip(unsafe { RUSTLS_ALL_CIPHER_SUITES }.iter().copied()) - { - let ffi_cipher_suite = try_ref_from_ptr!(ffi); - assert_eq!(original, ffi_cipher_suite); - } - } + use crate::crypto_provider::{ + rustls_default_crypto_provider_ciphersuites_get, + rustls_default_crypto_provider_ciphersuites_len, + }; #[test] - fn default_cipher_suites_arrays() { - assert_eq!( - RUSTLS_DEFAULT_CIPHER_SUITES_LEN, - DEFAULT_CIPHER_SUITES.len() - ); - for (original, ffi) in DEFAULT_CIPHER_SUITES - .iter() - .zip(unsafe { RUSTLS_DEFAULT_CIPHER_SUITES }.iter().copied()) - { - let ffi_cipher_suite = try_ref_from_ptr!(ffi); - assert_eq!(original, ffi_cipher_suite); + fn default_cipher_suites() { + let num_suites = rustls_default_crypto_provider_ciphersuites_len(); + assert!(num_suites > 2); + for i in 0..num_suites { + let suite = rustls_default_crypto_provider_ciphersuites_get(i); + let name = rustls_supported_ciphersuite_get_name(suite); + let name = unsafe { name.to_str() }; + println!("{}: {}", i, name); } } - - #[test] - fn ciphersuite_get_name() { - let suite = rustls_all_ciphersuites_get_entry(0); - let s = rustls_supported_ciphersuite_get_name(suite); - let want = "TLS13_AES_256_GCM_SHA384"; - unsafe { - let got = str::from_utf8(slice::from_raw_parts(s.data as *const u8, s.len)).unwrap(); - assert_eq!(want, got) - } - } - - #[test] - fn test_all_ciphersuites_len() { - let len = rustls_all_ciphersuites_len(); - assert!(len > 2); - } } diff --git a/src/client.rs b/src/client.rs index a6f4b386..a1679050 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,24 +7,24 @@ use libc::{c_char, size_t}; use pki_types::{CertificateDer, UnixTime}; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::client::ResolvesClientCert; -use rustls::crypto::ring::ALL_CIPHER_SUITES; +use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; use rustls::{ - sign::CertifiedKey, CertificateError, ClientConfig, ClientConnection, DigitallySignedStruct, - Error, ProtocolVersion, SignatureScheme, WantsVerifier, + sign::CertifiedKey, ClientConfig, ClientConnection, DigitallySignedStruct, Error, + ProtocolVersion, SignatureScheme, SupportedProtocolVersion, }; -use crate::cipher::{ - rustls_certified_key, rustls_server_cert_verifier, rustls_supported_ciphersuite, -}; +use crate::cipher::{rustls_certified_key, rustls_server_cert_verifier}; use crate::connection::{rustls_connection, Connection}; +use crate::crypto_provider::rustls_crypto_provider; use crate::error::rustls_result::{InvalidParameter, NullParameter}; -use crate::error::{self, rustls_result}; +use crate::error::{self, map_error, rustls_result}; use crate::rslice::NulByte; use crate::rslice::{rustls_slice_bytes, rustls_slice_slice_bytes, rustls_str}; use crate::{ - arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, set_boxed_mut_ptr, - to_arc_const_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, try_mut_from_ptr, - try_mut_from_ptr_ptr, try_ref_from_ptr, try_slice, userdata_get, + arc_castable, box_castable, crypto_provider, ffi_panic_boundary, free_arc, free_box, + set_arc_mut_ptr, set_boxed_mut_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, + try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, + userdata_get, }; box_castable! { @@ -44,8 +44,9 @@ box_castable! { } pub(crate) struct ClientConfigBuilder { - base: rustls::ConfigBuilder, - verifier: Arc, + provider: Option>, + versions: Vec<&'static SupportedProtocolVersion>, + verifier: Option>, alpn_protocols: Vec>, enable_sni: bool, cert_resolver: Option>, @@ -59,48 +60,8 @@ arc_castable! { pub struct rustls_client_config(ClientConfig); } -#[derive(Debug)] -struct NoneVerifier; - -impl ServerCertVerifier for NoneVerifier { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer, - _intermediates: &[CertificateDer], - _server_name: &pki_types::ServerName<'_>, - _ocsp_response: &[u8], - _now: UnixTime, - ) -> Result { - Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &CertificateDer, - _dss: &DigitallySignedStruct, - ) -> Result { - Err(Error::InvalidCertificate(CertificateError::BadSignature)) - } - - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &CertificateDer, - _dss: &DigitallySignedStruct, - ) -> Result { - Err(Error::InvalidCertificate(CertificateError::BadSignature)) - } - - fn supported_verify_schemes(&self) -> Vec { - rustls::crypto::ring::default_provider() - .signature_verification_algorithms - .supported_schemes() - } -} - impl rustls_client_config_builder { - /// Create a rustls_client_config_builder. + /// Create a rustls_client_config_builder using the process default crypto provider. /// /// Caller owns the memory and must eventually call `rustls_client_config_builder_build`, /// then free the resulting `rustls_client_config`. @@ -108,25 +69,18 @@ impl rustls_client_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, /// call `rustls_client_config_builder_free` to free the builder directly. /// - /// This uses rustls safe default values for the cipher suites, key exchange - /// groups and protocol versions. + /// This uses the process default provider's values for the cipher suites and key + /// exchange groups, as well as safe defaults for protocol versions. /// /// This starts out with no trusted roots. Caller must add roots with - /// rustls_client_config_builder_load_roots_from_file or provide a custom - /// verifier. + /// rustls_client_config_builder_load_roots_from_file or provide a custom verifier. #[no_mangle] pub extern "C" fn rustls_client_config_builder_new() -> *mut rustls_client_config_builder { ffi_panic_boundary! { - // Unwrap safety: *ring* default provider always has ciphersuites compatible with the - // default protocol versions. - let base = ClientConfig::builder_with_provider( - rustls::crypto::ring::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .unwrap(); let builder = ClientConfigBuilder { - base, - verifier: Arc::new(NoneVerifier), + provider: crypto_provider::get_default_or_install_from_crate_features(), + versions: rustls::DEFAULT_VERSIONS.to_vec(), + verifier: None, cert_resolver: None, alpn_protocols: vec![], enable_sni: true, @@ -135,7 +89,7 @@ impl rustls_client_config_builder { } } - /// Create a rustls_client_config_builder. + /// Create a rustls_client_config_builder using the specified crypto provider. /// /// Caller owns the memory and must eventually call `rustls_client_config_builder_build`, /// then free the resulting `rustls_client_config`. @@ -143,13 +97,7 @@ impl rustls_client_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, /// call `rustls_client_config_builder_free` to free the builder directly. /// - /// Specify cipher suites in preference order; the `cipher_suites` parameter - /// must point to an array containing `cipher_suites_len` pointers to - /// `rustls_supported_ciphersuite` previously obtained from - /// `rustls_all_ciphersuites_get_entry()`, or to a provided array, - /// RUSTLS_DEFAULT_CIPHER_SUITES or RUSTLS_ALL_CIPHER_SUITES. - /// - /// Set the TLS protocol versions to use when negotiating a TLS session. + /// `tls_version` sets the TLS protocol versions to use when negotiating a TLS session. /// `tls_version` is the version of the protocol, as defined in rfc8446, /// ch. 4.2.1 and end of ch. 5.1. Some values are defined in /// `rustls_tls_version` for convenience, and the arrays @@ -158,28 +106,18 @@ impl rustls_client_config_builder { /// `tls_versions` will only be used during the call and the application retains /// ownership. `tls_versions_len` is the number of consecutive `uint16_t` /// pointed to by `tls_versions`. + /// + /// Ciphersuites are configured separately via the crypto provider. See + /// `rustls_crypto_provider_builder_set_cipher_suites` for more information. #[no_mangle] pub extern "C" fn rustls_client_config_builder_new_custom( - cipher_suites: *const *const rustls_supported_ciphersuite, - cipher_suites_len: size_t, + provider: *const rustls_crypto_provider, tls_versions: *const u16, tls_versions_len: size_t, builder_out: *mut *mut rustls_client_config_builder, ) -> rustls_result { ffi_panic_boundary! { - if builder_out.is_null() { - return NullParameter; - } - let cipher_suites = try_slice!(cipher_suites, cipher_suites_len); - let mut cs_vec = Vec::new(); - for &cs in cipher_suites.iter() { - let cs = try_ref_from_ptr!(cs); - match ALL_CIPHER_SUITES.iter().find(|&acs| cs.eq(acs)) { - Some(scs) => cs_vec.push(*scs), - None => return InvalidParameter, - } - } - + let provider = try_clone_arc!(provider); let tls_versions = try_slice!(tls_versions, tls_versions_len); let mut versions = vec![]; for version_number in tls_versions { @@ -190,22 +128,12 @@ impl rustls_client_config_builder { versions.push(&rustls::version::TLS13); } } - let builder_out = try_mut_from_ptr_ptr!(builder_out); - let provider = rustls::crypto::CryptoProvider { - cipher_suites: cs_vec, - ..rustls::crypto::ring::default_provider() - }; - let result = ClientConfig::builder_with_provider(provider.into()) - .with_protocol_versions(&versions); - let base = match result { - Ok(new) => new, - Err(_) => return InvalidParameter, - }; let config_builder = ClientConfigBuilder { - base, - verifier: Arc::new(NoneVerifier), + provider: Some(provider), + versions, + verifier: None, cert_resolver: None, alpn_protocols: vec![], enable_sni: true, @@ -259,6 +187,7 @@ type VerifyCallback = unsafe extern "C" fn( // An implementation of rustls::ServerCertVerifier based on a C callback. struct Verifier { + provider: Arc, callback: VerifyCallback, } @@ -314,11 +243,11 @@ impl ServerCertVerifier for Verifier { cert: &CertificateDer, dss: &DigitallySignedStruct, ) -> Result { - rustls::crypto::verify_tls12_signature( + verify_tls12_signature( message, cert, dss, - &rustls::crypto::ring::default_provider().signature_verification_algorithms, + &self.provider.signature_verification_algorithms, ) } @@ -328,16 +257,16 @@ impl ServerCertVerifier for Verifier { cert: &CertificateDer, dss: &DigitallySignedStruct, ) -> Result { - rustls::crypto::verify_tls13_signature( + verify_tls13_signature( message, cert, dss, - &rustls::crypto::ring::default_provider().signature_verification_algorithms, + &self.provider.signature_verification_algorithms, ) } fn supported_verify_schemes(&self) -> Vec { - rustls::crypto::ring::default_provider() + self.provider .signature_verification_algorithms .supported_schemes() } @@ -350,7 +279,10 @@ impl Debug for Verifier { } impl rustls_client_config_builder { - /// Set a custom server certificate verifier. + /// Set a custom server certificate verifier using the builder crypto provider. + /// Returns rustls_result::NoDefaultCryptoProvider if no process default crypto + /// provider has been set, and the builder was not constructed with an explicit + /// provider choice. /// /// The callback must not capture any of the pointers in its /// rustls_verify_server_cert_params. @@ -386,8 +318,12 @@ impl rustls_client_config_builder { None => return InvalidParameter, }; - let verifier = Verifier { callback }; - config_builder.verifier = Arc::new(verifier); + let provider = match &config_builder.provider { + Some(provider) => provider.clone(), + None => return rustls_result::NoDefaultCryptoProvider, + }; + + config_builder.verifier = Some(Arc::new(Verifier { provider, callback })); rustls_result::Ok } } @@ -402,8 +338,7 @@ impl rustls_client_config_builder { ) { ffi_panic_boundary! { let builder = try_mut_from_ptr!(builder); - let verifier = try_ref_from_ptr!(verifier); - builder.verifier = verifier.clone(); + builder.verifier = Some(try_ref_from_ptr!(verifier).clone()); } } @@ -520,20 +455,41 @@ impl rustls_client_config_builder { #[no_mangle] pub extern "C" fn rustls_client_config_builder_build( builder: *mut rustls_client_config_builder, - ) -> *const rustls_client_config { + config_out: *mut *const rustls_client_config, + ) -> rustls_result { ffi_panic_boundary! { let builder = try_box_from_ptr!(builder); - let config = builder - .base + let config_out = try_ref_from_ptr_ptr!(config_out); + + let provider = match builder.provider { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + + let verifier = match builder.verifier { + Some(v) => v, + None => return rustls_result::NoServerCertVerifier, + }; + + let config = match ClientConfig::builder_with_provider(provider) + .with_protocol_versions(&builder.versions) + { + Ok(c) => c, + Err(err) => return map_error(err), + }; + + let config = config .dangerous() - .with_custom_certificate_verifier(builder.verifier); + .with_custom_certificate_verifier(verifier); let mut config = match builder.cert_resolver { Some(r) => config.with_client_cert_resolver(r), None => config.with_no_client_auth(), }; config.alpn_protocols = builder.alpn_protocols; config.enable_sni = builder.enable_sni; - to_arc_const_ptr(config) + + set_arc_mut_ptr(config_out, config); + rustls_result::Ok } } @@ -629,6 +585,14 @@ mod tests { #[test] fn test_config_builder() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); let h1 = "http/1.1".as_bytes(); let h2 = "h2".as_bytes(); let alpn = [h1.into(), h2.into()]; @@ -638,13 +602,18 @@ mod tests { alpn.len(), ); rustls_client_config_builder::rustls_client_config_builder_set_enable_sni(builder, false); - let config = rustls_client_config_builder::rustls_client_config_builder_build(builder); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); { let config2 = try_ref_from_ptr!(config); assert!(!config2.enable_sni); assert_eq!(config2.alpn_protocols, vec![h1, h2]); } - rustls_client_config::rustls_client_config_free(config) + rustls_client_config::rustls_client_config_free(config); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); } // Build a client connection and test the getters and initial values. @@ -652,7 +621,19 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_client_connection_new() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - let config = rustls_client_config_builder::rustls_client_config_builder_build(builder); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut conn = null_mut(); let result = rustls_client_config::rustls_client_connection_new( config, @@ -679,8 +660,10 @@ mod tests { assert_eq!( rustls_connection::rustls_connection_get_negotiated_ciphersuite(conn), - null() + 0 ); + let cs_name = rustls_connection::rustls_connection_get_negotiated_ciphersuite_name(conn); + assert_eq!(unsafe { cs_name.to_str() }, ""); assert_eq!( rustls_connection::rustls_connection_get_peer_certificate(conn, 0), null() @@ -691,13 +674,26 @@ mod tests { 0 ); rustls_connection::rustls_connection_free(conn); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); } #[test] #[cfg_attr(miri, ignore)] fn test_client_connection_new_ipaddress() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - let config = rustls_client_config_builder::rustls_client_config_builder_build(builder); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut conn = null_mut(); let result = rustls_client_config::rustls_client_connection_new( config, @@ -707,5 +703,15 @@ mod tests { if !matches!(result, rustls_result::Ok) { panic!("expected RUSTLS_RESULT_OK, got {:?}", result); } + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); + } + + #[test] + fn test_client_builder_no_verifier_err() { + let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::NoServerCertVerifier); } } diff --git a/src/connection.rs b/src/connection.rs index d4871c98..602b1695 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -4,21 +4,20 @@ use std::{ptr::null_mut, slice}; use libc::{size_t, EINVAL, EIO}; use pki_types::CertificateDer; -use rustls::crypto::ring::ALL_CIPHER_SUITES; -use rustls::{ClientConnection, ServerConnection, SupportedCipherSuite}; +use rustls::CipherSuite::TLS_NULL_WITH_NULL_NULL; +use rustls::{ClientConnection, ServerConnection}; +use crate::cipher::rustls_certificate; +use crate::error::{map_error, rustls_io_result, rustls_result}; use crate::io::{ - rustls_write_vectored_callback, CallbackReader, CallbackWriter, VectoredCallbackWriter, + rustls_read_callback, rustls_write_callback, rustls_write_vectored_callback, CallbackReader, + CallbackWriter, VectoredCallbackWriter, }; use crate::log::{ensure_log_registered, rustls_log_callback}; - +use crate::rslice::rustls_str; use crate::{ - box_castable, - cipher::{rustls_certificate, rustls_supported_ciphersuite}, - error::{map_error, rustls_io_result, rustls_result}, - ffi_panic_boundary, free_box, - io::{rustls_read_callback, rustls_write_callback}, - try_callback, try_mut_from_ptr, try_ref_from_ptr, try_slice, userdata_push, + box_castable, ffi_panic_boundary, free_box, try_callback, try_mut_from_ptr, try_ref_from_ptr, + try_slice, userdata_push, }; use rustls_result::NullParameter; @@ -391,29 +390,44 @@ impl rustls_connection { } } - /// Retrieves the cipher suite agreed with the peer. - /// This returns NULL until the ciphersuite is agreed. - /// The returned pointer lives as long as the program. - /// + /// Retrieves the [IANA registered cipher suite identifier][IANA] agreed with the peer. + /// + /// This returns `TLS_NULL_WITH_NULL_NULL` (0x0000) until the ciphersuite is agreed. + /// + /// [IANA]: #[no_mangle] pub extern "C" fn rustls_connection_get_negotiated_ciphersuite( conn: *const rustls_connection, - ) -> *const rustls_supported_ciphersuite { + ) -> u16 { ffi_panic_boundary! { - let conn = try_ref_from_ptr!(conn); - let negotiated = match conn.negotiated_cipher_suite() { + match try_ref_from_ptr!(conn).negotiated_cipher_suite() { + Some(cs) => u16::from(cs.suite()), + None => u16::from(TLS_NULL_WITH_NULL_NULL), + } + } + } + + /// Retrieves the cipher suite name agreed with the peer. + /// + /// This returns "" until the ciphersuite is agreed. + /// + /// The lifetime of the `rustls_str` is the lifetime of the program, it does not + /// need to be freed. + /// + /// + #[no_mangle] + pub extern "C" fn rustls_connection_get_negotiated_ciphersuite_name( + conn: *const rustls_connection, + ) -> rustls_str<'static> { + ffi_panic_boundary! { + let cs_name = try_ref_from_ptr!(conn) + .negotiated_cipher_suite() + .and_then(|cs| cs.suite().as_str()) + .and_then(|name| rustls_str::try_from(name).ok()); + match cs_name { Some(cs) => cs, - None => return null(), - }; - for cs in ALL_CIPHER_SUITES { - // This type annotation is here to enforce the lifetime stated - // in the doccomment - that the returned pointer lives as long - // as the program. - if negotiated == *cs { - return cs as *const SupportedCipherSuite as *const _; - } + None => rustls_str::from_str_unchecked(""), } - null() } } diff --git a/src/crypto_provider.rs b/src/crypto_provider.rs new file mode 100644 index 00000000..e61e36d3 --- /dev/null +++ b/src/crypto_provider.rs @@ -0,0 +1,447 @@ +use libc::size_t; +use std::io::Cursor; +use std::slice; +use std::sync::Arc; + +#[cfg(feature = "aws-lc-rs")] +use rustls::crypto::aws_lc_rs; +#[cfg(feature = "ring")] +use rustls::crypto::ring; +use rustls::crypto::CryptoProvider; +use rustls::sign::SigningKey; +use rustls::SupportedCipherSuite; + +use crate::cipher::rustls_supported_ciphersuite; +use crate::error::map_error; +use crate::{ + arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, rustls_result, + set_arc_mut_ptr, set_boxed_mut_ptr, to_boxed_mut_ptr, try_clone_arc, try_mut_from_ptr, + try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, try_take, +}; + +box_castable! { + /// A `rustls_crypto_provider` builder. + pub struct rustls_crypto_provider_builder(Option); +} + +/// A builder for customizing a `CryptoProvider`. Can be used to install a process-wide default. +#[derive(Debug)] +pub struct CryptoProviderBuilder { + base: Arc, + cipher_suites: Vec, +} + +impl CryptoProviderBuilder { + fn build_provider(self) -> CryptoProvider { + let cipher_suites = match self.cipher_suites.is_empty() { + true => self.base.cipher_suites.clone(), + false => self.cipher_suites, + }; + + // Unfortunately we can't use the `..` syntax to fill in the rest of the provider + // fields, because we're working with `Arc` as the base, + // not `CryptoProvider`. + // TODO(#450): once MSRV is 1.76+, use `Arc::unwrap_or_clone`. + CryptoProvider { + cipher_suites, + kx_groups: self.base.kx_groups.clone(), + signature_verification_algorithms: self.base.signature_verification_algorithms, + secure_random: self.base.secure_random, + key_provider: self.base.key_provider, + } + } +} + +/// Constructs a new `rustls_crypto_provider_builder` using the process-wide default crypto +/// provider as the base crypto provider to be customized. +/// +/// When this function returns `rustls_result::Ok` a pointer to the `rustls_crypto_provider_builder` +/// is written to `builder_out`. It returns `rustls_result::NoDefaultCryptoProvider` if no default +/// provider has been registered. +/// +/// The caller owns the returned `rustls_crypto_provider_builder` and must free it using +/// `rustls_crypto_provider_builder_free`. +/// +/// This function is typically used for customizing the default crypto provider for specific +/// connections. For example, a typical workflow might be to: +/// +/// * Either: +/// * Use the default `aws-lc-rs` or `*ring*` provider that rustls-ffi is built with based on +/// the `CRYPTO_PROVIDER` build variable. +/// * Call `rustls_crypto_provider_builder_new_with_base` with the desired provider, and +/// then install it as the process default with +/// `rustls_crypto_provider_builder_build_as_default`. +/// * Afterward, as required for customization: +/// * Use `rustls_crypto_provider_builder_new_from_default` to get a builder backed by the +/// default crypto provider. +/// * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported +/// ciphersuites. +/// * Use `rustls_crypto_provider_builder_build` to build a customized provider. +/// * Provide that customized provider to client or server configuration builders. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_new_from_default( + builder_out: *mut *mut rustls_crypto_provider_builder, +) -> rustls_result { + ffi_panic_boundary! { + let provider_out = try_mut_from_ptr_ptr!(builder_out); + + let base = match get_default_or_install_from_crate_features() { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + + set_boxed_mut_ptr( + provider_out, + Some(CryptoProviderBuilder { + base, + cipher_suites: Vec::default(), + }), + ); + + rustls_result::Ok + } +} + +/// Constructs a new `rustls_crypto_provider_builder` using the given `rustls_crypto_provider` +/// as the base crypto provider to be customized. +/// +/// The caller owns the returned `rustls_crypto_provider_builder` and must free it using +/// `rustls_crypto_provider_builder_free`. +/// +/// This function can be used for setting the default process wide crypto provider, +/// or for constructing a custom crypto provider for a specific connection. A typical +/// workflow could be to: +/// +/// * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider +/// * Install the custom provider as the process-wide default with +/// `rustls_crypto_provider_builder_build_as_default`. +/// +/// Or, for per-connection customization: +/// +/// * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider +/// * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported +/// ciphersuites. +/// * Use `rustls_crypto_provider_builder_build` to build a customized provider. +/// * Provide that customized provider to client or server configuration builders. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_new_with_base( + base: *const rustls_crypto_provider, +) -> *mut rustls_crypto_provider_builder { + ffi_panic_boundary! { + to_boxed_mut_ptr(Some(CryptoProviderBuilder { + base: try_clone_arc!(base), + cipher_suites: Vec::default(), + })) + } +} + +/// Customize the supported ciphersuites of the `rustls_crypto_provider_builder`. +/// +/// Returns an error if the builder has already been built. Overwrites any previously +/// set ciphersuites. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_set_cipher_suites( + builder: *mut rustls_crypto_provider_builder, + cipher_suites: *const *const rustls_supported_ciphersuite, + cipher_suites_len: size_t, +) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + let builder = match builder { + Some(builder) => builder, + None => return rustls_result::AlreadyUsed, + }; + + let cipher_suites = try_slice!(cipher_suites, cipher_suites_len); + let mut supported_cipher_suites = Vec::new(); + for cs in cipher_suites { + let cs = *cs; + let cs = try_ref_from_ptr!(cs); + supported_cipher_suites.push(*cs); + } + + builder.cipher_suites = supported_cipher_suites; + rustls_result::Ok + } +} + +/// Builds a `rustls_crypto_provider` from the builder and returns it. Returns an error if the +/// builder has already been built. +/// +/// The `rustls_crypto_provider_builder` builder is consumed and should not be used +/// for further calls, except to `rustls_crypto_provider_builder_free`. The caller must +/// still free the builder after a successful build. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_build( + builder: *mut rustls_crypto_provider_builder, + provider_out: *mut *const rustls_crypto_provider, +) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + set_arc_mut_ptr( + try_ref_from_ptr_ptr!(provider_out), + try_take!(builder).build_provider(), + ); + rustls_result::Ok + } +} + +/// Builds a `rustls_crypto_provider` from the builder and sets it as the +/// process-wide default crypto provider. +/// +/// Afterward, the default provider can be retrieved using `rustls_crypto_provider_default`. +/// +/// This can only be done once per process, and will return an error if a +/// default provider has already been set, or if the builder has already been built. +/// +/// The `rustls_crypto_provider_builder` builder is consumed and should not be used +/// for further calls, except to `rustls_crypto_provider_builder_free`. The caller must +/// still free the builder after a successful build. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_build_as_default( + builder: *mut rustls_crypto_provider_builder, +) -> rustls_result { + let builder = try_mut_from_ptr!(builder); + match try_take!(builder).build_provider().install_default() { + Ok(_) => rustls_result::Ok, + Err(_) => rustls_result::AlreadyUsed, + } +} + +/// Free the `rustls_crypto_provider_builder`. +/// +/// Calling with `NULL` is fine. +/// Must not be called twice with the same value. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_free( + builder: *mut rustls_crypto_provider_builder, +) { + ffi_panic_boundary! { + free_box(builder); + } +} + +/// Return the `rustls_crypto_provider` backed by the `*ring*` cryptography library. +/// +/// The caller owns the returned `rustls_crypto_provider` and must free it using +/// `rustls_crypto_provider_free`. +#[no_mangle] +#[cfg(feature = "ring")] +pub extern "C" fn rustls_ring_crypto_provider() -> *const rustls_crypto_provider { + ffi_panic_boundary! { + Arc::into_raw(Arc::new(ring::default_provider())) as *const rustls_crypto_provider + } +} + +/// Return the `rustls_crypto_provider` backed by the `aws-lc-rs` cryptography library. +/// +/// The caller owns the returned `rustls_crypto_provider` and must free it using +/// `rustls_crypto_provider_free`. +#[no_mangle] +#[cfg(feature = "aws-lc-rs")] +pub extern "C" fn rustls_aws_lc_rs_crypto_provider() -> *const rustls_crypto_provider { + ffi_panic_boundary! { + Arc::into_raw(Arc::new(aws_lc_rs::default_provider())) as *const rustls_crypto_provider + } +} + +/// Retrieve a pointer to the process default `rustls_crypto_provider`. +/// +/// This may return `NULL` if no process default provider has been set using +/// `rustls_crypto_provider_builder_build_default`. +/// +/// Caller owns the returned `rustls_crypto_provider` and must free it w/ `rustls_crypto_provider_free`. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_default() -> *const rustls_crypto_provider { + ffi_panic_boundary! { + match CryptoProvider::get_default() { + Some(provider) => Arc::into_raw(provider.clone()) as *const rustls_crypto_provider, + None => core::ptr::null(), + } + } +} + +arc_castable! { + /// A C representation of a Rustls [`CryptoProvider`]. + pub struct rustls_crypto_provider(CryptoProvider); +} + +/// Returns the number of ciphersuites the `rustls_crypto_provider` supports. +/// +/// You can use this to know the maximum allowed index for use with +/// `rustls_crypto_provider_ciphersuites_get`. +/// +/// This function will return 0 if the `provider` is NULL. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_ciphersuites_len( + provider: *const rustls_crypto_provider, +) -> usize { + ffi_panic_boundary! { + try_clone_arc!(provider).cipher_suites.len() + } +} + +/// Retrieve a pointer to a supported ciphersuite of the `rustls_crypto_provider`. +/// +/// This function will return NULL if the `provider` is NULL, or if the index is out of bounds +/// with respect to `rustls_crypto_provider_ciphersuites_len`. +/// +/// The lifetime of the returned `rustls_supported_ciphersuite` is equal to the lifetime of the +/// `provider` and should not be used after the `provider` is freed. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_ciphersuites_get( + provider: *const rustls_crypto_provider, + index: usize, +) -> *const rustls_supported_ciphersuite { + ffi_panic_boundary! { + match try_clone_arc!(provider).cipher_suites.get(index) { + Some(ciphersuite) => ciphersuite as *const SupportedCipherSuite as *const _, + None => core::ptr::null(), + } + } +} + +/// Load a private key from the provided PEM content using the crypto provider. +/// +/// `private_key` must point to a buffer of `private_key_len` bytes, containing +/// a PEM-encoded private key. The exact formats supported will differ based on +/// the crypto provider in use. The default providers support PKCS#1, PKCS#8 or +/// SEC1 formats. +/// +/// When this function returns `rustls_result::Ok` a pointer to a `rustls_signing_key` +/// is written to `signing_key_out`. The caller owns the returned `rustls_signing_key` +/// and must free it with `rustls_signing_key_free`. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_load_key( + provider: *const rustls_crypto_provider, + private_key: *const u8, + private_key_len: size_t, + signing_key_out: *mut *mut rustls_signing_key, +) -> rustls_result { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let private_key_pem = try_slice!(private_key, private_key_len); + let signing_key_out = try_mut_from_ptr_ptr!(signing_key_out); + + let private_key_der = match rustls_pemfile::private_key(&mut Cursor::new(private_key_pem)) { + Ok(Some(p)) => p, + _ => return rustls_result::PrivateKeyParseError, + }; + + let private_key = match provider.key_provider.load_private_key(private_key_der) { + Ok(key) => key, + Err(e) => return map_error(e), + }; + + set_boxed_mut_ptr(signing_key_out, private_key); + rustls_result::Ok + } +} + +/// Frees the `rustls_crypto_provider`. +/// +/// Calling with `NULL` is fine. +/// Must not be called twice with the same value. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_free(provider: *const rustls_crypto_provider) { + ffi_panic_boundary! { + free_arc(provider); + } +} + +/// Returns the number of ciphersuites the default process-wide crypto provider supports. +/// +/// You can use this to know the maximum allowed index for use with +/// `rustls_default_crypto_provider_ciphersuites_get`. +/// +/// This function will return 0 if no process-wide default `rustls_crypto_provider` is available. +#[no_mangle] +pub extern "C" fn rustls_default_crypto_provider_ciphersuites_len() -> usize { + ffi_panic_boundary! { + match get_default_or_install_from_crate_features() { + Some(provider) => provider.cipher_suites.len(), + None => return 0, + } + } +} + +/// Retrieve a pointer to a supported ciphersuite of the default process-wide crypto provider. +/// +/// This function will return NULL if the `provider` is NULL, or if the index is out of bounds +/// with respect to `rustls_default_crypto_provider_ciphersuites_len`. +/// +/// The lifetime of the returned `rustls_supported_ciphersuite` is static, as the process-wide +/// default provider lives for as long as the process. +#[no_mangle] +pub extern "C" fn rustls_default_crypto_provider_ciphersuites_get( + index: usize, +) -> *const rustls_supported_ciphersuite { + ffi_panic_boundary! { + let default_provider = match get_default_or_install_from_crate_features() { + Some(provider) => provider, + None => return core::ptr::null(), + }; + match default_provider.cipher_suites.get(index) { + Some(ciphersuite) => ciphersuite as *const SupportedCipherSuite as *const _, + None => core::ptr::null(), + } + } +} + +box_castable! { + /// A signing key that can be used to construct a certified key. + // NOTE: we box cast an arc over the dyn trait per the pattern described + // in our docs[0] for dynamically sized types. + // [0]: + pub struct rustls_signing_key(Arc); +} + +impl rustls_signing_key { + /// Frees the `rustls_signing_key`. This is safe to call with a `NULL` argument, but + /// must not be called twice with the same value. + #[no_mangle] + pub extern "C" fn rustls_signing_key_free(signing_key: *mut rustls_signing_key) { + ffi_panic_boundary! { + free_box(signing_key); + } + } +} + +pub(crate) fn get_default_or_install_from_crate_features() -> Option> { + // If a process-wide default has already been installed, return it. + if let Some(provider) = CryptoProvider::get_default() { + return Some(provider.clone()); + } + + let provider = match provider_from_crate_features() { + Some(provider) => provider, + None => return None, + }; + + // Ignore the error resulting from us losing a race to install the default, + // and accept the outcome. + let _ = provider.install_default(); + + // Safety: we can unwrap safely here knowing we've just set the default, or + // lost a race to something else setting the default. + Some(CryptoProvider::get_default().unwrap().clone()) +} + +fn provider_from_crate_features() -> Option { + // Provider default is unambiguously aws-lc-rs + #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] + { + return Some(aws_lc_rs::default_provider()); + } + + // Provider default is unambiguously ring + #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] + { + return Some(ring::default_provider()); + } + + // Both features activated - no clear default provider based on + // crate features. + #[allow(unreachable_code)] + None +} diff --git a/src/error.rs b/src/error.rs index 85ce803d..0dbcb922 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,6 +60,8 @@ u32_enum_builder! { AcceptorNotReady => 7012, AlreadyUsed => 7013, CertificateRevocationListParseError => 7014, + NoServerCertVerifier => 7015, + NoDefaultCryptoProvider => 7016, // From https://docs.rs/rustls/latest/rustls/enum.Error.html NoCertificatesPresented => 7101, @@ -481,6 +483,18 @@ impl Display for rustls_result { CertificateRevocationListParseError => { write!(f, "error parsing certificate revocation list (CRL)",) } + NoServerCertVerifier => { + write!( + f, + "no server certificate verifier was configured on the client config builder" + ) + } + NoDefaultCryptoProvider => { + write!( + f, + "no default process-wide crypto provider has been installed" + ) + } CertEncodingBad => Error::InvalidCertificate(CertificateError::BadEncoding).fmt(f), CertExpired => Error::InvalidCertificate(CertificateError::Expired).fmt(f), diff --git a/src/lib.rs b/src/lib.rs index 80787b8a..2bcf9464 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub mod acceptor; pub mod cipher; pub mod client; pub mod connection; +pub mod crypto_provider; pub mod enums; mod error; pub mod io; diff --git a/src/rslice.rs b/src/rslice.rs index c14c47ea..d653cef6 100644 --- a/src/rslice.rs +++ b/src/rslice.rs @@ -212,6 +212,15 @@ impl<'a> rustls_str<'a> { pub unsafe fn into_static(self) -> rustls_str<'static> { std::mem::transmute(self) } + + /// Change a rustls_str back to a &str. + /// + /// # Safety + /// + /// The caller must ensure the rustls_str data is valid utf8 + pub unsafe fn to_str(&self) -> &str { + str::from_utf8_unchecked(slice::from_raw_parts(self.data as *const u8, self.len)) + } } // If the assertion about Rust code being the only creator of rustls_str objects @@ -247,6 +256,8 @@ fn test_rustls_str() { assert_eq!(*rs.data, 'a' as c_char); assert_eq!(*rs.data.offset(3), 'd' as c_char); } + let rs = unsafe { rs.to_str() }; + assert_eq!(rs, s); } #[cfg(test)] diff --git a/src/rustls.h b/src/rustls.h index 60aade0f..711cb4f7 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -23,6 +23,8 @@ enum rustls_result { RUSTLS_RESULT_ACCEPTOR_NOT_READY = 7012, RUSTLS_RESULT_ALREADY_USED = 7013, RUSTLS_RESULT_CERTIFICATE_REVOCATION_LIST_PARSE_ERROR = 7014, + RUSTLS_RESULT_NO_SERVER_CERT_VERIFIER = 7015, + RUSTLS_RESULT_NO_DEFAULT_CRYPTO_PROVIDER = 7016, RUSTLS_RESULT_NO_CERTIFICATES_PRESENTED = 7101, RUSTLS_RESULT_DECRYPT_ERROR = 7102, RUSTLS_RESULT_FAILED_TO_GET_CURRENT_TIME = 7103, @@ -225,6 +227,16 @@ typedef struct rustls_client_config_builder rustls_client_config_builder; typedef struct rustls_connection rustls_connection; +/** + * A C representation of a Rustls [`CryptoProvider`]. + */ +typedef struct rustls_crypto_provider rustls_crypto_provider; + +/** + * A `rustls_crypto_provider` builder. + */ +typedef struct rustls_crypto_provider_builder rustls_crypto_provider_builder; + /** * An alias for `struct iovec` from uio.h (on Unix) or `WSABUF` on Windows. * @@ -280,6 +292,11 @@ typedef struct rustls_server_config rustls_server_config; */ typedef struct rustls_server_config_builder rustls_server_config_builder; +/** + * A signing key that can be used to construct a certified key. + */ +typedef struct rustls_signing_key rustls_signing_key; + /** * A read-only view of a slice of Rust byte slices. * @@ -657,20 +674,26 @@ typedef uint32_t (*rustls_session_store_put_callback)(rustls_session_store_userd const struct rustls_slice_bytes *key, const struct rustls_slice_bytes *val); -extern const struct rustls_supported_ciphersuite *RUSTLS_ALL_CIPHER_SUITES[9]; - -extern const size_t RUSTLS_ALL_CIPHER_SUITES_LEN; - -extern const struct rustls_supported_ciphersuite *RUSTLS_DEFAULT_CIPHER_SUITES[9]; - -extern const size_t RUSTLS_DEFAULT_CIPHER_SUITES_LEN; - +/** + * Rustls' list of supported protocol versions. The length of the array is + * given by `RUSTLS_ALL_VERSIONS_LEN`. + */ extern const uint16_t RUSTLS_ALL_VERSIONS[2]; +/** + * The length of the array `RUSTLS_ALL_VERSIONS`. + */ extern const size_t RUSTLS_ALL_VERSIONS_LEN; +/** + * Rustls' default list of protocol versions. The length of the array is + * given by `RUSTLS_DEFAULT_VERSIONS_LEN`. + */ extern const uint16_t RUSTLS_DEFAULT_VERSIONS[2]; +/** + * The length of the array `RUSTLS_DEFAULT_VERSIONS`. + */ extern const size_t RUSTLS_DEFAULT_VERSIONS_LEN; /** @@ -995,44 +1018,53 @@ uint16_t rustls_supported_ciphersuite_get_suite(const struct rustls_supported_ci struct rustls_str rustls_supported_ciphersuite_get_name(const struct rustls_supported_ciphersuite *supported_ciphersuite); /** - * Return the length of rustls' list of supported cipher suites. - */ -size_t rustls_all_ciphersuites_len(void); - -/** - * Get a pointer to a member of rustls' list of supported cipher suites. + * Build a `rustls_certified_key` from a certificate chain and a private key + * and the default process-wide crypto provider. * - * This will return non-NULL for i < rustls_all_ciphersuites_len(). + * `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing + * a series of PEM-encoded certificates, with the end-entity (leaf) + * certificate first. * - * The returned pointer is valid for the lifetime of the program and may - * be used directly when building a ClientConfig or ServerConfig. - */ -const struct rustls_supported_ciphersuite *rustls_all_ciphersuites_get_entry(size_t i); - -/** - * Return the length of rustls' list of default cipher suites. - */ -size_t rustls_default_ciphersuites_len(void); - -/** - * Get a pointer to a member of rustls' list of supported cipher suites. + * `private_key` must point to a buffer of `private_key_len` bytes, containing + * a PEM-encoded private key in either PKCS#1, PKCS#8 or SEC#1 format when + * using `aws-lc-rs` as the crypto provider. Supported formats may vary by + * provider. + * + * On success, this writes a pointer to the newly created + * `rustls_certified_key` in `certified_key_out`. That pointer must later + * be freed with `rustls_certified_key_free` to avoid memory leaks. Note that + * internally, this is an atomically reference-counted pointer, so even after + * the original caller has called `rustls_certified_key_free`, other objects + * may retain a pointer to the object. The memory will be freed when all + * references are gone. * - * This will return non-NULL for i < rustls_default_ciphersuites_len(). + * This function does not take ownership of any of its input pointers. It + * parses the pointed-to data and makes a copy of the result. You may + * free the cert_chain and private_key pointers after calling it. * - * The returned pointer is valid for the lifetime of the program and may - * be used directly when building a ClientConfig or ServerConfig. + * Typically, you will build a `rustls_certified_key`, use it to create a + * `rustls_server_config` (which increments the reference count), and then + * immediately call `rustls_certified_key_free`. That leaves the + * `rustls_server_config` in possession of the sole reference, so the + * `rustls_certified_key`'s memory will automatically be released when + * the `rustls_server_config` is freed. */ -const struct rustls_supported_ciphersuite *rustls_default_ciphersuites_get_entry(size_t i); +rustls_result rustls_certified_key_build(const uint8_t *cert_chain, + size_t cert_chain_len, + const uint8_t *private_key, + size_t private_key_len, + const struct rustls_certified_key **certified_key_out); /** - * Build a `rustls_certified_key` from a certificate chain and a private key. + * Build a `rustls_certified_key` from a certificate chain and a + * `rustls_signing_key`. * * `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing * a series of PEM-encoded certificates, with the end-entity (leaf) * certificate first. * - * `private_key` must point to a buffer of `private_key_len` bytes, containing - * a PEM-encoded private key in either PKCS#1 or PKCS#8 format. + * `signing_key` must point to a `rustls_signing_key` loaded using a + * `rustls_crypto_provider` and `rustls_crypto_provider_load_key()`. * * On success, this writes a pointer to the newly created * `rustls_certified_key` in `certified_key_out`. That pointer must later @@ -1053,11 +1085,10 @@ const struct rustls_supported_ciphersuite *rustls_default_ciphersuites_get_entry * `rustls_certified_key`'s memory will automatically be released when * the `rustls_server_config` is freed. */ -rustls_result rustls_certified_key_build(const uint8_t *cert_chain, - size_t cert_chain_len, - const uint8_t *private_key, - size_t private_key_len, - const struct rustls_certified_key **certified_key_out); +rustls_result rustls_certified_key_build_with_signing_key(const uint8_t *cert_chain, + size_t cert_chain_len, + struct rustls_signing_key *signing_key, + const struct rustls_certified_key **certified_key_out); /** * Return the i-th rustls_certificate in the rustls_certified_key. @@ -1174,7 +1205,8 @@ void rustls_root_cert_store_free(const struct rustls_root_cert_store *store); void rustls_client_cert_verifier_free(struct rustls_client_cert_verifier *verifier); /** - * Create a `rustls_web_pki_client_cert_verifier_builder`. + * Create a `rustls_web_pki_client_cert_verifier_builder` using the process-wide default + * cryptography provider. * * Caller owns the memory and may eventually call `rustls_web_pki_client_cert_verifier_builder_free` * to free it, whether or not `rustls_web_pki_client_cert_verifier_builder_build` was called. @@ -1200,6 +1232,36 @@ void rustls_client_cert_verifier_free(struct rustls_client_cert_verifier *verifi */ struct rustls_web_pki_client_cert_verifier_builder *rustls_web_pki_client_cert_verifier_builder_new(const struct rustls_root_cert_store *store); +/** + * Create a `rustls_web_pki_client_cert_verifier_builder` using the specified + * cryptography provider. + * + * Caller owns the memory and may eventually call + * `rustls_web_pki_client_cert_verifier_builder_free` to free it, whether or + * not `rustls_web_pki_client_cert_verifier_builder_build` was called. + * + * Without further modification the builder will produce a client certificate verifier that + * will require a client present a client certificate that chains to one of the trust anchors + * in the provided `rustls_root_cert_store`. The root cert store must not be empty. + * + * Revocation checking will not be performed unless + * `rustls_web_pki_client_cert_verifier_builder_add_crl` is used to add certificate revocation + * lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + * for the entire certificate chain unless + * `rustls_web_pki_client_cert_verifier_only_check_end_entity_revocation` is used. Unknown + * revocation status for certificates considered for revocation status will be treated as + * an error unless `rustls_web_pki_client_cert_verifier_allow_unknown_revocation_status` is + * used. + * + * Unauthenticated clients will not be permitted unless + * `rustls_web_pki_client_cert_verifier_builder_allow_unauthenticated` is used. + * + * This copies the contents of the `rustls_root_cert_store`. It does not take + * ownership of the pointed-to data. + */ +struct rustls_web_pki_client_cert_verifier_builder *rustls_web_pki_client_cert_verifier_builder_new_with_provider(const struct rustls_crypto_provider *provider, + const struct rustls_root_cert_store *store); + /** * Add one or more certificate revocation lists (CRLs) to the client certificate verifier * builder by reading the CRL content from the provided buffer of PEM encoded content. @@ -1279,7 +1341,8 @@ rustls_result rustls_web_pki_client_cert_verifier_builder_build(struct rustls_we void rustls_web_pki_client_cert_verifier_builder_free(struct rustls_web_pki_client_cert_verifier_builder *builder); /** - * Create a `rustls_web_pki_server_cert_verifier_builder`. + * Create a `rustls_web_pki_server_cert_verifier_builder` using the process-wide default + * crypto provider. Caller owns the memory and may free it with * * Caller owns the memory and may free it with `rustls_web_pki_server_cert_verifier_builder_free`, * regardless of whether `rustls_web_pki_server_cert_verifier_builder_build` was called. @@ -1302,6 +1365,31 @@ void rustls_web_pki_client_cert_verifier_builder_free(struct rustls_web_pki_clie */ struct rustls_web_pki_server_cert_verifier_builder *rustls_web_pki_server_cert_verifier_builder_new(const struct rustls_root_cert_store *store); +/** + * Create a `rustls_web_pki_server_cert_verifier_builder` using the specified + * crypto provider. Caller owns the memory and may free it with + * `rustls_web_pki_server_cert_verifier_builder_free`, regardless of whether + * `rustls_web_pki_server_cert_verifier_builder_build` was called. + * + * Without further modification the builder will produce a server certificate verifier that + * will require a server present a certificate that chains to one of the trust anchors + * in the provided `rustls_root_cert_store`. The root cert store must not be empty. + * + * Revocation checking will not be performed unless + * `rustls_web_pki_server_cert_verifier_builder_add_crl` is used to add certificate revocation + * lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + * for the entire certificate chain unless + * `rustls_web_pki_server_cert_verifier_only_check_end_entity_revocation` is used. Unknown + * revocation status for certificates considered for revocation status will be treated as + * an error unless `rustls_web_pki_server_cert_verifier_allow_unknown_revocation_status` is + * used. + * + * This copies the contents of the `rustls_root_cert_store`. It does not take + * ownership of the pointed-to data. + */ +struct rustls_web_pki_server_cert_verifier_builder *rustls_web_pki_server_cert_verifier_builder_new_with_provider(const struct rustls_crypto_provider *provider, + const struct rustls_root_cert_store *store); + /** * Add one or more certificate revocation lists (CRLs) to the server certificate verifier * builder by reading the CRL content from the provided buffer of PEM encoded content. @@ -1362,7 +1450,19 @@ void rustls_web_pki_server_cert_verifier_builder_free(struct rustls_web_pki_serv * * [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier */ -struct rustls_server_cert_verifier *rustls_platform_server_cert_verifier(void); +rustls_result rustls_platform_server_cert_verifier(struct rustls_server_cert_verifier **verifier_out); + +/** + * Create a verifier that uses the default behavior for the current platform. + * + * This uses [`rustls-platform-verifier`][] and the specified crypto provider. + * + * The verifier can be used in several `rustls_client_config` instances and must be freed by + * the application using `rustls_server_cert_verifier_free` when no longer needed. + * + * [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier + */ +struct rustls_server_cert_verifier *rustls_platform_server_cert_verifier_with_provider(const struct rustls_crypto_provider *provider); /** * Free a `rustls_server_cert_verifier` previously returned from @@ -1373,7 +1473,7 @@ struct rustls_server_cert_verifier *rustls_platform_server_cert_verifier(void); void rustls_server_cert_verifier_free(struct rustls_server_cert_verifier *verifier); /** - * Create a rustls_client_config_builder. + * Create a rustls_client_config_builder using the process default crypto provider. * * Caller owns the memory and must eventually call `rustls_client_config_builder_build`, * then free the resulting `rustls_client_config`. @@ -1381,17 +1481,16 @@ void rustls_server_cert_verifier_free(struct rustls_server_cert_verifier *verifi * Alternatively, if an error occurs or, you don't wish to build a config, * call `rustls_client_config_builder_free` to free the builder directly. * - * This uses rustls safe default values for the cipher suites, key exchange - * groups and protocol versions. + * This uses the process default provider's values for the cipher suites and key + * exchange groups, as well as safe defaults for protocol versions. * * This starts out with no trusted roots. Caller must add roots with - * rustls_client_config_builder_load_roots_from_file or provide a custom - * verifier. + * rustls_client_config_builder_load_roots_from_file or provide a custom verifier. */ struct rustls_client_config_builder *rustls_client_config_builder_new(void); /** - * Create a rustls_client_config_builder. + * Create a rustls_client_config_builder using the specified crypto provider. * * Caller owns the memory and must eventually call `rustls_client_config_builder_build`, * then free the resulting `rustls_client_config`. @@ -1399,13 +1498,7 @@ struct rustls_client_config_builder *rustls_client_config_builder_new(void); * Alternatively, if an error occurs or, you don't wish to build a config, * call `rustls_client_config_builder_free` to free the builder directly. * - * Specify cipher suites in preference order; the `cipher_suites` parameter - * must point to an array containing `cipher_suites_len` pointers to - * `rustls_supported_ciphersuite` previously obtained from - * `rustls_all_ciphersuites_get_entry()`, or to a provided array, - * RUSTLS_DEFAULT_CIPHER_SUITES or RUSTLS_ALL_CIPHER_SUITES. - * - * Set the TLS protocol versions to use when negotiating a TLS session. + * `tls_version` sets the TLS protocol versions to use when negotiating a TLS session. * `tls_version` is the version of the protocol, as defined in rfc8446, * ch. 4.2.1 and end of ch. 5.1. Some values are defined in * `rustls_tls_version` for convenience, and the arrays @@ -1414,15 +1507,20 @@ struct rustls_client_config_builder *rustls_client_config_builder_new(void); * `tls_versions` will only be used during the call and the application retains * ownership. `tls_versions_len` is the number of consecutive `uint16_t` * pointed to by `tls_versions`. + * + * Ciphersuites are configured separately via the crypto provider. See + * `rustls_crypto_provider_builder_set_cipher_suites` for more information. */ -rustls_result rustls_client_config_builder_new_custom(const struct rustls_supported_ciphersuite *const *cipher_suites, - size_t cipher_suites_len, +rustls_result rustls_client_config_builder_new_custom(const struct rustls_crypto_provider *provider, const uint16_t *tls_versions, size_t tls_versions_len, struct rustls_client_config_builder **builder_out); /** - * Set a custom server certificate verifier. + * Set a custom server certificate verifier using the builder crypto provider. + * Returns rustls_result::NoDefaultCryptoProvider if no process default crypto + * provider has been set, and the builder was not constructed with an explicit + * provider choice. * * The callback must not capture any of the pointers in its * rustls_verify_server_cert_params. @@ -1510,7 +1608,8 @@ rustls_result rustls_client_config_builder_set_certified_key(struct rustls_clien * Turn a *rustls_client_config_builder (mutable) into a const *rustls_client_config * (read-only). */ -const struct rustls_client_config *rustls_client_config_builder_build(struct rustls_client_config_builder *builder); +rustls_result rustls_client_config_builder_build(struct rustls_client_config_builder *builder, + const struct rustls_client_config **config_out); /** * "Free" a client_config_builder without building it into a rustls_client_config. @@ -1706,12 +1805,25 @@ void rustls_connection_get_alpn_protocol(const struct rustls_connection *conn, uint16_t rustls_connection_get_protocol_version(const struct rustls_connection *conn); /** - * Retrieves the cipher suite agreed with the peer. - * This returns NULL until the ciphersuite is agreed. - * The returned pointer lives as long as the program. + * Retrieves the [IANA registered cipher suite identifier][IANA] agreed with the peer. + * + * This returns `TLS_NULL_WITH_NULL_NULL` (0x0000) until the ciphersuite is agreed. + * + * [IANA]: + */ +uint16_t rustls_connection_get_negotiated_ciphersuite(const struct rustls_connection *conn); + +/** + * Retrieves the cipher suite name agreed with the peer. + * + * This returns "" until the ciphersuite is agreed. + * + * The lifetime of the `rustls_str` is the lifetime of the program, it does not + * need to be freed. + * * */ -const struct rustls_supported_ciphersuite *rustls_connection_get_negotiated_ciphersuite(const struct rustls_connection *conn); +struct rustls_str rustls_connection_get_negotiated_ciphersuite_name(const struct rustls_connection *conn); /** * Write up to `count` plaintext bytes from `buf` into the `rustls_connection`. @@ -1773,6 +1885,209 @@ rustls_result rustls_connection_read_2(struct rustls_connection *conn, */ void rustls_connection_free(struct rustls_connection *conn); +/** + * Constructs a new `rustls_crypto_provider_builder` using the process-wide default crypto + * provider as the base crypto provider to be customized. + * + * When this function returns `rustls_result::Ok` a pointer to the `rustls_crypto_provider_builder` + * is written to `builder_out`. It returns `rustls_result::NoDefaultCryptoProvider` if no default + * provider has been registered. + * + * The caller owns the returned `rustls_crypto_provider_builder` and must free it using + * `rustls_crypto_provider_builder_free`. + * + * This function is typically used for customizing the default crypto provider for specific + * connections. For example, a typical workflow might be to: + * + * * Either: + * * Use the default `aws-lc-rs` or `*ring*` provider that rustls-ffi is built with based on + * the `CRYPTO_PROVIDER` build variable. + * * Call `rustls_crypto_provider_builder_new_with_base` with the desired provider, and + * then install it as the process default with + * `rustls_crypto_provider_builder_build_as_default`. + * * Afterward, as required for customization: + * * Use `rustls_crypto_provider_builder_new_from_default` to get a builder backed by the + * default crypto provider. + * * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported + * ciphersuites. + * * Use `rustls_crypto_provider_builder_build` to build a customized provider. + * * Provide that customized provider to client or server configuration builders. + */ +rustls_result rustls_crypto_provider_builder_new_from_default(struct rustls_crypto_provider_builder **builder_out); + +/** + * Constructs a new `rustls_crypto_provider_builder` using the given `rustls_crypto_provider` + * as the base crypto provider to be customized. + * + * The caller owns the returned `rustls_crypto_provider_builder` and must free it using + * `rustls_crypto_provider_builder_free`. + * + * This function can be used for setting the default process wide crypto provider, + * or for constructing a custom crypto provider for a specific connection. A typical + * workflow could be to: + * + * * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider + * * Install the custom provider as the process-wide default with + * `rustls_crypto_provider_builder_build_as_default`. + * + * Or, for per-connection customization: + * + * * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider + * * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported + * ciphersuites. + * * Use `rustls_crypto_provider_builder_build` to build a customized provider. + * * Provide that customized provider to client or server configuration builders. + */ +struct rustls_crypto_provider_builder *rustls_crypto_provider_builder_new_with_base(const struct rustls_crypto_provider *base); + +/** + * Customize the supported ciphersuites of the `rustls_crypto_provider_builder`. + * + * Returns an error if the builder has already been built. Overwrites any previously + * set ciphersuites. + */ +rustls_result rustls_crypto_provider_builder_set_cipher_suites(struct rustls_crypto_provider_builder *builder, + const struct rustls_supported_ciphersuite *const *cipher_suites, + size_t cipher_suites_len); + +/** + * Builds a `rustls_crypto_provider` from the builder and returns it. Returns an error if the + * builder has already been built. + * + * The `rustls_crypto_provider_builder` builder is consumed and should not be used + * for further calls, except to `rustls_crypto_provider_builder_free`. The caller must + * still free the builder after a successful build. + */ +rustls_result rustls_crypto_provider_builder_build(struct rustls_crypto_provider_builder *builder, + const struct rustls_crypto_provider **provider_out); + +/** + * Builds a `rustls_crypto_provider` from the builder and sets it as the + * process-wide default crypto provider. + * + * Afterward, the default provider can be retrieved using `rustls_crypto_provider_default`. + * + * This can only be done once per process, and will return an error if a + * default provider has already been set, or if the builder has already been built. + * + * The `rustls_crypto_provider_builder` builder is consumed and should not be used + * for further calls, except to `rustls_crypto_provider_builder_free`. The caller must + * still free the builder after a successful build. + */ +rustls_result rustls_crypto_provider_builder_build_as_default(struct rustls_crypto_provider_builder *builder); + +/** + * Free the `rustls_crypto_provider_builder`. + * + * Calling with `NULL` is fine. + * Must not be called twice with the same value. + */ +void rustls_crypto_provider_builder_free(struct rustls_crypto_provider_builder *builder); + +#if defined(DEFINE_RING) +/** + * Return the `rustls_crypto_provider` backed by the `*ring*` cryptography library. + * + * The caller owns the returned `rustls_crypto_provider` and must free it using + * `rustls_crypto_provider_free`. + */ +const struct rustls_crypto_provider *rustls_ring_crypto_provider(void); +#endif + +#if defined(DEFINE_AWS_LC_RS) +/** + * Return the `rustls_crypto_provider` backed by the `aws-lc-rs` cryptography library. + * + * The caller owns the returned `rustls_crypto_provider` and must free it using + * `rustls_crypto_provider_free`. + */ +const struct rustls_crypto_provider *rustls_aws_lc_rs_crypto_provider(void); +#endif + +/** + * Retrieve a pointer to the process default `rustls_crypto_provider`. + * + * This may return `NULL` if no process default provider has been set using + * `rustls_crypto_provider_builder_build_default`. + * + * Caller owns the returned `rustls_crypto_provider` and must free it w/ `rustls_crypto_provider_free`. + */ +const struct rustls_crypto_provider *rustls_crypto_provider_default(void); + +/** + * Returns the number of ciphersuites the `rustls_crypto_provider` supports. + * + * You can use this to know the maximum allowed index for use with + * `rustls_crypto_provider_ciphersuites_get`. + * + * This function will return 0 if the `provider` is NULL. + */ +size_t rustls_crypto_provider_ciphersuites_len(const struct rustls_crypto_provider *provider); + +/** + * Retrieve a pointer to a supported ciphersuite of the `rustls_crypto_provider`. + * + * This function will return NULL if the `provider` is NULL, or if the index is out of bounds + * with respect to `rustls_crypto_provider_ciphersuites_len`. + * + * The lifetime of the returned `rustls_supported_ciphersuite` is equal to the lifetime of the + * `provider` and should not be used after the `provider` is freed. + */ +const struct rustls_supported_ciphersuite *rustls_crypto_provider_ciphersuites_get(const struct rustls_crypto_provider *provider, + size_t index); + +/** + * Load a private key from the provided PEM content using the crypto provider. + * + * `private_key` must point to a buffer of `private_key_len` bytes, containing + * a PEM-encoded private key. The exact formats supported will differ based on + * the crypto provider in use. The default providers support PKCS#1, PKCS#8 or + * SEC1 formats. + * + * When this function returns `rustls_result::Ok` a pointer to a `rustls_signing_key` + * is written to `signing_key_out`. The caller owns the returned `rustls_signing_key` + * and must free it with `rustls_signing_key_free`. + */ +rustls_result rustls_crypto_provider_load_key(const struct rustls_crypto_provider *provider, + const uint8_t *private_key, + size_t private_key_len, + struct rustls_signing_key **signing_key_out); + +/** + * Frees the `rustls_crypto_provider`. + * + * Calling with `NULL` is fine. + * Must not be called twice with the same value. + */ +void rustls_crypto_provider_free(const struct rustls_crypto_provider *provider); + +/** + * Returns the number of ciphersuites the default process-wide crypto provider supports. + * + * You can use this to know the maximum allowed index for use with + * `rustls_default_crypto_provider_ciphersuites_get`. + * + * This function will return 0 if no process-wide default `rustls_crypto_provider` is available. + */ +size_t rustls_default_crypto_provider_ciphersuites_len(void); + +/** + * Retrieve a pointer to a supported ciphersuite of the default process-wide crypto provider. + * + * This function will return NULL if the `provider` is NULL, or if the index is out of bounds + * with respect to `rustls_default_crypto_provider_ciphersuites_len`. + * + * The lifetime of the returned `rustls_supported_ciphersuite` is static, as the process-wide + * default provider lives for as long as the process. + */ +const struct rustls_supported_ciphersuite *rustls_default_crypto_provider_ciphersuites_get(size_t index); + +/** + * Frees the `rustls_signing_key`. This is safe to call with a `NULL` argument, but + * must not be called twice with the same value. + */ +void rustls_signing_key_free(struct rustls_signing_key *signing_key); + /** * After a rustls function returns an error, you may call * this to get a pointer to a buffer containing a detailed error @@ -1821,7 +2136,7 @@ size_t rustls_slice_str_len(const struct rustls_slice_str *input); struct rustls_str rustls_slice_str_get(const struct rustls_slice_str *input, size_t n); /** - * Create a rustls_server_config_builder. + * Create a rustls_server_config_builder using the process default crypto provider. * * Caller owns the memory and must eventually call rustls_server_config_builder_build, * then free the resulting rustls_server_config. @@ -1829,13 +2144,13 @@ struct rustls_str rustls_slice_str_get(const struct rustls_slice_str *input, siz * Alternatively, if an error occurs or, you don't wish to build a config, call * `rustls_server_config_builder_free` to free the builder directly. * - * This uses rustls safe default values for the cipher suites, key exchange groups - * and protocol versions. + * This uses the process default provider's values for the cipher suites and key exchange + * groups, as well as safe defaults for protocol versions. */ struct rustls_server_config_builder *rustls_server_config_builder_new(void); /** - * Create a rustls_server_config_builder. + * Create a rustls_server_config_builder using the specified crypto provider. * * Caller owns the memory and must eventually call rustls_server_config_builder_build, * then free the resulting rustls_server_config. @@ -1843,10 +2158,6 @@ struct rustls_server_config_builder *rustls_server_config_builder_new(void); * Alternatively, if an error occurs or, you don't wish to build a config, call * `rustls_server_config_builder_free` to free the builder directly. * - * Specify cipher suites in preference order; the `cipher_suites` parameter must - * point to an array containing `len` pointers to `rustls_supported_ciphersuite` - * previously obtained from `rustls_all_ciphersuites_get_entry()`. - * * `tls_versions` set the TLS protocol versions to use when negotiating a TLS session. * * `tls_versions` is the version of the protocol, as defined in rfc8446, @@ -1856,9 +2167,11 @@ struct rustls_server_config_builder *rustls_server_config_builder_new(void); * `tls_versions` will only be used during the call and the application retains * ownership. `tls_versions_len` is the number of consecutive `uint16_t` pointed * to by `tls_versions`. + * + * Ciphersuites are configured separately via the crypto provider. See + * `rustls_crypto_provider_builder_set_cipher_suites` for more information. */ -rustls_result rustls_server_config_builder_new_custom(const struct rustls_supported_ciphersuite *const *cipher_suites, - size_t cipher_suites_len, +rustls_result rustls_server_config_builder_new_custom(const struct rustls_crypto_provider *provider, const uint16_t *tls_versions, size_t tls_versions_len, struct rustls_server_config_builder **builder_out); @@ -1931,9 +2244,15 @@ rustls_result rustls_server_config_builder_set_certified_keys(struct rustls_serv /** * Turn a *rustls_server_config_builder (mutable) into a const *rustls_server_config - * (read-only). + * (read-only). The constructed `rustls_server_config` will be written to the `config_out` + * pointer when this function returns `rustls_result::Ok`. + * + * This function may return an error if no process default crypto provider has been set + * and the builder was constructed using `rustls_server_config_builder_new`, or if no + * certificate resolver was set. */ -const struct rustls_server_config *rustls_server_config_builder_build(struct rustls_server_config_builder *builder); +rustls_result rustls_server_config_builder_build(struct rustls_server_config_builder *builder, + const struct rustls_server_config **config_out); /** * "Free" a rustls_server_config previously returned from @@ -2035,4 +2354,4 @@ rustls_result rustls_server_config_builder_set_persistence(struct rustls_server_ rustls_session_store_get_callback get_cb, rustls_session_store_put_callback put_cb); -#endif /* RUSTLS_H */ +#endif /* RUSTLS_H */ diff --git a/src/server.rs b/src/server.rs index 9ef3fda2..0e94ac6b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,33 +1,32 @@ use std::ffi::c_void; use std::fmt::{Debug, Formatter}; -use std::ptr::null; use std::slice; use std::sync::Arc; use libc::size_t; -use rustls::crypto::ring::ALL_CIPHER_SUITES; +use rustls::crypto::CryptoProvider; use rustls::server::danger::ClientCertVerifier; use rustls::server::{ ClientHello, ResolvesServerCert, ServerConfig, ServerConnection, StoresServerSessions, WebPkiClientVerifier, }; use rustls::sign::CertifiedKey; -use rustls::{ProtocolVersion, SignatureScheme, WantsVerifier}; +use rustls::{ProtocolVersion, SignatureScheme, SupportedProtocolVersion}; -use crate::cipher::{ - rustls_certified_key, rustls_client_cert_verifier, rustls_supported_ciphersuite, -}; +use crate::cipher::{rustls_certified_key, rustls_client_cert_verifier}; use crate::connection::{rustls_connection, Connection}; -use crate::error::rustls_result::{InvalidParameter, NullParameter}; +use crate::crypto_provider::rustls_crypto_provider; +use crate::error::rustls_result::NullParameter; use crate::error::{map_error, rustls_result}; use crate::rslice::{rustls_slice_bytes, rustls_slice_slice_bytes, rustls_slice_u16, rustls_str}; use crate::session::{ rustls_session_store_get_callback, rustls_session_store_put_callback, SessionStoreBroker, }; use crate::{ - arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, set_boxed_mut_ptr, - to_arc_const_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, try_mut_from_ptr, - try_mut_from_ptr_ptr, try_ref_from_ptr, try_slice, userdata_get, Castable, OwnershipRef, + arc_castable, box_castable, crypto_provider, ffi_panic_boundary, free_arc, free_box, + set_arc_mut_ptr, set_boxed_mut_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, + try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, + userdata_get, Castable, OwnershipRef, }; box_castable! { @@ -47,7 +46,8 @@ box_castable! { } pub(crate) struct ServerConfigBuilder { - base: rustls::ConfigBuilder, + provider: Option>, + versions: Vec<&'static SupportedProtocolVersion>, verifier: Arc, cert_resolver: Option>, session_storage: Option>, @@ -64,7 +64,7 @@ arc_castable! { } impl rustls_server_config_builder { - /// Create a rustls_server_config_builder. + /// Create a rustls_server_config_builder using the process default crypto provider. /// /// Caller owns the memory and must eventually call rustls_server_config_builder_build, /// then free the resulting rustls_server_config. @@ -72,20 +72,14 @@ impl rustls_server_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, call /// `rustls_server_config_builder_free` to free the builder directly. /// - /// This uses rustls safe default values for the cipher suites, key exchange groups - /// and protocol versions. + /// This uses the process default provider's values for the cipher suites and key exchange + /// groups, as well as safe defaults for protocol versions. #[no_mangle] pub extern "C" fn rustls_server_config_builder_new() -> *mut rustls_server_config_builder { ffi_panic_boundary! { - // Unwrap safety: *ring* default provider always has ciphersuites compatible with the - // default protocol versions. - let base = ServerConfig::builder_with_provider( - rustls::crypto::ring::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .unwrap(); let builder = ServerConfigBuilder { - base, + provider: crypto_provider::get_default_or_install_from_crate_features(), + versions: rustls::DEFAULT_VERSIONS.to_vec(), verifier: WebPkiClientVerifier::no_client_auth(), cert_resolver: None, session_storage: None, @@ -96,7 +90,7 @@ impl rustls_server_config_builder { } } - /// Create a rustls_server_config_builder. + /// Create a rustls_server_config_builder using the specified crypto provider. /// /// Caller owns the memory and must eventually call rustls_server_config_builder_build, /// then free the resulting rustls_server_config. @@ -104,10 +98,6 @@ impl rustls_server_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, call /// `rustls_server_config_builder_free` to free the builder directly. /// - /// Specify cipher suites in preference order; the `cipher_suites` parameter must - /// point to an array containing `len` pointers to `rustls_supported_ciphersuite` - /// previously obtained from `rustls_all_ciphersuites_get_entry()`. - /// /// `tls_versions` set the TLS protocol versions to use when negotiating a TLS session. /// /// `tls_versions` is the version of the protocol, as defined in rfc8446, @@ -117,28 +107,18 @@ impl rustls_server_config_builder { /// `tls_versions` will only be used during the call and the application retains /// ownership. `tls_versions_len` is the number of consecutive `uint16_t` pointed /// to by `tls_versions`. + /// + /// Ciphersuites are configured separately via the crypto provider. See + /// `rustls_crypto_provider_builder_set_cipher_suites` for more information. #[no_mangle] pub extern "C" fn rustls_server_config_builder_new_custom( - cipher_suites: *const *const rustls_supported_ciphersuite, - cipher_suites_len: size_t, + provider: *const rustls_crypto_provider, tls_versions: *const u16, tls_versions_len: size_t, builder_out: *mut *mut rustls_server_config_builder, ) -> rustls_result { ffi_panic_boundary! { - if builder_out.is_null() { - return NullParameter; - } - let cipher_suites = try_slice!(cipher_suites, cipher_suites_len); - let mut cs_vec = Vec::new(); - for &cs in cipher_suites.iter() { - let cs = try_ref_from_ptr!(cs); - match ALL_CIPHER_SUITES.iter().find(|&acs| cs.eq(acs)) { - Some(scs) => cs_vec.push(*scs), - None => return InvalidParameter, - } - } - + let provider = try_clone_arc!(provider); let tls_versions = try_slice!(tls_versions, tls_versions_len); let mut versions = vec![]; for version_number in tls_versions { @@ -149,22 +129,11 @@ impl rustls_server_config_builder { versions.push(&rustls::version::TLS13); } } - let builder_out = try_mut_from_ptr_ptr!(builder_out); - let provider = rustls::crypto::CryptoProvider { - cipher_suites: cs_vec, - ..rustls::crypto::ring::default_provider() - }; - let result = rustls::ServerConfig::builder_with_provider(provider.into()) - .with_protocol_versions(&versions); - let base = match result { - Ok(new) => new, - Err(_) => return rustls_result::InvalidParameter, - }; - let builder = ServerConfigBuilder { - base, + provider: Some(provider), + versions, verifier: WebPkiClientVerifier::no_client_auth(), cert_resolver: None, session_storage: None, @@ -287,18 +256,38 @@ impl rustls_server_config_builder { } /// Turn a *rustls_server_config_builder (mutable) into a const *rustls_server_config - /// (read-only). + /// (read-only). The constructed `rustls_server_config` will be written to the `config_out` + /// pointer when this function returns `rustls_result::Ok`. + /// + /// This function may return an error if no process default crypto provider has been set + /// and the builder was constructed using `rustls_server_config_builder_new`, or if no + /// certificate resolver was set. #[no_mangle] pub extern "C" fn rustls_server_config_builder_build( builder: *mut rustls_server_config_builder, - ) -> *const rustls_server_config { + config_out: *mut *const rustls_server_config, + ) -> rustls_result { ffi_panic_boundary! { let builder = try_box_from_ptr!(builder); - let base = builder.base.with_client_cert_verifier(builder.verifier); + let config_out = try_ref_from_ptr_ptr!(config_out); + + let provider = match builder.provider { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + + let base = match ServerConfig::builder_with_provider(provider) + .with_protocol_versions(&builder.versions) + { + Ok(base) => base, + Err(err) => return map_error(err), + } + .with_client_cert_verifier(builder.verifier); + let mut config = if let Some(r) = builder.cert_resolver { base.with_cert_resolver(r) } else { - return null(); + return rustls_result::General; }; if let Some(ss) = builder.session_storage { config.session_storage = ss; @@ -307,7 +296,9 @@ impl rustls_server_config_builder { if let Some(ignore_client_order) = builder.ignore_client_order { config.ignore_client_order = ignore_client_order; } - to_arc_const_ptr(config) + + set_arc_mut_ptr(config_out, config); + rustls_result::Ok } } } @@ -692,11 +683,13 @@ impl rustls_server_config_builder { #[cfg(test)] mod tests { + use std::ptr::null; use std::ptr::null_mut; use super::*; #[test] + #[cfg_attr(miri, ignore)] fn test_config_builder() { let builder = rustls_server_config_builder::rustls_server_config_builder_new(); let h1 = "http/1.1".as_bytes(); @@ -707,7 +700,34 @@ mod tests { alpn.as_ptr(), alpn.len(), ); - let config = rustls_server_config_builder::rustls_server_config_builder_build(builder); + + let cert_pem = include_str!("../testdata/localhost/cert.pem").as_bytes(); + let key_pem = include_str!("../testdata/localhost/key.pem").as_bytes(); + let mut certified_key = null(); + let result = rustls_certified_key::rustls_certified_key_build( + cert_pem.as_ptr(), + cert_pem.len(), + key_pem.as_ptr(), + key_pem.len(), + &mut certified_key, + ); + if !matches!(result, rustls_result::Ok) { + panic!( + "expected RUSTLS_RESULT_OK from rustls_certified_key_build, got {:?}", + result + ); + } + rustls_server_config_builder::rustls_server_config_builder_set_certified_keys( + builder, + &certified_key, + 1, + ); + + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); { let config2 = try_ref_from_ptr!(config); assert_eq!(config2.alpn_protocols, vec![h1, h2]); @@ -719,11 +739,12 @@ mod tests { #[test] fn test_server_config_builder_new_empty() { let builder = rustls_server_config_builder::rustls_server_config_builder_new(); - // Building a config with no certificate and key configured results in null. - assert_eq!( - rustls_server_config_builder::rustls_server_config_builder_build(builder), - null() - ); + // Building a config with no certificate and key configured results in an error. + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::General); + assert!(config.is_null()); } #[test] @@ -752,8 +773,11 @@ mod tests { 1, ); - let config = rustls_server_config_builder::rustls_server_config_builder_build(builder); - assert_ne!(config, null()); + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut conn = null_mut(); let result = rustls_server_config::rustls_server_connection_new(config, &mut conn); @@ -777,8 +801,10 @@ mod tests { assert_eq!( rustls_connection::rustls_connection_get_negotiated_ciphersuite(conn), - null() + 0 ); + let cs_name = rustls_connection::rustls_connection_get_negotiated_ciphersuite_name(conn); + assert_eq!(unsafe { cs_name.to_str() }, ""); assert_eq!( rustls_connection::rustls_connection_get_peer_certificate(conn, 0), null() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cd30a772..a7b10a04 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,46 +1,52 @@ -IF(WIN32) - add_compile_definitions( - _WIN32_WINNT=0x601 - _CRT_SECURE_NO_WARNINGS - _CRT_NONSTDC_NO_WARNINGS - ssize_t=int - ) -ENDIF(WIN32) +IF (WIN32) + add_compile_definitions( + _WIN32_WINNT=0x601 + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_WARNINGS + ssize_t=int + ) +ENDIF (WIN32) + +if (CRYPTO_PROVIDER STREQUAL "aws_lc_rs") + add_compile_definitions(DEFINE_AWS_LC_RS) +elseif (CRYPTO_PROVIDER STREQUAL "ring") + add_compile_definitions(DEFINE_RING) +endif () add_executable(client client.c common.c) add_dependencies(client rustls-ffi) target_include_directories(client PUBLIC ${CMAKE_SOURCE_DIR}/src) -IF(WIN32) +IF (WIN32) target_link_libraries( - client - debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" - optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" - advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib + client + debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" + optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" + advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib ) set_property(TARGET client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") -ENDIF(WIN32) -IF(UNIX) +ENDIF (WIN32) +IF (UNIX) # TODO -ENDIF(UNIX) -IF(APPLE) +ENDIF (UNIX) +IF (APPLE) # TODO -ENDIF(APPLE) +ENDIF (APPLE) add_executable(server server.c common.c) add_dependencies(server rustls-ffi) target_include_directories(server PUBLIC ${CMAKE_SOURCE_DIR}/src) -IF(WIN32) +IF (WIN32) target_link_libraries( - server - debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" - optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" - advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib + server + debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" + optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" + advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib ) set_property(TARGET server PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") -ENDIF(WIN32) -IF(UNIX) +ENDIF (WIN32) +IF (UNIX) # TODO -ENDIF(UNIX) -IF(APPLE) +ENDIF (UNIX) +IF (APPLE) # TODO -ENDIF(APPLE) +ENDIF (APPLE) diff --git a/tests/client.c b/tests/client.c index fe01f1e1..65ee6218 100644 --- a/tests/client.c +++ b/tests/client.c @@ -170,6 +170,8 @@ send_request_and_read_response(struct conndata *conn, unsigned long content_length = 0; size_t headers_len = 0; struct rustls_str version; + int ciphersuite_id; + struct rustls_str ciphersuite_name; version = rustls_version(); memset(buf, '\0', sizeof(buf)); @@ -197,6 +199,13 @@ send_request_and_read_response(struct conndata *conn, goto cleanup; } + ciphersuite_id = rustls_connection_get_negotiated_ciphersuite(rconn); + ciphersuite_name = rustls_connection_get_negotiated_ciphersuite_name(rconn); + LOG("negotiated ciphersuite: %.*s (%#x)", + (int)ciphersuite_name.len, + ciphersuite_name.data, + ciphersuite_id); + for(;;) { FD_ZERO(&read_fds); /* These two calls just inspect the state of the connection - if it's time @@ -411,8 +420,8 @@ main(int argc, const char **argv) /* Set this global variable for logging purposes. */ programname = "client"; - struct rustls_client_config_builder *config_builder = - rustls_client_config_builder_new(); + const struct rustls_crypto_provider *custom_provider = NULL; + struct rustls_client_config_builder *config_builder = NULL; struct rustls_root_cert_store_builder *server_cert_root_store_builder = NULL; const struct rustls_root_cert_store *server_cert_root_store = NULL; const struct rustls_client_config *client_config = NULL; @@ -431,9 +440,32 @@ main(int argc, const char **argv) setmode(STDOUT_FILENO, O_BINARY); #endif + const char *custom_ciphersuite_name = getenv("RUSTLS_CIPHERSUITE"); + if(custom_ciphersuite_name != NULL) { + custom_provider = + default_provider_with_custom_ciphersuite(custom_ciphersuite_name); + if(custom_provider == NULL) { + goto cleanup; + } + printf("customized to use ciphersuite: %s\n", custom_ciphersuite_name); + + result = rustls_client_config_builder_new_custom(custom_provider, + default_tls_versions, + default_tls_versions_len, + &config_builder); + if(result != RUSTLS_RESULT_OK) { + print_error("creating client config builder", result); + goto cleanup; + } + } + else { + config_builder = rustls_client_config_builder_new(); + } + if(getenv("RUSTLS_PLATFORM_VERIFIER")) { - server_cert_verifier = rustls_platform_server_cert_verifier(); - if(server_cert_verifier == NULL) { + result = rustls_platform_server_cert_verifier(&server_cert_verifier); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "client: failed to construct platform verifier\n"); goto cleanup; } rustls_client_config_builder_set_server_verifier(config_builder, @@ -494,7 +526,11 @@ main(int argc, const char **argv) rustls_client_config_builder_set_alpn_protocols( config_builder, &alpn_http11, 1); - client_config = rustls_client_config_builder_build(config_builder); + result = rustls_client_config_builder_build(config_builder, &client_config); + if(result != RUSTLS_RESULT_OK) { + print_error("building client config", result); + goto cleanup; + } int i; for(i = 0; i < 3; i++) { @@ -515,6 +551,7 @@ main(int argc, const char **argv) rustls_server_cert_verifier_free(server_cert_verifier); rustls_certified_key_free(certified_key); rustls_client_config_free(client_config); + rustls_crypto_provider_free(custom_provider); #ifdef _WIN32 WSACleanup(); diff --git a/tests/client_server.rs b/tests/client_server.rs index d07b05b1..d0820c2a 100644 --- a/tests/client_server.rs +++ b/tests/client_server.rs @@ -92,11 +92,40 @@ fn client_server_integration() { ], }; + let custom_ciphersuites = TestCase { + name: "client/server with limited ciphersuites", + server_opts: ServerOptions { + valgrind: valgrind.clone(), + env: vec![("RUSTLS_CIPHERSUITE", "TLS13_CHACHA20_POLY1305_SHA256")], + }, + client_tests: vec![ + ClientTest { + name: "limited ciphersuite, supported by server", + valgrind: valgrind.clone(), + env: vec![ + ("NO_CHECK_CERTIFICATE", "1"), + ("RUSTLS_CIPHERSUITE", "TLS13_CHACHA20_POLY1305_SHA256"), + ], + expect_error: false, + }, + ClientTest { + name: "limited ciphersuite, not supported by server", + valgrind: valgrind.clone(), + env: vec![ + ("NO_CHECK_CERTIFICATE", "1"), + ("RUSTLS_CIPHERSUITE", "TLS13_AES_128_GCM_SHA256"), + ], + expect_error: true, // Unsupported ciphersuite. + }, + ], + }; + TestCases(vec![ standard_server, vectored_server, mandatory_client_auth_server, mandatory_client_auth_server_with_crls, + custom_ciphersuites, ]) .run(); } diff --git a/tests/common.c b/tests/common.c index 20cd537a..55d2084a 100644 --- a/tests/common.c +++ b/tests/common.c @@ -385,3 +385,67 @@ load_cert_and_key(const char *certfile, const char *keyfile) } return certified_key; } + +const struct rustls_crypto_provider * +default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) +{ + const struct rustls_supported_ciphersuite *custom_ciphersuite = NULL; + rustls_crypto_provider_builder *provider_builder = NULL; + const struct rustls_crypto_provider *custom_provider = NULL; + + size_t num_supported = rustls_default_crypto_provider_ciphersuites_len(); + for(size_t i = 0; i < num_supported; i++) { + const struct rustls_supported_ciphersuite *suite = + rustls_default_crypto_provider_ciphersuites_get(i); + if(suite == NULL) { + fprintf(stderr, "failed to get ciphersuite %zu\n", i); + goto cleanup; + } + + const rustls_str suite_name = rustls_supported_ciphersuite_get_name(suite); + if(strncmp(suite_name.data, custom_ciphersuite_name, suite_name.len) == + 0) { + custom_ciphersuite = suite; + break; + } + } + + if(custom_ciphersuite == NULL) { + fprintf(stderr, + "failed to select custom ciphersuite: %s\n", + custom_ciphersuite_name); + goto cleanup; + } + + rustls_result result = + rustls_crypto_provider_builder_new_from_default(&provider_builder); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "failed to create provider builder\n"); + goto cleanup; + } + + result = rustls_crypto_provider_builder_set_cipher_suites( + provider_builder, &custom_ciphersuite, 1); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "failed to set custom ciphersuite\n"); + goto cleanup; + } + + result = + rustls_crypto_provider_builder_build(provider_builder, &custom_provider); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "failed to build custom provider\n"); + goto cleanup; + } + +cleanup: + rustls_crypto_provider_builder_free(provider_builder); + return custom_provider; +} + +// TLS 1.2 and TLS 1.3, matching Rustls default. +const uint16_t default_tls_versions[] = { 0x0303, 0x0304 }; + +// Declare the length of the TLS versions array as a global constant +const size_t default_tls_versions_len = + sizeof(default_tls_versions) / sizeof(default_tls_versions[0]); diff --git a/tests/common.h b/tests/common.h index edd3ceab..1980c055 100644 --- a/tests/common.h +++ b/tests/common.h @@ -133,4 +133,10 @@ enum demo_result read_file(const char *filename, char *buf, size_t buflen, const struct rustls_certified_key *load_cert_and_key(const char *certfile, const char *keyfile); +const struct rustls_crypto_provider *default_provider_with_custom_ciphersuite( + const char *custom_ciphersuite_name); + +extern const uint16_t default_tls_versions[]; +extern const size_t default_tls_versions_len; + #endif /* COMMON_H */ diff --git a/tests/rustls_version.rs b/tests/rustls_version.rs index 446cd58b..bf2a5358 100644 --- a/tests/rustls_version.rs +++ b/tests/rustls_version.rs @@ -1,7 +1,6 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; -use std::{slice, str}; use toml::Table; @@ -35,6 +34,14 @@ fn rustls_version_match() { .as_str() .expect("missing crate version"); + let rustls_crypto_provider = { + if cfg!(all(feature = "ring", not(feature = "aws-lc-rs"))) { + "ring" + } else { + "aws-lc-rs" + } + }; + // Find the rustls dependency version specified in Cargo.toml let deps = metadata["dependencies"].as_table().unwrap(); let rustls_dep = &deps["rustls"]; @@ -51,12 +58,7 @@ fn rustls_version_match() { // E.g.: // rustls-ffi/0.13.0/rustls/0.23.4 let rustls_ffi_version = rustls_version(); - let rustls_ffi_version = unsafe { - str::from_utf8_unchecked(slice::from_raw_parts( - rustls_ffi_version.data as *const u8, - rustls_ffi_version.len, - )) - }; + let rustls_ffi_version = unsafe { rustls_ffi_version.to_str() }; let rustls_ffi_version_parts = rustls_ffi_version.split('/').collect::>(); assert_eq!( rustls_ffi_version_parts, @@ -65,6 +67,7 @@ fn rustls_version_match() { crate_version, "rustls", rustls_dep_version, + rustls_crypto_provider, ] ); } diff --git a/tests/server.c b/tests/server.c index c0a3a4f0..b3532720 100644 --- a/tests/server.c +++ b/tests/server.c @@ -127,6 +127,8 @@ handle_conn(struct conndata *conn) int sockfd = conn->fd; struct timeval tv; enum exchange_state state = READING_REQUEST; + int ciphersuite_id; + struct rustls_str ciphersuite_name; LOG("acccepted conn on fd %d", conn->fd); @@ -189,6 +191,14 @@ handle_conn(struct conndata *conn) if(state == READING_REQUEST && body_beginning(&conn->data) != NULL) { state = SENT_RESPONSE; LOG_SIMPLE("writing response"); + ciphersuite_id = rustls_connection_get_negotiated_ciphersuite(rconn); + ciphersuite_name = + rustls_connection_get_negotiated_ciphersuite_name(rconn); + LOG("negotiated ciphersuite: %.*s (%#x)", + (int)ciphersuite_name.len, + ciphersuite_name.data, + ciphersuite_id); + rustls_connection_get_alpn_protocol( rconn, &negotiated_alpn, &negotiated_alpn_len); if(negotiated_alpn != NULL) { @@ -232,8 +242,9 @@ main(int argc, const char **argv) { int ret = 1; int sockfd = 0; - struct rustls_server_config_builder *config_builder = - rustls_server_config_builder_new(); + + const struct rustls_crypto_provider *custom_provider = NULL; + struct rustls_server_config_builder *config_builder = NULL; const struct rustls_server_config *server_config = NULL; struct rustls_connection *rconn = NULL; const struct rustls_certified_key *certified_key = NULL; @@ -243,6 +254,7 @@ main(int argc, const char **argv) struct rustls_web_pki_client_cert_verifier_builder *client_cert_verifier_builder = NULL; struct rustls_client_cert_verifier *client_cert_verifier = NULL; + rustls_result result = RUSTLS_RESULT_OK; /* Set this global variable for logging purposes. */ programname = "server"; @@ -267,6 +279,28 @@ main(int argc, const char **argv) goto cleanup; } + const char *custom_ciphersuite_name = getenv("RUSTLS_CIPHERSUITE"); + if(custom_ciphersuite_name != NULL) { + custom_provider = + default_provider_with_custom_ciphersuite(custom_ciphersuite_name); + if(custom_provider == NULL) { + goto cleanup; + } + printf("customized to use ciphersuite: %s\n", custom_ciphersuite_name); + + result = rustls_server_config_builder_new_custom(custom_provider, + default_tls_versions, + default_tls_versions_len, + &config_builder); + if(result != RUSTLS_RESULT_OK) { + print_error("creating client config builder", result); + goto cleanup; + } + } + else { + config_builder = rustls_server_config_builder_new(); + } + certified_key = load_cert_and_key(argv[1], argv[2]); if(certified_key == NULL) { goto cleanup; @@ -326,7 +360,11 @@ main(int argc, const char **argv) client_cert_verifier); } - server_config = rustls_server_config_builder_build(config_builder); + result = rustls_server_config_builder_build(config_builder, &server_config); + if(result != RUSTLS_RESULT_OK) { + print_error("building server config", result); + goto cleanup; + } #ifdef _WIN32 WSADATA wsa; @@ -412,6 +450,7 @@ main(int argc, const char **argv) rustls_client_cert_verifier_free(client_cert_verifier); rustls_server_config_free(server_config); rustls_connection_free(rconn); + rustls_crypto_provider_free(custom_provider); if(sockfd > 0) { close(sockfd); }