diff --git a/.github/workflows/daily-tests.yml b/.github/workflows/daily-tests.yml index df4c0c16..3c888005 100644 --- a/.github/workflows/daily-tests.yml +++ b/.github/workflows/daily-tests.yml @@ -130,3 +130,66 @@ jobs: shell: bash run: | grep 'sni=encrypted' ech-test.log + + pq: + name: "Post-quantum (${{ matrix.os }})" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Install cargo-c (Ubuntu) + if: matrix.os == 'ubuntu-latest' + env: + LINK: https://github.com/lu-zero/cargo-c/releases/latest/download + CARGO_C_FILE: cargo-c-x86_64-unknown-linux-musl.tar.gz + run: | + curl -L $LINK/$CARGO_C_FILE | tar xz -C ~/.cargo/bin + + - name: Install cargo-c (macOS) + if: matrix.os == 'macos-latest' + env: + LINK: https://github.com/lu-zero/cargo-c/releases/latest/download + CARGO_C_FILE: cargo-c-macos.zip + run: | + curl -L $LINK/$CARGO_C_FILE -o cargo-c-macos.zip + unzip cargo-c-macos.zip -d ~/.cargo/bin + + - name: Install cargo-c (Windows) + if: matrix.os == 'windows-latest' + env: + LINK: https://github.com/lu-zero/cargo-c/releases/latest/download + CARGO_C_FILE: cargo-c-windows-msvc.zip + run: | + curl -L "$env:LINK/$env:CARGO_C_FILE" -o cargo-c-windows-msvc.zip + powershell -Command "Expand-Archive -Path cargo-c-windows-msvc.zip -DestinationPath $env:USERPROFILE\\.cargo\\bin -Force" + + - name: Setup cmake build + run: | + cmake ${{ + matrix.os != 'windows-latest' && '-DCMAKE_BUILD_TYPE=Release -DPOST_QUANTUM=on\' || '-DPOST_QUANTUM=on' + }} ${{ + matrix.os == 'macos-latest' && '-DCMAKE_OSX_DEPLOYMENT_TARGET=14.5' || '' + }} -S librustls -B build + + - name: Run PQ connect test + # NOTE: uses bash as the shell to allow for easy no-powershell tee/grep pipeline. + shell: bash + run: | + cmake --build build --target pq-test ${{ + matrix.os == 'windows-latest' && '--config Release' || '' + }} | tee pq-test.log + + - name: Verify PQ status + shell: bash + run: | + grep 'kex=X25519MLKEM768' pq-test.log diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6835ebbc..21175f17 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,7 +12,7 @@ on: jobs: build: - name: "Build+Test (${{ matrix.os }}, ${{ matrix.cc }}, ${{ matrix.rust }}, ${{ matrix.crypto }}${{ matrix.cert_compression == 'on' && ', cert compression' || '' }}${{ matrix.dyn_link == 'on' && ', dynamic linking' || '' }})" + name: "Build+Test (${{ matrix.os }}, ${{ matrix.cc }}, ${{ matrix.rust }}, ${{ matrix.crypto }}${{ matrix.cert_compression == 'on' && ', cert compression' || '' }}${{ matrix.pq == 'on' && ', post-quantum' || '' }}${{ matrix.dyn_link == 'on' && ', dynamic linking' || '' }})" runs-on: ${{ matrix.os }} strategy: matrix: @@ -42,6 +42,12 @@ jobs: crypto: aws-lc-rs rust: stable cert_compression: on + # Linux pq build + - os: ubuntu-latest + cc: clang + crypto: aws-lc-rs + rust: stable + pq: on # MacOS standard build - os: macos-latest cc: clang @@ -60,6 +66,12 @@ jobs: crypto: aws-lc-rs rust: stable cert_compression: on + # MacOS pq build + - os: macos-latest + cc: clang + crypto: aws-lc-rs + rust: stable + pq: on steps: - name: Checkout sources uses: actions/checkout@v4 @@ -97,6 +109,7 @@ jobs: cmake \ -DCRYPTO_PROVIDER=${{matrix.crypto}} \ -DCERT_COMPRESSION=${{matrix.cert_compression}} \ + -DPOST_QUANTUM=${{matrix.pq}} \ -DDYN_LINK=${{matrix.dyn_link}} \ -DCMAKE_BUILD_TYPE=Debug \ ${{ matrix.os == 'macos-latest' && '-DCMAKE_OSX_DEPLOYMENT_TARGET=14.5' || '' }} \ @@ -117,7 +130,7 @@ jobs: - name: Build release binaries run: | cmake --build build -- clean - CC=${{matrix.cc}} CXX=${{matrix.cc}} cmake -S librustls -B build -DCRYPTO_PROVIDER=${{matrix.crypto}} -DCMAKE_BUILD_TYPE=Release + CC=${{matrix.cc}} CXX=${{matrix.cc}} cmake -S librustls -B build -DCRYPTO_PROVIDER=${{matrix.crypto}} -DPOST_QUANTUM=${{matrix.pq}} -DCMAKE_BUILD_TYPE=Release cmake --build build - name: Verify release builds were not using ASAN @@ -189,7 +202,7 @@ jobs: run: cmake --build build --target integration-test test-windows: - name: "Windows (${{ matrix.crypto }}, ${{ matrix.config }}${{ matrix.cert_compression == 'on' && ', cert compression' || '' }}${{ matrix.dyn_link == 'on' && ', dynamic linking' || '' }})" + name: "Windows (${{ matrix.crypto }}, ${{ matrix.config }}${{ matrix.cert_compression == 'on' && ', cert compression' || '' }}${{ matrix.pq == 'on' && ', post-quantum' || '' }}${{ matrix.dyn_link == 'on' && ', dynamic linking' || '' }})" runs-on: windows-latest strategy: matrix: @@ -206,6 +219,10 @@ jobs: - crypto: aws-lc-rs config: Release cert_compression: on + # One build with pq. + - crypto: aws-lc-rs + config: Release + pq: on steps: - uses: actions/checkout@v4 with: @@ -233,7 +250,7 @@ jobs: powershell -Command "Expand-Archive -Path cargo-c-windows-msvc.zip -DestinationPath $env:USERPROFILE\\.cargo\\bin -Force" - name: Configure CMake - run: cmake -DCRYPTO_PROVIDER="${{ matrix.crypto }}" -DCERT_COMPRESSION="${{ matrix.cert_compression }}" -DDYN_LINK="${{ matrix.dyn_link }}" -S librustls -B build + run: cmake -DCRYPTO_PROVIDER="${{ matrix.crypto }}" -DCERT_COMPRESSION="${{ matrix.cert_compression }}" -DPOST_QUANTUM="${{ matrix.pq }}" -DDYN_LINK="${{ matrix.dyn_link }}" -S librustls -B build - name: Build run: cmake --build build --config "${{ matrix.config }}" diff --git a/Cargo.lock b/Cargo.lock index c94346ae..3055337e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,8 +1204,7 @@ dependencies = [ [[package]] name = "rustls" version = "0.23.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +source = "git+https://github.com/cpu/rustls.git?rev=2008a03eff1f608467a70915109dc100129143d0#2008a03eff1f608467a70915109dc100129143d0" dependencies = [ "aws-lc-rs", "brotli", @@ -1231,6 +1230,7 @@ dependencies = [ "regex", "rustls", "rustls-platform-verifier", + "rustls-post-quantum", "rustls-webpki", "toml", ] @@ -1289,6 +1289,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +[[package]] +name = "rustls-post-quantum" +version = "0.2.2" +source = "git+https://github.com/cpu/rustls.git?rev=2008a03eff1f608467a70915109dc100129143d0#2008a03eff1f608467a70915109dc100129143d0" +dependencies = [ + "aws-lc-rs", + "rustls", +] + [[package]] name = "rustls-webpki" version = "0.102.8" diff --git a/Cargo.toml b/Cargo.toml index 6215128d..e3e6ccc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ resolver = "2" [workspace.dependencies] rustls = { version = "0.23", default-features = false, features = ["std", "tls12"] } +rustls-post-quantum = { version = "0.2.2" } webpki = { package = "rustls-webpki", version = "0.102.0", default-features = false, features = ["std"] } libc = "0.2" log = "0.4.22" @@ -23,3 +24,10 @@ regex = "1.9.6" toml = { version = "0.6.0", default-features = false, features = ["parse"] } hickory-resolver = { version = "=0.25.0-alpha.4", features = ["dns-over-https-rustls", "webpki-roots"] } tokio = { version = "1.42.0", features = ["io-util", "macros", "net", "rt"] } + +# TODO(@cpu): remove this once Rustls 0.23.21 w/ built-in post-quantum support (rustls/rustls#2288) is published. +# we want to avoid rustls-post-quantum 0.2.1 because it activates aws-lc-rs/non-fips +# and so conflicts with our fips feature when running cbindgen with everything activated. +[patch.crates-io] +rustls = { git = 'https://github.com/cpu/rustls.git', rev = '2008a03eff1f608467a70915109dc100129143d0' } +rustls-post-quantum = { git = 'https://github.com/cpu/rustls.git', rev = '2008a03eff1f608467a70915109dc100129143d0' } diff --git a/README.md b/README.md index d2569e5b..8194f5af 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,22 @@ platforms see the upstream documentation: [`*ring*`]: https://crates.io/crates/ring [`*ring*` supported platforms]: https://github.com/briansmith/ring/blob/2e8363b433fa3b3962c877d9ed2e9145612f3160/include/ring-core/target.h#L18-L64 +#### Post-Quantum X25519MLKEM768 Key Exchange + +You can optionally enable use of a hybrid post-quantum secure key exchange ([X25519MLKEM768][]) with the +`--features=x25519mlkem768` flag. + +This feature is **disabled** by default. Enabling this feature will implicitly select `aws-lc-rs` as the +cryptography provider since `*ring*` has no equivalent support at this time. + +When enabled the default provider will include support for the `X25519MLKEM768` key exchange. +A `rustls_crypto_provider` with support can also be constructed with `rustls_post_quantum_provider()`. + +If used with the [`fips`](#fips-140-3) feature, the `rustls_default_fips_provider()` function will +also return a FIPS-enabled provider with support for `X25519MLKEM768`. + +[X25519MLKEM768]: https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem + #### Certificate Compression You can optionally enable [RFC 8879](https://www.rfc-editor.org/rfc/rfc8879) @@ -117,7 +133,8 @@ cargo capi install --features=cert_compression # With cert compression. You can optionally enable FIPS support via `--features=fips`. This implicitly enables the `aws-lc-rs` cryptography provider since `ring` does not have FIPS -140-3 support at this time. +140-3 support at this time. This feature can also be combined with the +[`post-quantum`](#post-quantum-x25519mlkem768-key-exchange) feature. Enabling FIPS mode adds several new build requirements depending on your platform. For example, all platforms will require `cmake` and `go`. Windows will @@ -218,6 +235,8 @@ cmake --build build Use `-DCRYPTO_PROVIDER=ring` to select the cryptography provider for the examples explicitly. +Use `-DPOST_QUANTUM=on` to enable post-quantum X25519MLKEM768 key exchange. + Use `-DCERT_COMPRESSION=on` to enable certificate compression. Use `-DFIPS=on` to enable FIPS mode. diff --git a/librustls/Cargo.toml b/librustls/Cargo.toml index f3b03507..51aed013 100644 --- a/librustls/Cargo.toml +++ b/librustls/Cargo.toml @@ -25,10 +25,12 @@ ring = ["rustls/ring", "webpki/ring"] aws-lc-rs = ["rustls/aws-lc-rs", "webpki/aws_lc_rs"] cert_compression = ["rustls/brotli", "rustls/zlib"] fips = ["aws-lc-rs", "rustls/fips"] +post-quantum = ["aws-lc-rs", "rustls-post-quantum"] [dependencies] # Keep in sync with RUSTLS_CRATE_VERSION in build.rs -rustls = { version = "0.23.18", default-features = false, features = ["std", "tls12"] } +rustls = { version = "0.23.20", default-features = false, features = ["std", "tls12"] } +rustls-post-quantum = { workspace = true, optional = true } webpki = { workspace = true } libc = { workspace = true } log = { workspace = true } diff --git a/librustls/build.rs b/librustls/build.rs index 4e408180..428444da 100644 --- a/librustls/build.rs +++ b/librustls/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.18"; +const RUSTLS_CRATE_VERSION: &str = "0.23.20"; fn main() { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); diff --git a/librustls/cbindgen.toml b/librustls/cbindgen.toml index 7c2a5966..cc06df5a 100644 --- a/librustls/cbindgen.toml +++ b/librustls/cbindgen.toml @@ -15,7 +15,8 @@ include = ["rustls_tls_version"] "feature = aws-lc-rs" = "DEFINE_AWS_LC_RS" "feature = ring" = "DEFINE_RING" "feature = fips" = "DEFINE_FIPS" +"feature = post-quantum" = "DEFINE_POST_QUANTUM" [parse.expand] crates = ["rustls-ffi"] -features = ["read_buf", "aws-lc-rs", "ring", "fips"] +features = ["read_buf", "aws-lc-rs", "ring", "fips", "post-quantum"] diff --git a/librustls/cmake/options.cmake b/librustls/cmake/options.cmake index c021fa62..fa0dd54b 100644 --- a/librustls/cmake/options.cmake +++ b/librustls/cmake/options.cmake @@ -20,6 +20,11 @@ option( option(FIPS "Whether to enable aws-lc-rs and FIPS support") +option( + POST_QUANTUM + "Whether to enable aws-lc-rs and post-quantum key exchange support" +) + option(DYN_LINK "Use dynamic linking for rustls library" OFF) if(DYN_LINK AND FIPS AND (APPLE OR WIN32)) @@ -45,6 +50,10 @@ if(FIPS) list(APPEND CARGO_FEATURES --features=fips) endif() +if(POST_QUANTUM) + list(APPEND CARGO_FEATURES --features=post-quantum) +endif() + # By default w/ Makefile or Ninja generators (e.g. Linux/MacOS CLI) # the `CMAKE_BUILD_TYPE` is "" when using the C/C++ project tooling. # diff --git a/librustls/cmake/rust.cmake b/librustls/cmake/rust.cmake index ca905ac0..0a1c7350 100644 --- a/librustls/cmake/rust.cmake +++ b/librustls/cmake/rust.cmake @@ -98,3 +98,24 @@ add_custom_command( $ cloudflare-ech.com 443 /cdn-cgi/trace WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) + +add_custom_target(pq-test DEPENDS client) + +if(WIN32 AND DYN_LINK) + add_custom_command( + TARGET pq-test + PRE_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/rust/bin/rustls.dll" + "${CMAKE_BINARY_DIR}\\tests\\$\\" + ) +endif() + +add_custom_command( + TARGET pq-test + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E env RUSTLS_PLATFORM_VERIFIER=1 $ + pq.cloudflareresearch.com 443 /cdn-cgi/trace + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +) diff --git a/librustls/src/crypto_provider.rs b/librustls/src/crypto_provider.rs index 3ce5606f..c1b9f13b 100644 --- a/librustls/src/crypto_provider.rs +++ b/librustls/src/crypto_provider.rs @@ -256,6 +256,9 @@ pub extern "C" fn rustls_aws_lc_rs_crypto_provider() -> *const rustls_crypto_pro /// /// See the upstream [rustls FIPS documentation][FIPS] for more information. /// +/// If the `post-quantum` feature is also enabled, this function will return a provider +/// that includes the FIPS approved post-quantum X25519MLKEM768 key exchange group. +/// /// The caller owns the returned `rustls_crypto_provider` and must free it using /// `rustls_crypto_provider_free`. /// @@ -264,8 +267,38 @@ pub extern "C" fn rustls_aws_lc_rs_crypto_provider() -> *const rustls_crypto_pro #[cfg(feature = "fips")] pub extern "C" fn rustls_default_fips_provider() -> *const rustls_crypto_provider { ffi_panic_boundary! { - Arc::into_raw(Arc::new(rustls::crypto::default_fips_provider())) - as *const rustls_crypto_provider + #[cfg(not(feature = "post-quantum"))] + { + Arc::into_raw(Arc::new(rustls::crypto::default_fips_provider())) + as *const rustls_crypto_provider + } + #[cfg(feature = "post-quantum")] + { + let mut fips_provider = rustls::crypto::default_fips_provider(); + fips_provider + .kx_groups + .splice(0..0, vec![rustls_post_quantum::X25519MLKEM768]); + Arc::into_raw(Arc::new(fips_provider)) as *const rustls_crypto_provider + } + } +} + +/// Return a post-quantum enabled `rustls_crypto_provider` backed by the `aws-lc-rs` cryptography +/// library. +/// +/// This `rustls_crypto_provider` is the same as the one returned from +/// `rustls_aws_lc_rs_crypto_provider()`, but includes the post-quantum X25519MLKEM768 key exchange +/// algorithm. +/// +/// Requires the `post-quantum` feature be enabled. +/// +/// The caller owns the returned `rustls_crypto_provider` and must free it using +/// `rustls_crypto_provider_free`. +#[no_mangle] +#[cfg(feature = "post-quantum")] +pub extern "C" fn rustls_post_quantum_provider() -> *const rustls_crypto_provider { + ffi_panic_boundary! { + Arc::into_raw(Arc::new(rustls_post_quantum::provider())) as *const rustls_crypto_provider } } @@ -559,10 +592,19 @@ pub(crate) fn get_default_or_install_from_crate_features() -> Option Option { // Provider default is unambiguously aws-lc-rs - #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] + #[cfg(all( + feature = "aws-lc-rs", + not(feature = "ring"), + not(feature = "post-quantum") + ))] { return Some(aws_lc_rs::default_provider()); } + // Provider default is unambiguously post-qc aws-lc-rs + #[cfg(all(feature = "aws-lc-rs", not(feature = "ring"), feature = "post-quantum"))] + { + return Some(rustls_post_quantum::provider()); + } // Provider default is unambiguously ring #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] diff --git a/librustls/src/rustls.h b/librustls/src/rustls.h index 41619f01..c251e590 100644 --- a/librustls/src/rustls.h +++ b/librustls/src/rustls.h @@ -1972,6 +1972,9 @@ const struct rustls_crypto_provider *rustls_aws_lc_rs_crypto_provider(void); * * See the upstream [rustls FIPS documentation][FIPS] for more information. * + * If the `post-quantum` feature is also enabled, this function will return a provider + * that includes the FIPS approved post-quantum X25519MLKEM768 key exchange group. + * * The caller owns the returned `rustls_crypto_provider` and must free it using * `rustls_crypto_provider_free`. * @@ -1980,6 +1983,23 @@ const struct rustls_crypto_provider *rustls_aws_lc_rs_crypto_provider(void); const struct rustls_crypto_provider *rustls_default_fips_provider(void); #endif +#if defined(DEFINE_POST_QUANTUM) +/** + * Return a post-quantum enabled `rustls_crypto_provider` backed by the `aws-lc-rs` cryptography + * library. + * + * This `rustls_crypto_provider` is the same as the one returned from + * `rustls_aws_lc_rs_crypto_provider()`, but includes the post-quantum X25519MLKEM768 key exchange + * algorithm. + * + * Requires the `post-quantum` feature be enabled. + * + * The caller owns the returned `rustls_crypto_provider` and must free it using + * `rustls_crypto_provider_free`. + */ +const struct rustls_crypto_provider *rustls_post_quantum_provider(void); +#endif + /** * Retrieve a pointer to the process default `rustls_crypto_provider`. *