Compare commits

...

166 Commits

Author SHA1 Message Date
minish eec42222af
Add barbosshack prefix patch 2025-11-04 16:55:11 -05:00
0x676e67 be22a3940d Merge remote-tracking branch 'upstream/master' 2025-10-29 19:49:47 +08:00
Bas Westerbaan 47c33f6428 pq patch: also enable P256Kyber768Draft00 by default 2025-10-28 10:54:23 +00:00
Bas Westerbaan 410a96752b pq patch: enable PQ by default like upstream
The big diff is misleading. Applying each patch to the base 478b28ab12f
and comparing them, we see:

git range-diff 478b28ab12f2001a03261624261fd041f5439706..adcd4022f75953605a9bf9f6a4a45c0b4fd8ed94 478b28ab12f2001a03261624261fd041f5439706..6f1b1e1f451e61cd2bda0922eecaa8387397ac5a
1:  adcd4022f ! 1:  6f1b1e1f4 Add additional post-quantum key agreements
    @@ Commit message

         This patch adds:

    -    1. Support for MLKEM768X25519 under the codepoint 0x11ec. The version
    -       of BoringSSL we patch against did not support it yet.
    +    1. Support for X25519MLKEM768 under the codepoint 0x11ec. The version
    +       of BoringSSL we patch against did not support it yet. Like recent
    +       upstream, enable by default.

         2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily
            need for compliance reasons.  (Note that this is not the codepoint
    @@ ssl/extensions.cc: static bool tls1_check_duplicate_extensions(const CBS *cbs) {
            return true;
          default:
            return false;
    +@@ ssl/extensions.cc: bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
    + }
    +
    + static const uint16_t kDefaultGroups[] = {
    ++    SSL_GROUP_X25519_MLKEM768,
    +     SSL_GROUP_X25519,
    +     SSL_GROUP_SECP256R1,
    +     SSL_GROUP_SECP384R1,

      ## ssl/ssl_key_share.cc ##
     @@
2025-10-28 10:54:23 +00:00
0x676e67 5ddfb2e097
chore(ssl): remove deprecated code (#98) 2025-10-21 13:15:12 +08:00
0x676e67 d7805d6053
Modify prerelease condition in CI workflow
Updated prerelease condition to exclude 'alpha' branch.
2025-10-21 12:57:44 +08:00
0x676e67 231010c0cb Merge remote-tracking branch 'upstream/master' 2025-10-21 12:35:13 +08:00
Jaap Aarts e23d2d16d4 Update main.rs 2025-10-16 13:31:09 +01:00
Christopher Patton 5cd912df1d Remove "pq-experimental", apply PQ patch by default
Users can override the new default behavior in the usual way. The
expectation is that the build of BoringSSL they provide the feature set
implemented by the patch.
2025-10-15 10:36:27 +01:00
Kornel 77f612c16c Simplify Error::reason() 2025-10-15 10:35:38 +01:00
0x676e67 ee94551993
Fix duplicate entry for RPK support in README
Removed duplicate mention of RPK not being supported.
2025-09-22 01:58:11 +08:00
0x676e67 bcc3ccb390
Fix formatting in FUNDING.yml for ko-fi entry 2025-09-22 01:03:48 +08:00
0x676e67 b17dd9d23c
Update FUNDING.yml for sponsorship links 2025-09-22 01:03:17 +08:00
0x676e67 cd2c47eb5b v5.0.0-alpha.10 2025-09-19 19:00:11 +08:00
0x676e67 2f94005cf0
feat: Add `set_preserve_tls13_cipher_list` method to `SslContextBuilder` (#97)
* feat: Add set_preserve_tls13_cipher_list method to `SslContextBuilder`

* Update boring/src/ssl/mod.rs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-19 18:59:00 +08:00
0x676e67 37e0cde8c2
Remove CI badge from README 2025-09-12 18:55:15 +08:00
0x676e67 4bc35b3959 v5.0.0-alpha.9 2025-09-08 17:01:26 +08:00
0x676e67 219a6bccea Merge remote-tracking branch 'upstream/master' 2025-09-08 17:00:51 +08:00
dependabot[bot] ca9cb3fc66
build(deps): update compio requirement from 0.15.0 to 0.16.0 (#96)
* build(deps): update compio requirement from 0.15.0 to 0.16.0

Updates the requirements on [compio](https://github.com/compio-rs/compio) to permit the latest version.
- [Release notes](https://github.com/compio-rs/compio/releases)
- [Commits](https://github.com/compio-rs/compio/compare/v0.15.0...v0.15.0)

---
updated-dependencies:
- dependency-name: compio
  dependency-version: 0.15.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* update

* update

* update

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 0x676e67 <gngppz@gmail.com>
2025-09-08 16:51:01 +08:00
dependabot[bot] 2c2ea08f44
build(deps): update compio-io requirement from 0.7.0 to 0.8.0 (#95) 2025-09-08 16:37:04 +08:00
0x676e67 197b9fcb5c Merge remote-tracking branch 'upstream/master' 2025-09-04 16:20:35 +08:00
0x676e67 373bbbea5b Reapply "Don't link binaries on docs.rs" (#94)
This reverts commit d574ccc370.
2025-09-04 16:20:03 +08:00
0x676e67 21652b6344
Fix grammar in README description 2025-08-31 18:07:31 +08:00
0x676e67 c3da3cddbb v5.0.0-alpha.8 2025-08-31 17:20:36 +08:00
0x676e67 d574ccc370
Revert "Don't link binaries on docs.rs" (#94)
This reverts commit 8d5fba3767.
2025-08-31 17:09:36 +08:00
0x676e67 b988878d88 v5.0.0-alpha.7 2025-08-21 08:25:20 +08:00
0x676e67 d0103d9a55
boring-sys: Implement `MLKEM1024` for TLS (#93)
* boring-sys: Implement MLKEM1024 for TLS

* clippy fix
2025-08-21 08:09:09 +08:00
dependabot[bot] 7d3833243b
build(deps): update hex-literal requirement from 0.4 to 1.0 (#89) 2025-07-29 01:10:22 +08:00
dependabot[bot] 50a73aa8c3
build(deps): update rcgen requirement from 0.13 to 0.14 (#88) 2025-07-29 00:59:21 +08:00
0x676e67 8229b96f5b
Create FUNDING.yml 2025-07-26 09:00:42 +08:00
0x676e67 d9a1d9442e
feat(boring): adapt boring2 for quinn (#87) 2025-07-25 07:28:03 +08:00
0x676e67 6bcc6af083 Merge remote-tracking branch 'upstream/master' 2025-07-22 19:29:54 +08:00
0x676e67 7194a9ea9f Update README.md 2025-07-10 09:33:39 +08:00
0x676e67 95cfdbc910 chore(compio-boring): update homepage and docs 2025-07-07 23:37:37 +08:00
0x676e67 33dc7775bd v5.0.0-alpha.4 2025-07-07 21:40:34 +08:00
0x676e67 e6e8b0af17 chore(compio-boring2): remove unused docs 2025-07-07 21:40:20 +08:00
0x676e67 109c35839c chore: fix ci and docs 2025-07-07 21:32:45 +08:00
0x676e67 d8ab50af6e docs: update README.md 2025-07-07 21:22:27 +08:00
0x676e67 39afe81356 chore: update `compio-boring` version 2025-07-07 21:13:24 +08:00
0x676e67 83e049d8d9
feat(boring): adapt `boring2` for compio async runtime (#85)
close: https://github.com/0x676e67/boring2/issues/78
2025-07-07 21:10:50 +08:00
0x676e67 736c374e3d v5.0.0-alpha.3 2025-07-06 18:01:43 +08:00
0x676e67 8ec7576cf4
feat: Add Hash impls for extension types (#84) 2025-07-06 17:59:49 +08:00
0x676e67 3010f78112 v5.0.0-alpha.2 2025-06-23 23:34:29 +08:00
zebrapurring e47d09e242
feat: add support for FreeBSD (#83)
* feat: add support for FreeBSD

---------

Co-authored-by: zebrapurring <>
Co-authored-by: 0x676e67 <gngppz@gmail.com>
2025-06-23 23:14:01 +08:00
0x676e67 f8918297cc Merge branch 'docs' 2025-06-21 19:54:27 +08:00
0x676e67 3a32ea51f7 docs(connector): update documents 2025-06-21 19:54:17 +08:00
0x676e67 58d2b58d70
docs: update prefer chacha20 option docs (#81) 2025-06-18 22:19:30 +08:00
0x676e67 590cef9b1f docs: update prefer chacha20 option docs 2025-06-18 22:16:46 +08:00
0x676e67 afde990c6b docs: update prefer chacha20 option docs 2025-06-18 22:15:44 +08:00
0x676e67 53e9475c68 v5.0.0-alpha.1 2025-06-18 12:39:57 +08:00
0x676e67 eaf49e631e
feat(boring): sync updated extension permutation patch (#80) 2025-06-18 12:36:34 +08:00
0x676e67 4ba97ba54e
chore(boring): Remove deprecated or outdated APIs (#79) 2025-06-17 23:19:52 +08:00
0x676e67 ee124d7ccc
Update README.md 2025-06-17 22:59:08 +08:00
0x676e67 f08c7cf8b7
test(boring): fix ech test (#77) 2025-06-17 22:26:23 +08:00
0x676e67 f4419dc416 revert(boring): Restore `src/x509/store.rs` to match upstream 2025-06-17 20:22:42 +08:00
0x676e67 e04066ee46 Merge remote-tracking branch 'upstream/master' 2025-06-17 20:16:49 +08:00
0x676e67 b4fb079585
Merge pull request #75 from 0x676e67/dependabot/cargo/bindgen-0.72.0 2025-06-09 23:17:47 +08:00
dependabot[bot] e362ce762e
build(deps): update bindgen requirement from 0.71.1 to 0.72.0
Updates the requirements on [bindgen](https://github.com/rust-lang/rust-bindgen) to permit the latest version.
- [Release notes](https://github.com/rust-lang/rust-bindgen/releases)
- [Changelog](https://github.com/rust-lang/rust-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/rust-bindgen/compare/v0.71.1...v0.71.1)

---
updated-dependencies:
- dependency-name: bindgen
  dependency-version: 0.71.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 09:07:04 +00:00
0x676e67 6107475db1 chore: remove dead code 2025-06-07 23:09:11 +08:00
0x676e67 80f9221bbb Merge remote-tracking branch 'upstream/master' 2025-06-07 23:08:32 +08:00
0x676e67 0f2461ad00
sync upstream (#74)
* Add fips-precompiled feature to support newer versions of FIPS (#338)

Newer versions of FIPS don't need any special casing in our bindings,
unlike the submoduled boringssl-fips. In addition, many users currently
use FIPS by precompiling BoringSSL with the proper build tools and
passing that in to the bindings.

Until we adopt the Update Stream pattern for FIPS, there are two main
use cases:

1. Passing an unmodified, precompiled FIPS validated version of
   boringssl (fips-precompiled)

2. Passing a custom source directory of boringssl meant to be linked
   with a FIPS validated bcm.o. This is mainly useful if you carry
   custom patches but still want to use a FIPS validated BoringCrypto.
   (fips-link-precompiled)

This commit introduces the `fips-precompiled` feature and removes the
`fips-no-compat` feature.

* Release 4.16.0 (#341)

* feat(x509): Implement `Clone` for `X509Store` (#339)


* boring(x509): impl Clone of X509Store

* expose SSL_set_compliance_policy

* fix clippy error

* Use ubuntu-latest for all ci jobs

ubuntu 20.04 is now deprecated:
https://github.com/actions/runner-images/issues/11101

* add SslCurve::X25519_MLKEM768 constant

* Clippy

* Fix linking SystemFunction036 from advapi32 in Rust 1.87

* rustfmt ;(

* build: Fix the build for 32-bit Linux platform

* Update Cargo.toml

* boring(ssl): use `corresponds` macro in `add_certificate_compression_algorithm`

* Add `X509_STORE_CTX_get0_cert` interface

This method reliably retrieves the certificate the `X509_STORE_CTX` is
verifying, unlike `X509_STORE_CTX_get_current_cert`, which may return
the "problematic" cert when verification fails.

* Update bindgen from 0.70.1 -> 0.71.1.

* Revert "feat(x509): Implement `Clone` for `X509Store` (#339)" (#353)

* Revert "feat(x509): Implement `Clone` for `X509Store` (#339)"

This reverts commit 49a8d0906a.

See <https://github.com/cloudflare/boring/pull/120>.

* Ensure Clone is not added to X509Store

* Add comment about why X509Store must not implement Clone

---------

Co-authored-by: Kornel <kornel@cloudflare.com>

* Release 4.17.0 (#354)

* Add set_verify_param

* clippy fix

---------

Co-authored-by: Rushil Mehra <84047965+rushilmehra@users.noreply.github.com>
Co-authored-by: Shih-Chiang Chien <shih-chiang@cloudflare.com>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
Co-authored-by: Eric Rosenberg <eric_rosenberg@apple.com>
Co-authored-by: Kornel <kornel@cloudflare.com>
Co-authored-by: James Larisch <jlarisch@cloudflare.com>
Co-authored-by: Yury Yarashevich <yura.yaroshevich@gmail.com>
Co-authored-by: Anthony Ramine <123095+nox@users.noreply.github.com>
2025-05-30 11:15:39 +08:00
0x676e67 4fd641f79d clippy fix 2025-05-30 11:00:23 +08:00
0x676e67 6169bcd543 Merge remote-tracking branch 'upstream/master' into sync 2025-05-30 10:55:52 +08:00
0x676e67 7205960bc1
boring(ssl): add ZSTD to `CertificateCompressionAlgorithm` (#71) 2025-05-19 16:46:31 +08:00
0x676e67 9da3b7b44c
boring(ssl): use `corresponds` macro in `add_certificate_compression_algorithm` (#70) 2025-05-18 19:21:38 +08:00
0x676e67 9fb6143b11
chore(boring): deprecate legacy `CertCompressionAlgorithm` API (#69)
* chore(boring): deprecate legacy `CertCompressionAlgorithm` API

* ci: fix windows build
2025-05-18 18:55:08 +08:00
0x676e67 2b497506ef
chore(boring): deprecate `set_verify_cert_store_ref` on `SslContextBuilder` (#68) 2025-05-13 18:23:22 +08:00
0x676e67 493c0cb51b v4.15.13 2025-04-27 17:14:16 +08:00
hev fbad63bb8c
feat: Add basic support for LoongArch (#67) 2025-04-27 17:11:58 +08:00
0x676e67 cb25aadd4e v4.15.12 2025-04-23 12:55:37 +08:00
0x676e67 ee6d225ea3
ci: use ubuntu-latest (#66) 2025-04-23 12:54:54 +08:00
0x676e67 6458b54e6a
docs(boring): fix doc warning (#65) 2025-04-23 12:50:43 +08:00
dependabot[bot] c72c81aedd
build(deps): update brotli requirement from 7 to 8 (#64)
---
updated-dependencies:
- dependency-name: brotli
  dependency-version: 7.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-23 12:07:36 +08:00
0x676e67 42d2398fcd
Create dependabot.yml 2025-04-05 19:43:37 +08:00
0x676e67 ba1ee0dd48 v4.15.11 2025-03-21 20:38:44 +08:00
0x676e67 f55f9e1dd2
build: update workflows action (#61) 2025-03-21 19:36:03 +08:00
0x676e67 2576807382 chore: clippy fix 2025-03-21 18:28:13 +08:00
0x676e67 9c6c805947
Update issue templates 2025-03-21 18:23:14 +08:00
0x676e67 20f203cb57
Update ci.yml 2025-03-21 18:19:39 +08:00
0x676e67 dc306ba199
Delete .github/workflows/semgrep.yml 2025-03-21 18:19:23 +08:00
0x676e67 5ddc6ddb71 Update README.md 2025-03-21 18:18:48 +08:00
0x676e67 360c3949c8 build: fix doc test 2025-03-21 17:56:57 +08:00
0x676e67 6c74708e27 build: Ignore tests that bundle with the upstream boringssl binary 2025-03-21 17:54:31 +08:00
0x676e67 d72c6a4211
boring(x509): impl `Clone` of `X509Store` (#59) 2025-03-21 17:18:21 +08:00
0x676e67 cfe524c10b build: fix `pq-experimental` feature build 2025-03-21 16:56:19 +08:00
0x676e67 80d7385f78 Merge remote-tracking branch 'upstream/master' 2025-03-20 23:25:49 +08:00
0x676e67 94cd4e1498
chore(boring): simplify extensions sort order calculation (#58) 2025-03-20 23:15:54 +08:00
0x676e67 d3911bfc86 v4.15.9 2025-03-07 11:10:14 +08:00
0x676e67 d69d6b9cb3
feat: Allow overriding AES encryption for Encrypted Client Hello (#57) 2025-03-07 11:02:38 +08:00
0x676e67 888a72ef43 v4.15.8 2025-02-24 11:38:35 +08:00
0x676e67 657dcc230e
Fix lifetimes in ssl::select_next_proto (#55)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

* chore: Fix docs on SslRef::replace_ex_data

* Detailed error codes

* Clean up boring_sys::init()

We don't need the workaround that was initially introduced for a bug in
openssl, and OPENSSL_init_ssl always calls into CRYPTO_library_init on
boringssl, so just call it explicitly.

* Expose EVP_HPKE_KEY

* Expose client/server-side ECH

Resolves https://github.com/cloudflare/boring/issues/282

* Clean up ECH tests

* Expose SSL_set_enable_ech_grease

* Use corresponds macro

* build: Fix the build for 32-bit Linux platform (#312)

build: Fix the build for 32-bit Linux platform

* Set CMAKE_BUILD_PARALLEL_LEVEL to available_parallelism

cmake-rs' jobserver doesn't work reliably, if at all. One workaround is
to set CMAKE_BUILD_PARALLEL_LEVEL to available_parallelism(). On my
machine it shaves ~35 seconds off of boring-sys builds.

* Expose SSL_CTX_set1_ech_keys from SslContextRef

We currently expose this method on `SslContextBuilder`, which is fine
for bootstrapping an `SSL_CTX`, but subsequent attempts to set ECH keys
(like during key rotation) can only happen via `SslContextRef`. Also
update the method on the builder to take an immutable reference to self
because the API is thread safe.

* Bump cmake-rs to improve Mac OS build parallelism

There's a bug on OSX that prevents the CMake jobserver from working
properly, and so CMake defaults to a single-threaded build. It's not
clear when this is actually going to get fixed, so recent versions of
cmake-rs just disable the jobserver and have CMake fall back to the
number of available cores:
https://github.com/rust-lang/cmake-rs/pull/229

This means we don't need e6833b0074

* Release 4.14.0 (#317)

* Actually expose SslEchKeys

* Address clippy lints

* Revert "Refactor!: Introduce a Cargo feature for optional Hyper 0 support"

This reverts commit 49d5a61163.

* Revert "Refactor!: Remove strict `TokioIo` response requirement from `hyper_boring::v1::HttpsConnector`"

This reverts commit e518c2444a.

* Introduce a builder pattern for SslEchKeys + make set_ech_keys take a reference (#320)

Previously, set_ech_keys would consume the SslEchKeys struct to enforce
the requirement that the struct is immutable after initializing it on a
SSL_CTX. The problem with this is that it requires applications to
needlessly reallocate the SslEchKeys struct if they want to initialize
keys on multiple SSL_CTXs, which is a pretty common pattern. To work
around this, we introduce a builder (SslEchKeysBuilder) that requires
mutable access to add keys to the underlying struct. set_ech_keys takes
in a reference to SslEchKeys, which can only be made via consuming the
builder.

* Revert cmake bump (for now) as it is overly restrictive (#321)

Some users of boring have issues with newer versions of cmake. Because
we have an alternative solution, we can hold off on the bump for now.

* Fix lifetimes in ssl::select_next_proto

See https://github.com/sfackler/rust-openssl/pull/2360 and
https://nvd.nist.gov/vuln/detail/CVE-2025-24898. From the rust-openssl
PR:

`SSL_select_next_proto` can return a pointer into either the client or
server buffers, but the type signature of the function previously only
bound the output buffer to the client buffer. This can result in a UAF
in situations where the server slice does not point to a long-lived
allocation.

Thanks to Matt Mastracci for reporting this issue.

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
Co-authored-by: Kornel <kornel@cloudflare.com>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
Co-authored-by: Rushil Mehra <84047965+rushilmehra@users.noreply.github.com>
2025-02-24 11:37:58 +08:00
0x676e67 1d18c908f0 v4.15.7 2025-02-23 19:44:36 +08:00
0x676e67 7ee98f663e
sync upstream (#54)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

* chore: Fix docs on SslRef::replace_ex_data

* Detailed error codes

* Clean up boring_sys::init()

We don't need the workaround that was initially introduced for a bug in
openssl, and OPENSSL_init_ssl always calls into CRYPTO_library_init on
boringssl, so just call it explicitly.

* Expose EVP_HPKE_KEY

* Expose client/server-side ECH

Resolves https://github.com/cloudflare/boring/issues/282

* Clean up ECH tests

* Expose SSL_set_enable_ech_grease

* Use corresponds macro

* build: Fix the build for 32-bit Linux platform (#312)

build: Fix the build for 32-bit Linux platform

* Set CMAKE_BUILD_PARALLEL_LEVEL to available_parallelism

cmake-rs' jobserver doesn't work reliably, if at all. One workaround is
to set CMAKE_BUILD_PARALLEL_LEVEL to available_parallelism(). On my
machine it shaves ~35 seconds off of boring-sys builds.

* Expose SSL_CTX_set1_ech_keys from SslContextRef

We currently expose this method on `SslContextBuilder`, which is fine
for bootstrapping an `SSL_CTX`, but subsequent attempts to set ECH keys
(like during key rotation) can only happen via `SslContextRef`. Also
update the method on the builder to take an immutable reference to self
because the API is thread safe.

* Bump cmake-rs to improve Mac OS build parallelism

There's a bug on OSX that prevents the CMake jobserver from working
properly, and so CMake defaults to a single-threaded build. It's not
clear when this is actually going to get fixed, so recent versions of
cmake-rs just disable the jobserver and have CMake fall back to the
number of available cores:
https://github.com/rust-lang/cmake-rs/pull/229

This means we don't need e6833b0074

* Release 4.14.0 (#317)

* Actually expose SslEchKeys

* Address clippy lints

* Revert "Refactor!: Introduce a Cargo feature for optional Hyper 0 support"

This reverts commit 49d5a61163.

* Revert "Refactor!: Remove strict `TokioIo` response requirement from `hyper_boring::v1::HttpsConnector`"

This reverts commit e518c2444a.

* Introduce a builder pattern for SslEchKeys + make set_ech_keys take a reference (#320)

Previously, set_ech_keys would consume the SslEchKeys struct to enforce
the requirement that the struct is immutable after initializing it on a
SSL_CTX. The problem with this is that it requires applications to
needlessly reallocate the SslEchKeys struct if they want to initialize
keys on multiple SSL_CTXs, which is a pretty common pattern. To work
around this, we introduce a builder (SslEchKeysBuilder) that requires
mutable access to add keys to the underlying struct. set_ech_keys takes
in a reference to SslEchKeys, which can only be made via consuming the
builder.

* Revert cmake bump (for now) as it is overly restrictive (#321)

Some users of boring have issues with newer versions of cmake. Because
we have an alternative solution, we can hold off on the bump for now.

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
Co-authored-by: Kornel <kornel@cloudflare.com>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
Co-authored-by: Rushil Mehra <84047965+rushilmehra@users.noreply.github.com>
2025-02-23 19:42:23 +08:00
0x676e67 372501ffc2
fix(patch): Fix alps use new codepoint negotiation (#53) 2025-02-23 19:34:37 +08:00
0x676e67 50437d514c v4.15.6 2025-02-16 21:07:21 +08:00
0x676e67 46aeed7868
boring(patch): Fix `alps_use_new_codepoint` not initialized, may lead to undefined behavior, then get any value (#52) 2025-02-16 21:04:33 +08:00
0x676e67 e7c534cda3 v4.15.5 2025-02-14 02:11:25 +08:00
0x676e67 8adb21b3b4
build: Fix 32-bit platform build (#51) 2025-02-14 02:10:21 +08:00
0x676e67 0d30ebfd58
Use corresponds macro (#50)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

* chore: Fix docs on SslRef::replace_ex_data

* Detailed error codes

* Clean up boring_sys::init()

We don't need the workaround that was initially introduced for a bug in
openssl, and OPENSSL_init_ssl always calls into CRYPTO_library_init on
boringssl, so just call it explicitly.

* Expose EVP_HPKE_KEY

* Expose client/server-side ECH

Resolves https://github.com/cloudflare/boring/issues/282

* Clean up ECH tests

* Expose SSL_set_enable_ech_grease

* update

* Use corresponds macro

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
Co-authored-by: Kornel <kornel@cloudflare.com>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
2025-02-14 02:07:41 +08:00
0x676e67 e82939f52e
Expose SSL_set_enable_ech_grease (#49)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

* chore: Fix docs on SslRef::replace_ex_data

* Detailed error codes

* Clean up boring_sys::init()

We don't need the workaround that was initially introduced for a bug in
openssl, and OPENSSL_init_ssl always calls into CRYPTO_library_init on
boringssl, so just call it explicitly.

* Expose EVP_HPKE_KEY

* Expose client/server-side ECH

Resolves https://github.com/cloudflare/boring/issues/282

* Clean up ECH tests

* Expose SSL_set_enable_ech_grease

* update

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
Co-authored-by: Kornel <kornel@cloudflare.com>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
2025-02-13 19:32:46 +08:00
0x676e67 d1f73a9aae
Expose client/server-side ECH (#48)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

* chore: Fix docs on SslRef::replace_ex_data

* Detailed error codes

* Clean up boring_sys::init()

We don't need the workaround that was initially introduced for a bug in
openssl, and OPENSSL_init_ssl always calls into CRYPTO_library_init on
boringssl, so just call it explicitly.

* Expose EVP_HPKE_KEY

* Expose client/server-side ECH

Resolves https://github.com/cloudflare/boring/issues/282

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
Co-authored-by: Kornel <kornel@cloudflare.com>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
2025-02-13 04:46:36 +08:00
0x676e67 dded5d4e8c
Sync `Detailed error codes` and `Clean up boring_sys::init()` (#47)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

* chore: Fix docs on SslRef::replace_ex_data

* Detailed error codes

* Clean up boring_sys::init()

We don't need the workaround that was initially introduced for a bug in
openssl, and OPENSSL_init_ssl always calls into CRYPTO_library_init on
boringssl, so just call it explicitly.

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
Co-authored-by: Kornel <kornel@cloudflare.com>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
2025-02-12 22:49:09 +08:00
0x676e67 2e17f2bc16 v4.15.3 2025-02-11 17:24:26 +08:00
0x676e67 3c63f0b24e
fix: Fix `key share` patch (#46) 2025-02-11 17:18:10 +08:00
0x676e67 4edbff8cad v4.15.2 2025-02-07 13:45:58 +08:00
0x676e67 b48c194d16
feat(boring): Add set_verify_cert_store_ref method to SslContextBuilder (#45) 2025-02-07 13:45:24 +08:00
0x676e67 72424152a9
feat(boring): Add add_cert_compression_alg support (#44) 2025-02-07 13:37:09 +08:00
0x676e67 dbb58741f6 v4.15.1 2025-02-07 12:28:22 +08:00
0x676e67 79949c2a8e chore: Removal unused deps 2025-02-07 12:27:42 +08:00
0x676e67 32492cd4f0
feat: Add `set_options` binding function to ConnectConfiguration (#43) 2025-02-07 12:19:41 +08:00
0x676e67 c6e390a8b8
feat: Add new binding functions to `ConnectConfiguration` (#42) 2025-02-07 12:08:49 +08:00
0x676e67 025f25c5bb chore: update documentation link 2025-02-07 11:52:24 +08:00
0x676e67 ed56d2c06a feat: Removal of `rpk` support 2025-02-07 11:50:05 +08:00
0x676e67 5d33987600
feat: Removal of `rpk` support (#41) 2025-02-07 11:36:39 +08:00
0x676e67 18b295a155 chore: remove unused `hyper-boring` crate 2025-02-07 11:24:58 +08:00
0x676e67 c75345aba4
chore: Fix docs on SslRef::replace_ex_data (#40)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

* chore: Fix docs on SslRef::replace_ex_data

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
2025-02-07 11:01:38 +08:00
0x676e67 7410a00788 v4.15.0 2025-02-02 19:32:21 +08:00
0x676e67 30c6bedc4d
feat: Add ALPS use new endpoint (#39) 2025-02-02 19:29:29 +08:00
0x676e67 af53f4b86e v4.14.2 2025-01-23 10:09:27 +08:00
0x676e67 13eb268616
feat: replace once_cell with LazyLock (#38)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* replace once_cell with LazyLock

We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.

* fix manual_c_str_literals clippy warning

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
2025-01-23 10:08:15 +08:00
0x676e67 1a0f1cd24e v4.14.1 2025-01-22 21:46:54 +08:00
0x676e67 0e555ba26b
feat: deprecated `set_key_shares_length_limit` (#37) 2025-01-22 13:19:08 +08:00
0x676e67 6bd4118645 Update README.md 2025-01-22 13:14:37 +08:00
0x676e67 bed5243775
feat: Add `kDHE` && `ffdhe2048`/`ffdhe3072` curves working implement (#36)
* RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client

X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

* feat: Add kDHE && ffdhe2048/ffdhe3072 curves working implement

* Update

---------

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
2025-01-22 13:08:20 +08:00
0x676e67 5da88184f1
RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client (#35)
X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

We've had support for X25519MLKEM768 in this crate for a while, but
didn't enable by default. We're now enabling serverside support by
default. We also let clients advertise support when set
to kx-client-pq-supported.

We don't enable support by default yet for clients set to
kx-client-pq-preferred, as that would cause an extra round-trip due to
HelloRetryRequest if the server doesn't support X25519MLKEM768 yet.

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.

Co-authored-by: Bas Westerbaan <bas@cloudflare.com>
2025-01-14 22:17:57 +08:00
0x676e67 038c5b2105 v4.13.8 2025-01-13 13:05:29 +08:00
0x676e67 a9feffea83
docs: Fix docs build (#34) 2025-01-13 13:05:00 +08:00
0x676e67 b875e49daf
boring-sys: Optional SSL_OP_NO_RENEGOTIATION to disable client renegotiation extension (#33) 2025-01-10 16:16:41 +08:00
0x676e67 74c03ad71f Merge remote-tracking branch 'upstream/master' 2025-01-07 15:10:56 +08:00
0x676e67 787606f830 v4.13.6 2025-01-05 11:56:10 +08:00
0x676e67 0e6b794ead
feat(boring): Add optional disable `PSK_DHE_KE` (#32) 2025-01-05 11:54:25 +08:00
0x676e67 543492f35d v4.13.5 2024-12-23 08:15:44 +08:00
0x676e67 5a03ebfbb6
chore: Remove extended alignment bounds validation unless the user is knowledgeable (#31) 2024-12-23 08:14:25 +08:00
0x676e67 6e966c9dac v4.13.4 2024-12-20 14:39:40 +08:00
0x676e67 d6e1792a7f
chore: Disable extended permutation deduplication verification (#30)
* chore: Disable extended permutation deduplication verification

* Update docs
2024-12-20 14:38:42 +08:00
0x676e67 f8e01e3d0c
feat: Expose extended sorting from indices (#29) 2024-12-20 14:18:16 +08:00
0x676e67 f9a7334f54 v4.13.3 2024-12-19 23:23:11 +08:00
0x676e67 66cc25a270
feat: Add setup extension permutation (#28) 2024-12-19 23:21:31 +08:00
0x676e67 292b2a1513
refactor: refactor `key_shares` length limit (#27) 2024-12-19 18:17:40 +08:00
0x676e67 928968947a
boring-sys: Add TLS extension zstd cert compression (#24) 2024-12-18 19:34:06 +08:00
0x676e67 b4c46bb8a1
patch: Add enable three `key_shares` limit (#23) 2024-12-18 18:58:41 +08:00
0x676e67 abd65310ba
patch: Add option enable delegated_credentials (#22) 2024-12-18 16:32:38 +08:00
0x676e67 39914a641c
patch: Add option enable record_size_limit (#21) 2024-12-18 14:57:23 +08:00
0x676e67 6ef0ca379e
boring: Add SslCurve `FFDHE2048`/`FFDHE3072` NID support (#20) 2024-12-18 13:14:02 +08:00
0x676e67 30cadfb1eb
boring: Add SslCurve `FFDHE2048`/`FFDHE3072` support (#19) 2024-12-17 23:22:56 +08:00
0x676e67 0ee30f7487
boring: update old ciphers patch (#18) 2024-12-17 20:12:33 +08:00
0x676e67 b9e1dbf825 Merge remote-tracking branch 'upstream/master' 2024-12-10 12:00:48 +08:00
0x676e67 8467142bf2 v4.13.2 2024-12-06 23:02:48 +08:00
0x676e67 cbe77fd2ff
boring: Add SslConnector no default cert verify paths builder (#16) 2024-12-06 23:01:36 +08:00
0x676e67 f1c29d439c sync upstream 2024-12-06 22:59:10 +08:00
0x676e67 8b986e3521 v4.12.1 2024-11-27 19:48:56 +08:00
0x676e67 63d01c439a
Fix Windows build (#13) 2024-11-27 19:47:44 +08:00
0x676e67 5422a6bdce Merge remote-tracking branch 'upstream/master' 2024-11-21 10:02:26 +08:00
0x676e67 1893144398
Update README.md 2024-11-15 10:55:58 +08:00
0x676e67 4e819482a1
Update README.md 2024-11-15 10:53:46 +08:00
0x676e67 5ddb1ee185 rename rboring to boring2 2024-11-15 10:44:03 +08:00
0x676e67 837ec6227b rename tokio-rboring to tokio-boring2 2024-11-15 10:42:55 +08:00
0x676e67 6b5844021d rename rboring-sys to boring-sys2 2024-11-15 10:42:09 +08:00
0x676e67 c77b15182d v4.11.1 2024-11-03 21:23:39 +08:00
0x676e67 a712b93e44 Fix X25519_MLKEM768 mapper 2024-11-03 21:23:26 +08:00
0x676e67 6768d3c437
sync upstream (#12)
* Release 4.10.3 (#280)

* Create semgrep.yml

Creating Semgrep.yml file - Semgrep is a tool that will be used to scan Cloudflare's public repos for Supply chain, code and secrets. This work is part of Application & Product Security team's initiative to onboard Semgrep onto all of Cloudflare's public repos.

In case of any questions, please reach out to "Hrushikesh Deshpande" on cf internal chat.

* Add "fips-compat" feature (#286)

This adds a feature to build against a BoringSSL version compatible with
the current boringssl-fips, but _without_ actually enabling the `fips`
feature.

This can be useful to use with `fips-link-precompiled` while using a
custom BoringSSL version based on the older FIPS branch.

* boring-sys: include HPKE header file for bindgen

BoringSSL doesn't expose these APIs for FIPs builds, so we gate them
here as well

* Release 4.11.0

* Add `set_cert_verify_callback` (`SSL_CTX_set_cert_verify`)

Add a wrapper for `SSL_CTX_set_cert_verify`, which allows consumers to
override the default certificate verification behavior.

The binding resembles `SSL_CTX_set_verify`'s.

See
https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/
for more details.

* Skip bindgen 0.70's layout tests before Rust 1.77

* (ci): brew link x86 toolchain for macos13 runner

It seems we need to manually symlink the x86_64-unknown-linux-gnu
toolchain for the macos13 runner. Also, we don't need to overwrite the
python version anymore

Fixes https://github.com/cloudflare/boring/issues/285

* feat(boring): Add SSL_CURVE_X25519_MLKEM768 curve binding

---------

Co-authored-by: Rushil Mehra <84047965+rushilmehra@users.noreply.github.com>
Co-authored-by: Hrushikesh Deshpande <161167942+hrushikeshdeshpande@users.noreply.github.com>
Co-authored-by: Alessandro Ghedini <alessandro@cloudflare.com>
Co-authored-by: Evan Rittenhouse <erittenhouse@cloudflare.com>
Co-authored-by: James Larisch <jlarisch@cloudflare.com>
Co-authored-by: Jordan Rose <jrose@signal.org>
Co-authored-by: Rushil Mehra <rmehra@cloudflare.com>
2024-11-03 21:13:37 +08:00
0x676e67 ab7848d878
feat(boring): Add SSL_CURVE_X25519_MLKEM768 curve binding (#11) 2024-11-03 21:12:28 +08:00
0x676e67 3342c9dffe Update docs link 2024-09-22 08:00:26 +08:00
0x676e67 ef88a8572b v4.10.2 2024-09-22 07:57:54 +08:00
0x676e67 b0fe60dcf6 Merge remote-tracking branch 'upstream/master' 2024-09-22 07:57:47 +08:00
0x676e67 9523ac82c9
Merge patch (#8) 2024-08-12 09:56:42 +08:00
98 changed files with 14297 additions and 3658 deletions

15
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi:
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: ['https://github.com/0x676e67/0x676e67/blob/main/SPONSOR.md']

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "cargo" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@ -1,17 +1,24 @@
name: CI
on:
pull_request:
branches:
- master
push:
branches:
- master
tags: ["v*"]
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
- '.github/**'
- 'README.md'
workflow_dispatch:
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
permissions:
contents: write
packages: write
jobs:
rustfmt:
name: rustfmt
@ -60,7 +67,7 @@ jobs:
- name: Run clippy
run: cargo clippy --all --all-targets
- name: Check docs
run: cargo doc --no-deps -p boring -p boring-sys --features rpk,pq-experimental,underscore-wildcards
run: cargo doc --no-deps -p boring2 -p boring-sys2 --features underscore-wildcards
env:
DOCS_RS: 1
test:
@ -99,21 +106,25 @@ jobs:
rust: stable
os: ubuntu-latest
check_only: true
extra_test_args: --workspace --exclude quinn-boring2
- thing: arm64-android
target: aarch64-linux-android
rust: stable
os: ubuntu-latest
check_only: true
extra_test_args: --workspace --exclude quinn-boring2
- thing: i686-android
target: i686-linux-android
rust: stable
os: ubuntu-latest
check_only: true
extra_test_args: --workspace --exclude quinn-boring2
- thing: x86_64-android
target: x86_64-linux-android
rust: stable
os: ubuntu-latest
check_only: true
extra_test_args: --workspace --exclude quinn-boring2
- thing: aarch64-ios
target: aarch64-apple-ios
os: macos-latest
@ -139,6 +150,7 @@ jobs:
rust: stable
os: ubuntu-latest
apt_packages: gcc-multilib g++-multilib
extra_test_args: --workspace --exclude compio-boring2
- thing: arm-linux
target: arm-unknown-linux-gnueabi
rust: stable
@ -149,6 +161,7 @@ jobs:
CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc
CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++
extra_test_args: --workspace --exclude compio-boring2
- thing: aarch64-linux
target: aarch64-unknown-linux-gnu
rust: stable
@ -180,27 +193,27 @@ jobs:
CPLUS_INCLUDE_PATH: "C:\\msys64\\usr\\include"
LIBRARY_PATH: "C:\\msys64\\usr\\lib"
# CI's Windows doesn't have required root certs
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
extra_test_args: --workspace --exclude tokio-boring2 --exclude compio-boring2
- thing: i686-msvc
target: i686-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
# CI's Windows doesn't have required root certs
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
extra_test_args: --workspace --exclude tokio-boring2 --exclude compio-boring2
- thing: x86_64-msvc
target: x86_64-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
# CI's Windows doesn't have required root certs
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
extra_test_args: --workspace --exclude tokio-boring2 --exclude compio-boring2
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
shell: bash
# - name: Install Rust (rustup)
# run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
# shell: bash
- run: rustup target add ${{ matrix.target }}
- name: Install golang
uses: actions/setup-go@v5
@ -236,47 +249,19 @@ jobs:
run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }}
shell: bash
env: ${{ matrix.custom_env }}
- name: Test boring-sys cargo publish
- name: Test boring-sys2 cargo publish
# Running `cargo publish --dry-run` tests two things:
#
# 1. That `boring-sys` can build BoringSSL with just the files included
# 1. That `boring-sys2` can build BoringSSL with just the files included
# in the crates.io package (as determined by the `include` field in
# the `Cargo.toml`).
# 2. That the final `boring-sys` package size, including the BoringSSL
# 2. That the final `boring-sys2` package size, including the BoringSSL
# submodules, is not too large to be published to `crates.io`.
#
# Both of these may no longer be the case after updating the BoringSSL
# submodules to a new revision, so it's important to test this on CI.
run: cargo publish --dry-run -p boring-sys
run: cargo publish --dry-run -p boring-sys2
test-fips:
name: Test FIPS integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- name: Install golang
uses: actions/setup-go@v5
with:
go-version: '>=1.22.0'
- name: Run tests
run: cargo test --features fips
- name: Test boring-sys cargo publish (FIPS)
# Running `cargo publish --dry-run` tests two things:
#
# 1. That `boring-sys` can build BoringSSL with just the files included
# in the crates.io package (as determined by the `include` field in
# the `Cargo.toml`).
# 2. That the final `boring-sys` package size, including the BoringSSL
# submodules, is not too large to be published to `crates.io`.
#
# Both of these may no longer be the case after updating the BoringSSL
# submodules to a new revision, so it's important to test this on CI.
run: cargo publish --dry-run -p boring-sys --features fips
cross-build:
name: Cross build from macOS to Linux
@ -306,45 +291,6 @@ jobs:
- name: Build for ${{ matrix.target }}
run: cargo build --target ${{ matrix.target }} --all-targets
cross-build-fips:
name: Cross build from macOS to Linux (FIPS)
runs-on: macos-13 # Need an Intel (x86_64) runner for Clang 12.0.0
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }}
shell: bash
- name: Install golang
uses: actions/setup-go@v5
with:
go-version: '>=1.22.0'
- name: Install ${{ matrix.target }} toolchain
run: brew tap messense/macos-cross-toolchains && brew install ${{ matrix.target }} && brew link x86_64-unknown-linux-gnu
- name: Install Clang-12
uses: KyleMayes/install-llvm-action@v1
with:
version: "12.0.0"
directory: ${{ runner.temp }}/llvm
- name: Add clang++-12 link
working-directory: ${{ runner.temp }}/llvm/bin
run: ln -s clang++ clang++-12
- name: Set BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN
run: echo "BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN=$(brew --prefix ${{ matrix.target }})/toolchain" >> $GITHUB_ENV
shell: bash
- name: Set BORING_BSSL_FIPS_SYSROOT
run: echo "BORING_BSSL_FIPS_SYSROOT=$BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN/${{ matrix.target }}/sysroot" >> $GITHUB_ENV
shell: bash
- name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER
run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV
- name: Build for ${{ matrix.target }}
run: cargo build --target ${{ matrix.target }} --all-targets --features fips
test-features:
name: Test features
runs-on: ubuntu-latest
@ -354,18 +300,43 @@ jobs:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- run: cargo test --features rpk
name: Run `rpk` tests
- run: cargo test --features pq-experimental
name: Run `pq-experimental` tests
- run: cargo test --features underscore-wildcards
name: Run `underscore-wildcards` tests
- run: cargo test --features pq-experimental,rpk
name: Run `pq-experimental,rpk` tests
- run: cargo test --features pq-experimental,underscore-wildcards
name: Run `pq-experimental,underscore-wildcards` tests
- run: cargo test --features rpk,underscore-wildcards
name: Run `rpk,underscore-wildcards` tests
- run: cargo test --features pq-experimental,rpk,underscore-wildcards
name: Run `pq-experimental,rpk,underscore-wildcards` tests
- name: Run `underscore-wildcards` tests
run: cargo test --features underscore-wildcards
crates:
name: crates
needs: [rustfmt, clippy, test, cross-build, test-features]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Prebuild boring-sys2
run: cargo build -p boring-sys2
- name: publish crates
run: |
cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }}
echo "=== Publishing boring-sys2... ==="
(cd boring-sys && cargo publish)
sleep 10
echo "=== Publishing boring2... ==="
(cd boring && cargo publish)
sleep 10
echo "=== Publishing tokio-boring2... ==="
(cd tokio-boring && cargo publish)
sleep 10
echo "=== Publishing compio-boring2... ==="
(cd compio-boring && cargo publish)
sleep 10
echo "=== Publishing quinn-boring2... ==="
(cd quinn-boring && cargo publish)
- name: Upload binaries to GitHub Release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
generate_release_notes: true

View File

@ -1,23 +0,0 @@
on:
pull_request: {}
workflow_dispatch: {}
push:
branches:
- master
schedule:
- cron: "0 0 * * *"
name: Semgrep config
jobs:
semgrep:
name: semgrep/ci
runs-on: ubuntu-latest
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
SEMGREP_URL: https://cloudflare.semgrep.dev
SEMGREP_APP_URL: https://cloudflare.semgrep.dev
SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- run: semgrep ci

View File

@ -3,13 +3,14 @@ members = [
"boring",
"boring-sys",
"tokio-boring",
"hyper-boring"
"compio-boring",
"quinn-boring",
]
resolver = "2"
[workspace.package]
version = "4.19.0"
repository = "https://github.com/cloudflare/boring"
version = "5.0.0-alpha.10"
repository = "https://github.com/0x676e67/boring2"
edition = "2021"
[workspace.metadata.release]
@ -19,9 +20,10 @@ tag-prefix = ""
publish = false
[workspace.dependencies]
boring-sys = { version = "4.19.0", path = "./boring-sys" }
boring = { version = "4.19.0", path = "./boring" }
tokio-boring = { version = "4.19.0", path = "./tokio-boring" }
boring-sys = { package = "boring-sys2", version = "5.0.0-alpha.10", path = "./boring-sys" }
boring = { package = "boring2", version = "5.0.0-alpha.10", path = "./boring" }
tokio-boring = { package = "tokio-boring2", version = "5.0.0-alpha.10", path = "./tokio-boring" }
compio-boring = { package = "compio-boring2", version = "5.0.0-alpha.10", path = "./compio-boring" }
bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] }
bitflags = "2.9"
@ -38,12 +40,8 @@ futures = "0.3"
tokio = "1"
anyhow = "1"
antidote = "1.0.0"
http = "1"
http-body-util = "0.1.2"
hyper = "1"
hyper-util = "0.1.6"
linked_hash_set = "0.1"
openssl-macros = "0.1.1"
tower = "0.4"
tower-layer = "0.3"
tower-service = "0.3"
autocfg = "1.3.0"
compio = { version = "0.16.0" }
compio-io = { version = "0.8.0" }

View File

@ -1,15 +1,23 @@
# boring
# boring2
[![crates.io](https://img.shields.io/crates/v/boring.svg)](https://crates.io/crates/boring)
[![crates.io](https://img.shields.io/crates/v/boring2.svg)](https://crates.io/crates/boring2)
BoringSSL bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio)
and [hyper](https://github.com/hyperium/hyper) built on top of it.
BoringSSL bindings for the Rust programming language and HTTP client for [wreq](https://github.com/0x676e67/wreq) built on top of it.
## Non-goals
This package implements only the TLS extensions specification and supports the original [boring](https://github.com/cloudflare/boring) library with the following features:
- RPK is not supported
- Required TLS extensions for Safari and Firefox
- kDHE, ffdhe2048, and ffdhe3072 implementations
- Support for LoongArch P64 and P32 architectures
## Documentation
- Boring API: <https://docs.rs/boring>
- tokio TLS adapters: <https://docs.rs/tokio-boring>
- hyper HTTPS connector: <https://docs.rs/hyper-boring>
- FFI bindings: <https://docs.rs/boring-sys>
- Boring API: <https://docs.rs/boring2>
- tokio TLS adapters: <https://docs.rs/tokio-boring2>
- compio TLS adapters: <https://docs.rs/compio-boring2>
- FFI bindings: <https://docs.rs/boring-sys2>
## Contribution
@ -20,4 +28,4 @@ Version 2.0 and the MIT license without any additional terms or conditions.
## Accolades
The project is based on a fork of [rust-openssl](https://github.com/sfackler/rust-openssl).
The project is based on a fork of [boring](https://github.com/cloudflare/boring).

View File

@ -1,5 +1,5 @@
[package]
name = "boring-sys"
name = "boring-sys2"
version = { workspace = true }
authors = ["Alex Crichton <alex@alexcrichton.com>",
"Steven Fackler <sfackler@gmail.com>",
@ -7,7 +7,7 @@ authors = ["Alex Crichton <alex@alexcrichton.com>",
license = "MIT"
description = "FFI bindings to BoringSSL"
repository = { workspace = true }
documentation = "https://docs.rs/boring-sys"
documentation = "https://docs.rs/boring-sys2"
links = "boringssl"
build = "build/main.rs"
readme = "README.md"
@ -21,14 +21,10 @@ include = [
"/cmake/*.cmake",
"/deps/boringssl/**/*.[chS]",
"/deps/boringssl/**/*.asm",
"/deps/boringssl/**/*.pl",
"/deps/boringssl/**/*.go",
"/deps/boringssl/**/*.cmake",
"/deps/boringssl/**/go.mod",
"/deps/boringssl/**/go.sum",
"/deps/boringssl/crypto/obj/obj_mac.num",
"/deps/boringssl/crypto/obj/objects.txt",
"/deps/boringssl/crypto/err/*.errordata",
"/deps/boringssl/sources.json",
"/deps/boringssl/src/crypto/obj/obj_mac.num",
"/deps/boringssl/src/crypto/obj/objects.txt",
"/deps/boringssl/src/util/32-bit-toolchain.cmake",
"/deps/boringssl/**/*.bzl",
"/deps/boringssl/**/*.cc",
"/deps/boringssl/**/CMakeLists.txt",
@ -41,7 +37,7 @@ include = [
]
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental", "underscore-wildcards"]
features = ["underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
@ -53,21 +49,18 @@ rustdoc-args = ["--cfg", "docsrs"]
# for instructions and more details on the boringssl FIPS flag.
fips = []
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
rpk = []
# Applies a patch (`patches/boring-pq.patch`) to the boringSSL source code that
# enables support for PQ key exchange. This feature is necessary in order to
# compile the bindings for the default branch of boringSSL (`deps/boringssl`).
# Alternatively, a version of boringSSL that implements the same feature set
# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`.
pq-experimental = []
# Applies a patch (`patches/underscore-wildcards.patch`) to enable
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as
# those for `pq-experimental` feature apply.
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This feature is necessary in
# order to compile the bindings for the default branch of boringSSL
# (`deps/boringssl`). Alternatively, a version of boringSSL that implements the
# same feature set can be provided by setting
# `BORING_BSSL{,_FIPS}_SOURCE_PATH`.
underscore-wildcards = []
# Add a prefix to all symbols in libcrypto and libssl to prevent conflicts
# with other OpenSSL or BoringSSL versions that might be linked in the same process.
prefix-symbols = []
[build-dependencies]
bindgen = { workspace = true }
cmake = { workspace = true }

View File

@ -16,9 +16,9 @@ pub(crate) struct Config {
pub(crate) struct Features {
pub(crate) fips: bool,
pub(crate) pq_experimental: bool,
pub(crate) rpk: bool,
pub(crate) underscore_wildcards: bool,
pub(crate) prefix_symbols: bool,
}
pub(crate) struct Env {
@ -89,9 +89,7 @@ impl Config {
);
}
let features_with_patches_enabled = self.features.rpk
|| self.features.pq_experimental
|| self.features.underscore_wildcards;
let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards;
let patches_required = features_with_patches_enabled && !self.env.assume_patched;
@ -106,15 +104,15 @@ impl Config {
impl Features {
fn from_env() -> Self {
let fips = env::var_os("CARGO_FEATURE_FIPS").is_some();
let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some();
let rpk = env::var_os("CARGO_FEATURE_RPK").is_some();
let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some();
let prefix_symbols = env::var_os("CARGO_FEATURE_PREFIX_SYMBOLS").is_some();
Self {
fips,
pq_experimental,
rpk,
underscore_wildcards,
prefix_symbols,
}
}

View File

@ -9,8 +9,10 @@ use std::process::{Command, Output};
use std::sync::OnceLock;
use crate::config::Config;
use crate::prefix::{prefix_symbols, PrefixCallback};
mod config;
mod prefix;
fn should_use_cmake_cross_compilation(config: &Config) -> bool {
if config.host == config.target {
@ -303,7 +305,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
config
.manifest_dir
.join(src_path)
.join("util/32-bit-toolchain.cmake")
.join("src/util/32-bit-toolchain.cmake")
.as_os_str(),
);
}
@ -434,14 +436,12 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> {
);
return Ok(());
} else if config.env.source_path.is_some()
&& (config.features.rpk
|| config.features.pq_experimental
|| config.features.underscore_wildcards)
&& (config.features.rpk || config.features.underscore_wildcards)
{
panic!(
"BORING_BSSL_ASSUME_PATCHED must be set when setting
BORING_BSSL_SOURCE_PATH and using any of the following
features: rpk, pq-experimental, underscore-wildcards"
features: rpk, underscore-wildcards"
);
}
@ -456,15 +456,14 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> {
run_command(Command::new("git").arg("init").current_dir(src_path))?;
}
if config.features.pq_experimental {
println!("cargo:warning=applying experimental post quantum crypto patch to boringssl");
apply_patch(config, "boring-pq.patch")?;
}
println!("cargo:warning=applying 44b3df6f03d85c901767250329c571db405122d5 patch to boringssl");
apply_patch(
config,
"boringssl-44b3df6f03d85c901767250329c571db405122d5.patch",
)?;
if config.features.rpk {
println!("cargo:warning=applying RPK patch to boringssl");
apply_patch(config, "rpk.patch")?;
}
println!("cargo:warning=applying loongarch patch to boringssl");
apply_patch(config, "boringssl-loongarch.patch")?;
if config.features.underscore_wildcards {
println!("cargo:warning=applying underscore wildcards patch to boringssl");
@ -547,6 +546,10 @@ fn built_boring_source_path(config: &Config) -> &PathBuf {
.define("FIPS", "1");
}
if config.features.prefix_symbols {
cfg.define("CMAKE_POSITION_INDEPENDENT_CODE", "ON");
}
cfg.build_target("ssl").build();
cfg.build_target("crypto").build()
})
@ -560,7 +563,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option<String> {
// TODO(rmehra): figure out how to do this for windows
if env::var_os("CARGO_CFG_UNIX").is_some() {
match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() {
"macos" | "ios" => Some("c++".into()),
"macos" | "ios" | "freebsd" => Some("c++".into()),
_ => Some("stdc++".into()),
}
} else {
@ -574,6 +577,9 @@ fn main() {
if !config.env.docs_rs {
emit_link_directives(&config);
}
if config.features.prefix_symbols {
prefix_symbols(&config);
}
generate_bindings(&config);
}
@ -646,7 +652,7 @@ fn generate_bindings(config: &Config) {
.derive_copy(true)
.derive_debug(true)
.derive_default(true)
.derive_eq(true)
.derive_eq(false)
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
@ -668,6 +674,10 @@ fn generate_bindings(config: &Config) {
.clang_arg(sysroot.display().to_string());
}
if config.features.prefix_symbols {
builder = builder.parse_callbacks(Box::new(PrefixCallback));
}
let headers = [
"aes.h",
"asn1_mac.h",

View File

@ -0,0 +1,81 @@
use crate::{config::Config, pick_best_android_ndk_toolchain, run_command};
use std::{fs, io::Write, path::PathBuf, process::Command};
// The prefix to add to all symbols
// RBSSL = Rust BoringSSL, chosen arbitrarily to avoid collisions with other projects
const PREFIX: &str = "RBSSL";
// Callback to add a `link_name` macro with the prefix to all generated bindings
#[derive(Debug)]
pub struct PrefixCallback;
impl bindgen::callbacks::ParseCallbacks for PrefixCallback {
fn generated_link_name_override(
&self,
item_info: bindgen::callbacks::ItemInfo<'_>,
) -> Option<String> {
Some(format!("{PREFIX}_{}", item_info.name))
}
}
fn android_toolchain(config: &Config) -> PathBuf {
let mut android_bin_path = config
.env
.android_ndk_home
.clone()
.expect("Please set ANDROID_NDK_HOME for Android build");
android_bin_path.extend(["toolchains", "llvm", "prebuilt"]);
android_bin_path.push(pick_best_android_ndk_toolchain(&android_bin_path).unwrap());
android_bin_path.push("bin");
android_bin_path
}
pub fn prefix_symbols(config: &Config) {
// List static libraries to prefix symbols in
let static_libs: Vec<PathBuf> = [
config.out_dir.join("build"),
config.out_dir.join("build").join("ssl"),
config.out_dir.join("build").join("crypto"),
]
.iter()
.flat_map(|dir| {
["libssl.a", "libcrypto.a"]
.into_iter()
.map(move |file| PathBuf::from(dir).join(file))
})
.filter(|p| p.exists())
.collect();
// Use `nm` to list symbols in these static libraries
let nm = match &*config.target_os {
"android" => android_toolchain(config).join("llvm-nm"),
_ => PathBuf::from("nm"),
};
let out = run_command(Command::new(nm).args(&static_libs)).unwrap();
let mut redefine_syms: Vec<String> = String::from_utf8_lossy(&out.stdout)
.lines()
.filter(|l| {
[" T ", " D ", " B ", " C ", " R ", " W "]
.iter()
.any(|s| l.contains(s))
})
.filter_map(|l| l.split_whitespace().nth(2).map(|s| s.to_string()))
.filter(|l| !l.starts_with("_"))
.map(|l| format!("{l} {PREFIX}_{l}"))
.collect();
redefine_syms.sort();
redefine_syms.dedup();
let redefine_syms_path = config.out_dir.join("redefine_syms.txt");
let mut f = fs::File::create(&redefine_syms_path).unwrap();
for sym in &redefine_syms {
writeln!(f, "{sym}").unwrap();
}
f.flush().unwrap();
// Use `objcopy` to prefix symbols in these static libraries
let objcopy = match &*config.target_os {
"android" => android_toolchain(config).join("llvm-objcopy"),
_ => PathBuf::from("objcopy"),
};
for static_lib in &static_libs {
run_command(
Command::new(&objcopy)
.arg(format!("--redefine-syms={}", redefine_syms_path.display()))
.arg(static_lib),
)
.unwrap();
}
}

@ -1 +1 @@
Subproject commit 478b28ab12f2001a03261624261fd041f5439706
Subproject commit 44b3df6f03d85c901767250329c571db405122d5

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
From d39506c373dc03b1bfab2c01abc2232e36d13ed6 Mon Sep 17 00:00:00 2001
From: WANG Rui <wangrui@loongson.cn>
Date: Sun, 27 Apr 2025 16:09:06 +0800
Subject: [PATCH] Add basic support for LoongArch
Signed-off-by: WANG Rui <wangrui@loongson.cn>
---
src/crypto/fipsmodule/rand/getrandom_fillin.h | 2 ++
src/include/openssl/base.h | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/src/crypto/fipsmodule/rand/getrandom_fillin.h b/src/crypto/fipsmodule/rand/getrandom_fillin.h
index 0f290e963..669a9077c 100644
--- a/src/crypto/fipsmodule/rand/getrandom_fillin.h
+++ b/src/crypto/fipsmodule/rand/getrandom_fillin.h
@@ -30,6 +30,8 @@
#define EXPECTED_NR_getrandom 278
#elif defined(OPENSSL_ARM)
#define EXPECTED_NR_getrandom 384
+#elif defined(OPENSSL_LOONGARCH64)
+#define EXPECTED_NR_getrandom 278
#elif defined(OPENSSL_RISCV64)
#define EXPECTED_NR_getrandom 278
#endif
diff --git a/src/include/openssl/base.h b/src/include/openssl/base.h
index 67429c099..6ef32eeec 100644
--- a/src/include/openssl/base.h
+++ b/src/include/openssl/base.h
@@ -96,6 +96,11 @@ extern "C" {
#elif defined(__ARMEL__) || defined(_M_ARM)
#define OPENSSL_32_BIT
#define OPENSSL_ARM
+#elif defined(__loongarch__) && __SIZEOF_POINTER__ == 8
+#define OPENSSL_64_BIT
+#define OPENSSL_LOONGARCH64
+#elif defined(__loongarch__) && __SIZEOF_POINTER__ == 4
+#define OPENSSL_32_BIT
#elif defined(__MIPSEL__) && !defined(__LP64__)
#define OPENSSL_32_BIT
#define OPENSSL_MIPS
--
2.46.0

View File

@ -1,792 +0,0 @@
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 003e0a5f7..b8f8d49c8 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -138,6 +138,25 @@
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
* OTHERWISE.
*/
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#ifndef OPENSSL_HEADER_SSL_H
#define OPENSSL_HEADER_SSL_H
@@ -1138,6 +1157,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key(
SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, size_t num_certs,
EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method);
+// SSL_CTX_set_nullchain_and_key sets the private key for a
+// TLS client or server. Reference to the given |EVP_PKEY|
+// object is added as needed. Exactly one of |privkey| or |privkey_method|
+// may be non-NULL. Returns one on success and zero on error.
+// Note the lack of a corresponding public-key certificate.
+// See SSL_CTX_set_server_raw_public_key_certificate.
+OPENSSL_EXPORT int SSL_CTX_set_nullchain_and_key(
+ SSL_CTX *ctx,
+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method);
+
// SSL_set_chain_and_key sets the certificate chain and private key for a TLS
// client or server. References to the given |CRYPTO_BUFFER| and |EVP_PKEY|
// objects are added as needed. Exactly one of |privkey| or |privkey_method|
@@ -1146,6 +1175,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key(
SSL *ssl, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey,
const SSL_PRIVATE_KEY_METHOD *privkey_method);
+// SSL_set_nullchain_and_key sets the private key for a TLS
+// client or server. Reference to the given |EVP_PKEY|
+// object is added as needed. Exactly one of |privkey| or |privkey_method|
+// may be non-NULL. Returns one on success and zero on error.
+// Note the lack of a corresponding public-key certificate.
+// See SSL_set_server_raw_public_key_certificate.
+OPENSSL_EXPORT int SSL_set_nullchain_and_key(
+ SSL *ssl, EVP_PKEY *privkey,
+ const SSL_PRIVATE_KEY_METHOD *privkey_method);
+
// SSL_CTX_get0_chain returns the list of |CRYPTO_BUFFER|s that were set by
// |SSL_CTX_set_chain_and_key|. Reference counts are not incremented by this
// call. The return value may be |NULL| if no chain has been set.
@@ -3041,6 +3080,21 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl);
OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new);
+// Server Certificate Type.
+
+#define TLSEXT_CERTIFICATETYPE_X509 0
+#define TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY 2
+
+OPENSSL_EXPORT int SSL_CTX_set_server_raw_public_key_certificate(
+ SSL_CTX *ctx, const uint8_t *raw_public_key, unsigned raw_public_key_len);
+
+OPENSSL_EXPORT int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx);
+
+OPENSSL_EXPORT int SSL_set_server_raw_public_key_certificate(
+ SSL *ssl, const uint8_t *raw_public_key, unsigned raw_public_key_len);
+
+OPENSSL_EXPORT int SSL_has_server_raw_public_key_certificate(SSL *ssl);
+
// Certificate compression.
//
// Certificates in TLS 1.3 can be compressed (RFC 8879). BoringSSL supports this
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index c1207a3b7..ac6ed222a 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -146,6 +146,25 @@
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
* OTHERWISE.
*/
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#ifndef OPENSSL_HEADER_TLS1_H
#define OPENSSL_HEADER_TLS1_H
@@ -197,6 +216,9 @@ extern "C" {
// ExtensionType value from RFC 7301
#define TLSEXT_TYPE_application_layer_protocol_negotiation 16
+// ExtensionType value from RFC 7250
+#define TLSEXT_TYPE_server_certificate_type 20
+
// ExtensionType value from RFC 7685
#define TLSEXT_TYPE_padding 21
diff --git a/ssl/extensions.cc b/ssl/extensions.cc
index b13400097..8694712fd 100644
--- a/ssl/extensions.cc
+++ b/ssl/extensions.cc
@@ -105,6 +105,25 @@
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com). */
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#include <openssl/ssl.h>
@@ -3108,6 +3127,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert,
return true;
}
+// Server Certificate Type
+
+static bool ext_server_certificate_type_add_clienthello(const SSL_HANDSHAKE *hs,
+ CBB *out,
+ CBB *out_compressible,
+ ssl_client_hello_type_t type) {
+
+ if (hs->max_version <= TLS1_2_VERSION) {
+ return true;
+ }
+
+ if (hs->config->server_certificate_type_list.empty()) {
+ return true;
+ }
+
+ CBB contents, server_certificate_types;
+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) ||
+ !CBB_add_u16_length_prefixed(out, &contents) ||
+ !CBB_add_u8_length_prefixed(&contents, &server_certificate_types) ||
+ !CBB_add_bytes(&server_certificate_types,
+ hs->config->server_certificate_type_list.data(),
+ hs->config->server_certificate_type_list.size()) ||
+ !CBB_flush(out)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool ssl_is_certificate_type_allowed(CBS *certificate_type_list,
+ uint8_t certificate_type)
+{
+ uint8_t supported_certificate_type;
+ while (CBS_len(certificate_type_list) > 0) {
+ if (!CBS_get_u8(certificate_type_list,
+ &supported_certificate_type)) {
+ break;
+ }
+
+ if (supported_certificate_type != certificate_type) {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool ext_server_certificate_type_parse_serverhello(SSL_HANDSHAKE *hs,
+ uint8_t *out_alert,
+ CBS *content)
+{
+ if (hs->max_version <= TLS1_2_VERSION ||
+ hs->config->server_certificate_type_list.empty()) {
+ return true;
+ }
+
+ // Strict
+ if (!content) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+ return false;
+ }
+
+ CBS certificate_type_list =
+ MakeConstSpan(hs->config->server_certificate_type_list);
+
+ uint8_t certificate_type;
+ if (CBS_get_u8(content, &certificate_type) &&
+ ssl_is_certificate_type_allowed(&certificate_type_list,
+ certificate_type)) {
+ hs->server_certificate_type = certificate_type;
+ hs->server_certificate_type_negotiated = 1;
+ return true;
+ }
+
+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+ return false;
+}
+
+static bool ext_server_certificate_type_parse_clienthello(SSL_HANDSHAKE *hs,
+ uint8_t *out_alert,
+ CBS *content)
+{
+ if (!content) {
+ return true;
+ }
+
+ if (hs->max_version <= TLS1_2_VERSION ||
+ hs->config->server_certificate_type_list.empty()) {
+ return true;
+ }
+
+ CBS certificate_type_list =
+ MakeConstSpan(hs->config->server_certificate_type_list);
+
+ CBS type_list;
+ if (!CBS_get_u8_length_prefixed(content, &type_list)) {
+ type_list.len = 0;
+ }
+
+ uint8_t type;
+ while(CBS_len(&type_list) > 0) {
+ if (!CBS_get_u8(&type_list, &type)) {
+ break;
+ }
+
+ if (!ssl_is_certificate_type_allowed(&certificate_type_list, type)) {
+ continue;
+ }
+
+ hs->server_certificate_type = type;
+ hs->server_certificate_type_negotiated = 1;
+ return true;
+ }
+
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+ return false;
+}
+
+static bool ext_server_certificate_type_add_serverhello(SSL_HANDSHAKE *hs,
+ CBB *out)
+{
+ if (!hs->server_certificate_type_negotiated) {
+ return true;
+ }
+
+ CBB contents;
+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) ||
+ !CBB_add_u16_length_prefixed(out, &contents) ||
+ !CBB_add_u8(&contents, hs->server_certificate_type) ||
+ !CBB_flush(out)) {
+ return false;
+ }
+
+ return true;
+}
+
// kExtensions contains all the supported extensions.
static const struct tls_extension kExtensions[] = {
{
@@ -3289,6 +3448,13 @@ static const struct tls_extension kExtensions[] = {
ignore_parse_clienthello,
ext_alps_add_serverhello_old,
},
+ {
+ TLSEXT_TYPE_server_certificate_type,
+ ext_server_certificate_type_add_clienthello,
+ ext_server_certificate_type_parse_serverhello,
+ ext_server_certificate_type_parse_clienthello,
+ ext_server_certificate_type_add_serverhello,
+ },
};
#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 8d5a23872..c8ca629e8 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -109,6 +109,25 @@
* Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
* ECC cipher suite support in OpenSSL originally developed by
* SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#include <openssl/ssl.h>
@@ -148,6 +167,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
handback(false),
hints_requested(false),
cert_compression_negotiated(false),
+ server_certificate_type_negotiated(false),
apply_jdk11_workaround(false),
can_release_private_key(false),
channel_id_negotiated(false) {
@@ -365,7 +385,21 @@ enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) {
uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
enum ssl_verify_result_t ret;
- if (hs->config->custom_verify_callback != nullptr) {
+ if (hs->server_certificate_type_negotiated &&
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) {
+ ret = ssl_verify_invalid;
+ EVP_PKEY *peer_pubkey = hs->peer_pubkey.get();
+ CBS spki = MakeConstSpan(ssl->config->server_raw_public_key_certificate);
+ EVP_PKEY *pubkey = EVP_parse_public_key(&spki);
+ if (!pubkey) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+ alert = SSL_AD_INTERNAL_ERROR;
+ } else if (EVP_PKEY_cmp(peer_pubkey, pubkey) == 1 /* Equal */) {
+ ret = ssl_verify_ok;
+ } else {
+ alert = SSL_AD_BAD_CERTIFICATE;
+ }
+ } else if (hs->config->custom_verify_callback != nullptr) {
ret = hs->config->custom_verify_callback(ssl, &alert);
switch (ret) {
case ssl_verify_ok:
diff --git a/ssl/internal.h b/ssl/internal.h
index c9facb699..d7363e729 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -138,6 +138,25 @@
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
* OTHERWISE.
*/
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#ifndef OPENSSL_HEADER_SSL_INTERNAL_H
#define OPENSSL_HEADER_SSL_INTERNAL_H
@@ -1311,6 +1330,8 @@ int ssl_write_buffer_flush(SSL *ssl);
// configured.
bool ssl_has_certificate(const SSL_HANDSHAKE *hs);
+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs);
+
// ssl_parse_cert_chain parses a certificate list from |cbs| in the format used
// by a TLS Certificate message. On success, it advances |cbs| and returns
// true. Otherwise, it returns false and sets |*out_alert| to an alert to send
@@ -1912,6 +1933,8 @@ struct SSL_HANDSHAKE {
// |cert_compression_negotiated| is true.
uint16_t cert_compression_alg_id;
+ uint8_t server_certificate_type;
+
// ech_hpke_ctx is the HPKE context used in ECH. On the server, it is
// initialized if |ech_status| is |ssl_ech_accepted|. On the client, it is
// initialized if |selected_ech_config| is not nullptr.
@@ -2062,6 +2085,8 @@ struct SSL_HANDSHAKE {
// cert_compression_negotiated is true iff |cert_compression_alg_id| is valid.
bool cert_compression_negotiated : 1;
+ bool server_certificate_type_negotiated : 1;
+
// apply_jdk11_workaround is true if the peer is probably a JDK 11 client
// which implemented TLS 1.3 incorrectly.
bool apply_jdk11_workaround : 1;
@@ -3074,6 +3099,9 @@ struct SSL_CONFIG {
// along with their corresponding ALPS values.
GrowableArray<ALPSConfig> alps_configs;
+ Array<uint8_t> server_certificate_type_list;
+ Array<uint8_t> server_raw_public_key_certificate;
+
// Contains the QUIC transport params that this endpoint will send.
Array<uint8_t> quic_transport_params;
@@ -3666,6 +3694,9 @@ struct ssl_ctx_st {
// format.
bssl::Array<uint8_t> alpn_client_proto_list;
+ bssl::Array<uint8_t> server_certificate_type_list;
+ bssl::Array<uint8_t> server_raw_public_key_certificate;
+
// SRTP profiles we are willing to do from RFC 5764
bssl::UniquePtr<STACK_OF(SRTP_PROTECTION_PROFILE)> srtp_profiles;
diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc
index aa46a8bb6..d90840fce 100644
--- a/ssl/ssl_cert.cc
+++ b/ssl/ssl_cert.cc
@@ -111,6 +111,25 @@
* Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
* ECC cipher suite support in OpenSSL originally developed by
* SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#include <openssl/ssl.h>
@@ -302,6 +321,25 @@ static int cert_set_chain_and_key(
return 1;
}
+static int cert_set_key(
+ CERT *cert,
+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+ if (privkey == NULL && privkey_method == NULL) {
+ OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
+ return 0;
+ }
+
+ if (privkey != NULL && privkey_method != NULL) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD);
+ return 0;
+ }
+
+ cert->privatekey = UpRef(privkey);
+ cert->key_method = privkey_method;
+
+ return 1;
+}
+
bool ssl_set_cert(CERT *cert, UniquePtr<CRYPTO_BUFFER> buffer) {
switch (check_leaf_cert_and_privkey(buffer.get(), cert->privatekey.get())) {
case leaf_cert_and_privkey_error:
@@ -343,6 +381,12 @@ bool ssl_has_certificate(const SSL_HANDSHAKE *hs) {
ssl_has_private_key(hs);
}
+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs) {
+ return hs->server_certificate_type_negotiated &&
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY &&
+ ssl_has_private_key(hs);
+}
+
bool ssl_parse_cert_chain(uint8_t *out_alert,
UniquePtr<STACK_OF(CRYPTO_BUFFER)> *out_chain,
UniquePtr<EVP_PKEY> *out_pubkey,
@@ -721,11 +765,20 @@ bool ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey,
bool ssl_on_certificate_selected(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
- if (!ssl_has_certificate(hs)) {
+ if (!ssl_has_certificate(hs) &&
+ !ssl_has_raw_public_key_certificate(hs)) {
// Nothing to do.
return true;
}
+ if (ssl_has_raw_public_key_certificate(hs)) {
+ CBS spki = MakeConstSpan(
+ ssl->config->server_raw_public_key_certificate.data(),
+ ssl->config->server_raw_public_key_certificate.size());
+ hs->local_pubkey = UniquePtr<EVP_PKEY>(EVP_parse_public_key(&spki));
+ return hs->local_pubkey != NULL;
+ }
+
if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(hs)) {
return false;
}
@@ -880,6 +933,15 @@ int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs,
privkey, privkey_method);
}
+int SSL_set_nullchain_and_key(SSL *ssl,
+ EVP_PKEY *privkey,
+ const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+ if (!ssl->config) {
+ return 0;
+ }
+ return cert_set_key(ssl->config->cert.get(), privkey, privkey_method);
+}
+
int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs,
size_t num_certs, EVP_PKEY *privkey,
const SSL_PRIVATE_KEY_METHOD *privkey_method) {
@@ -887,6 +949,12 @@ int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs,
privkey_method);
}
+int SSL_CTX_set_nullchain_and_key(SSL_CTX *ctx,
+ EVP_PKEY *privkey,
+ const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+ return cert_set_key(ctx->cert.get(), privkey, privkey_method);
+}
+
const STACK_OF(CRYPTO_BUFFER)* SSL_CTX_get0_chain(const SSL_CTX *ctx) {
return ctx->cert->chain.get();
}
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 58b68e675..384debbd3 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -137,6 +137,25 @@
* SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
* OTHERWISE. */
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#include <openssl/ssl.h>
@@ -687,6 +706,11 @@ SSL *SSL_new(SSL_CTX *ctx) {
ssl->config->handoff = ctx->handoff;
ssl->quic_method = ctx->quic_method;
+ ssl->config->server_certificate_type_list.CopyFrom(
+ ctx->server_certificate_type_list);
+ ssl->config->server_raw_public_key_certificate.CopyFrom(
+ ctx->server_raw_public_key_certificate);
+
if (!ssl->method->ssl_new(ssl.get()) ||
!ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) {
return nullptr;
@@ -3249,6 +3273,53 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) {
return SSL_set1_groups_list(ssl, curves);
}
+int SSL_CTX_set_server_raw_public_key_certificate(SSL_CTX *ctx,
+ const uint8_t *raw_public_key, unsigned raw_public_key_len) {
+ if (!ctx->server_raw_public_key_certificate.CopyFrom(
+ MakeConstSpan(raw_public_key, raw_public_key_len))) {
+ return 0; /* Failure */
+ }
+
+ if (!ctx->server_certificate_type_list.Init(1)) {
+ return 0;
+ }
+ ctx->server_certificate_type_list[0] = TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY;
+
+ return 1; /* Success */
+}
+
+int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx) {
+ return !ctx->server_raw_public_key_certificate.empty();
+}
+
+int SSL_set_server_raw_public_key_certificate(SSL *ssl,
+ const uint8_t *raw_public_key, unsigned raw_public_key_len) {
+ if (!ssl->config) {
+ return 0; /* Failure */
+ }
+
+ if (!ssl->config->server_raw_public_key_certificate.CopyFrom(
+ MakeConstSpan(raw_public_key, raw_public_key_len))) {
+ return 0;
+ }
+
+ if (!ssl->config->server_certificate_type_list.Init(1)) {
+ return 0;
+ }
+ ssl->config->server_certificate_type_list[0] =
+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY;
+
+ return 1; /* Success */
+}
+
+int SSL_has_server_raw_public_key_certificate(SSL *ssl) {
+ if (!ssl->config) {
+ return 0; /* Failure */
+ }
+
+ return !ssl->config->server_raw_public_key_certificate.empty();
+}
+
namespace fips202205 {
// (References are to SP 800-52r2):
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 5ab5a1c93..79135613e 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -11,6 +11,25 @@
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#include <openssl/ssl.h>
@@ -197,7 +216,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg,
return false;
}
- if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) {
+ if (hs->server_certificate_type_negotiated &&
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) {
+ pkey = UniquePtr<EVP_PKEY>(EVP_parse_public_key(&certificate));
+ if (!pkey) {
+ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+ return false;
+ }
+ }
+ else if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) {
pkey = ssl_cert_parse_pubkey(&certificate);
if (!pkey) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
@@ -299,7 +327,10 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg,
}
if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) {
- if (!allow_anonymous) {
+ if (!allow_anonymous &&
+ !(hs->server_certificate_type_negotiated &&
+ hs->server_certificate_type ==
+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED);
return false;
@@ -416,6 +447,20 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) {
return false;
}
+ if (hs->server_certificate_type_negotiated &&
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) {
+ CBB leaf, extensions;
+ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) ||
+ !CBB_add_bytes(&leaf,
+ ssl->config->server_raw_public_key_certificate.data(),
+ ssl->config->server_raw_public_key_certificate.size()) ||
+ !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) {
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+ return false;
+ }
+ return ssl_add_message_cbb(ssl, cbb.get());
+ }
+
if (!ssl_has_certificate(hs)) {
return ssl_add_message_cbb(ssl, cbb.get());
}
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 707cf846b..6916606c2 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -11,6 +11,25 @@
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+/* ====================================================================
+ * Copyright 2020 Apple Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the “Software”),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
#include <openssl/ssl.h>
@@ -860,7 +879,8 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
// Send the server Certificate message, if necessary.
if (!ssl->s3->session_reused) {
- if (!ssl_has_certificate(hs)) {
+ if (!ssl_has_certificate(hs) &&
+ !ssl_has_raw_public_key_certificate(hs)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
return ssl_hs_error;
}

View File

@ -1,10 +1,21 @@
https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 9699b5a75..b0e9b34a6 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -4420,6 +4420,31 @@ TEST(X509Test, Names) {
--- a/src/crypto/x509v3/v3_utl.c
+++ b/src/crypto/x509v3/v3_utl.c
@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len,
// Check that the part matched by the wildcard contains only
// permitted characters and only matches a single label.
for (p = wildcard_start; p != wildcard_end; ++p) {
- if (!OPENSSL_isalnum(*p) && *p != '-') {
+ if (!OPENSSL_isalnum(*p) && *p != '-' &&
+ !(*p == '_' &&
+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) {
return 0;
}
}
--- a/src/crypto/x509/x509_test.cc
+++ b/src/crypto/x509/x509_test.cc
@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) {
/*invalid_emails=*/{},
/*flags=*/0,
},
@ -36,26 +47,9 @@ index 9699b5a75..b0e9b34a6 100644
};
size_t i = 0;
diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c
index bbc82e283..e61e1901d 100644
--- a/crypto/x509v3/v3_utl.c
+++ b/crypto/x509v3/v3_utl.c
@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len,
// Check that the part matched by the wildcard contains only
// permitted characters and only matches a single label.
for (p = wildcard_start; p != wildcard_end; ++p) {
- if (!OPENSSL_isalnum(*p) && *p != '-') {
+ if (!OPENSSL_isalnum(*p) && *p != '-' &&
+ !(*p == '_' &&
+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) {
return 0;
}
}
diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h
index 2a2e02c2e..24e0604b0 100644
--- a/include/openssl/x509v3.h
+++ b/include/openssl/x509v3.h
@@ -939,6 +939,8 @@ OPENSSL_EXPORT STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x);
--- a/src/include/openssl/x509c3.h
+++ b/src/include/openssl/x509v3.h
@@ -4497,6 +4497,8 @@ OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *);
#define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
// Skip the subject common name fallback if subjectAltNames is missing.
#define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20
@ -64,3 +58,4 @@ index 2a2e02c2e..24e0604b0 100644
OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen,
unsigned int flags, char **peername);
--

View File

@ -1,11 +1,11 @@
[package]
name = "boring"
name = "boring2"
version = { workspace = true }
authors = ["Steven Fackler <sfackler@gmail.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
license = "Apache-2.0"
description = "BoringSSL bindings"
repository = { workspace = true }
documentation = "https://docs.rs/boring"
documentation = "https://docs.rs/boring2"
readme = "README.md"
keywords = ["crypto", "tls", "ssl", "dtls"]
categories = ["cryptography", "api-bindings"]
@ -13,7 +13,7 @@ edition = { workspace = true }
rust-version = "1.80"
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental", "underscore-wildcards"]
features = ["underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
@ -25,25 +25,17 @@ fips = ["boring-sys/fips"]
# **DO NOT USE** This will be removed without warning in future releases.
legacy-compat-deprecated = []
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
# This feature is necessary in order to compile the bindings for the
# default branch of boringSSL. Alternatively, a version of boringSSL that
# implements the same feature set can be provided by setting
# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
rpk = ["boring-sys/rpk"]
# Applies a patch to the boringSSL source code that enables support for PQ key
# exchange. This feature is necessary in order to compile the bindings for the
# default branch of boringSSL. Alternatively, a version of boringSSL that
# implements the same feature set can be provided by setting
# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
pq-experimental = ["boring-sys/pq-experimental"]
# Applies a patch to enable
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as
# those for `pq-experimental` feature apply.
# Applies a patch to enable `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This
# feature is necessary in order to compile the bindings for the default branch
# of boringSSL. Alternatively, a version of boringSSL that implements the same
# feature set can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH` and
# `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
underscore-wildcards = ["boring-sys/underscore-wildcards"]
# Add a prefix to all symbols in libcrypto and libssl to prevent conflicts
# with other OpenSSL or BoringSSL versions that might be linked in the same process.
prefix-symbols = ["boring-sys/prefix-symbols"]
[dependencies]
bitflags = { workspace = true }
foreign-types = { workspace = true }

View File

@ -1,3 +1,5 @@
use boring2 as boring;
fn main() {
println!("boring::fips::enabled(): {}", boring::fips::enabled());
}

View File

@ -1,7 +1,7 @@
//! A program that generates ca certs, certs verified by the ca, and public
//! and private keys.
extern crate boring;
extern crate boring2 as boring;
use boring::asn1::Asn1Time;
use boring::bn::{BigNum, MsbOption};

View File

@ -22,7 +22,7 @@
//!
//! ## Key wrapping
//! ```rust
//! use boring::aes::{AesKey, unwrap_key, wrap_key};
//! use boring2::aes::{AesKey, unwrap_key, wrap_key};
//!
//! let kek = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
//! let key_to_wrap = b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF";

View File

@ -21,7 +21,7 @@
//! ## Examples
//!
//! ```
//! use boring::asn1::Asn1Time;
//! use boring2::asn1::Asn1Time;
//! let tomorrow = Asn1Time::days_from_now(1);
//! ```
use crate::ffi;

View File

@ -9,8 +9,8 @@
//! # Examples
//!
//! ```
//! use boring::bn::BigNum;
//! use boring::error::ErrorStack;
//! use boring2::bn::BigNum;
//! use boring2::error::ErrorStack;
//!
//! fn main() -> Result<(), ErrorStack> {
//! let a = BigNum::new()?; // a = 0
@ -97,8 +97,8 @@ foreign_type_and_impl_send_sync! {
///
/// # Examples
/// ```
/// use boring::bn::BigNum;
/// # use boring::error::ErrorStack;
/// use boring2::bn::BigNum;
/// # use boring2::error::ErrorStack;
/// # fn bignums() -> Result< (), ErrorStack > {
/// let little_big = BigNum::from_u32(std::u32::MAX)?;
/// assert_eq!(*&little_big.num_bytes(), 4);
@ -272,7 +272,7 @@ impl BigNumRef {
/// # Examples
///
/// ```
/// # use boring::bn::BigNum;
/// # use boring2::bn::BigNum;
/// # use std::cmp::Ordering;
/// let s = -BigNum::from_u32(8).unwrap();
/// let o = BigNum::from_u32(8).unwrap();
@ -316,8 +316,8 @@ impl BigNumRef {
/// # Examples
///
/// ```
/// use boring::bn::{BigNum, MsbOption};
/// use boring::error::ErrorStack;
/// use boring2::bn::{BigNum, MsbOption};
/// use boring2::error::ErrorStack;
///
/// fn generate_random() -> Result< BigNum, ErrorStack > {
/// let mut big = BigNum::new()?;
@ -370,8 +370,8 @@ impl BigNumRef {
/// # Examples
///
/// ```
/// use boring::bn::BigNum;
/// use boring::error::ErrorStack;
/// use boring2::bn::BigNum;
/// use boring2::error::ErrorStack;
///
/// fn generate_weak_prime() -> Result< BigNum, ErrorStack > {
/// let mut big = BigNum::new()?;
@ -729,7 +729,7 @@ impl BigNumRef {
/// `self` can be recreated by using `from_slice`.
///
/// ```
/// # use boring::bn::BigNum;
/// # use boring2::bn::BigNum;
/// let s = -BigNum::from_u32(4543).unwrap();
/// let r = BigNum::from_u32(4543).unwrap();
///
@ -756,7 +756,7 @@ impl BigNumRef {
/// `self` can be recreated by using `from_slice`.
///
/// ```
/// # use boring::bn::BigNum;
/// # use boring2::bn::BigNum;
/// let bn = BigNum::from_u32(0x4543).unwrap();
///
/// let bn_vec = bn.to_vec_padded(4).unwrap();
@ -781,7 +781,7 @@ impl BigNumRef {
/// Returns a decimal string representation of `self`.
///
/// ```
/// # use boring::bn::BigNum;
/// # use boring2::bn::BigNum;
/// let s = -BigNum::from_u32(12345).unwrap();
///
/// assert_eq!(&**s.to_dec_str().unwrap(), "-12345");
@ -797,7 +797,7 @@ impl BigNumRef {
/// Returns a hexadecimal string representation of `self`.
///
/// ```
/// # use boring::bn::BigNum;
/// # use boring2::bn::BigNum;
/// let s = -BigNum::from_u32(0x99ff).unwrap();
///
/// assert_eq!(&**s.to_hex_str().unwrap(), "-99ff");
@ -870,7 +870,7 @@ impl BigNum {
/// [`BN_bin2bn`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_bin2bn.html
///
/// ```
/// # use boring::bn::BigNum;
/// # use boring2::bn::BigNum;
/// let bignum = BigNum::from_slice(&[0x12, 0x00, 0x34]).unwrap();
///
/// assert_eq!(bignum, BigNum::from_u32(0x120034).unwrap());

View File

@ -41,9 +41,9 @@ generic_foreign_type_and_impl_send_sync! {
/// # Examples
///
/// ```
/// use boring::dsa::Dsa;
/// use boring::error::ErrorStack;
/// use boring::pkey::Private;
/// use boring2::dsa::Dsa;
/// use boring2::error::ErrorStack;
/// use boring2::pkey::Private;
///
/// fn create_dsa() -> Result<Dsa<Private>, ErrorStack> {
/// let sign = Dsa::generate(2048)?;

View File

@ -609,10 +609,10 @@ impl EcKey<Public> {
/// # Example
///
/// ```no_run
/// use boring::bn::BigNumContext;
/// use boring::ec::*;
/// use boring::nid::Nid;
/// use boring::pkey::PKey;
/// use boring2::bn::BigNumContext;
/// use boring2::ec::*;
/// use boring2::nid::Nid;
/// use boring2::pkey::PKey;
///
/// // get bytes from somewhere, i.e. this will not produce a valid key
/// let public_key: Vec<u8> = vec![];

View File

@ -6,8 +6,8 @@
//! # Examples
//!
//! ```
//! use boring::error::ErrorStack;
//! use boring::bn::BigNum;
//! use boring2::error::ErrorStack;
//! use boring2::bn::BigNum;
//!
//! let an_error = BigNum::from_dec_str("Cannot parse letters");
//! match an_error {
@ -103,7 +103,7 @@ impl fmt::Display for ErrorStack {
write!(
fmt,
"[{}]",
err.reason_internal()
err.reason()
.or_else(|| err.library())
.unwrap_or("unknown reason")
)?;
@ -252,7 +252,10 @@ impl Error {
/// Returns the reason for the error.
#[must_use]
pub fn reason(&self) -> Option<&'static str> {
pub fn reason(&self) -> Option<&str> {
if self.is_internal() {
return self.data();
}
unsafe {
let cstr = ffi::ERR_reason_error_string(self.code);
if cstr.is_null() {
@ -330,15 +333,6 @@ impl Error {
fn is_internal(&self) -> bool {
std::ptr::eq(self.file, BORING_INTERNAL.as_ptr())
}
// reason() needs 'static
fn reason_internal(&self) -> Option<&str> {
if self.is_internal() {
self.data()
} else {
self.reason()
}
}
}
impl fmt::Debug for Error {
@ -369,7 +363,7 @@ impl fmt::Display for Error {
write!(
fmt,
"{}\n\nCode: {:08X}\nLoc: {}:{}",
self.reason_internal().unwrap_or("unknown TLS error"),
self.reason().unwrap_or("unknown TLS error"),
&self.code,
self.file(),
self.line()

View File

@ -116,7 +116,7 @@ use self::State::*;
/// Calculate a hash in one go:
///
/// ```
/// use boring::hash::{hash, MessageDigest};
/// use boring2::hash::{hash, MessageDigest};
///
/// let data = b"\x42\xF4\x97\xE0";
/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";
@ -127,7 +127,7 @@ use self::State::*;
/// Supply the input in chunks:
///
/// ```
/// use boring::hash::{Hasher, MessageDigest};
/// use boring2::hash::{Hasher, MessageDigest};
///
/// let data = [b"\x42\xF4", b"\x97\xE0"];
/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";

View File

@ -70,11 +70,6 @@
//!
//! # Optional patches
//!
//! ## Raw Public Key
//!
//! The crate can be compiled with [RawPublicKey](https://datatracker.ietf.org/doc/html/rfc7250)
//! support by turning on `rpk` compilation feature.
//!
//! ## Experimental post-quantum cryptography
//!
//! The crate can be compiled with [post-quantum cryptography](https://blog.cloudflare.com/post-quantum-for-all/)

View File

@ -16,7 +16,7 @@
//! values:
//!
//! ```
//! use boring::memcmp::eq;
//! use boring2::memcmp::eq;
//!
//! // We want to compare `a` to `b` and `c`, without giving
//! // away through timing analysis that `c` is more similar to `a`
@ -48,7 +48,7 @@ use libc::size_t;
/// values:
///
/// ```
/// use boring::memcmp::eq;
/// use boring2::memcmp::eq;
///
/// // We want to compare `a` to `b` and `c`, without giving
/// // away through timing analysis that `c` is more similar to `a`

View File

@ -34,7 +34,7 @@ pub struct SignatureAlgorithms {
/// To view the integer representation of a `Nid`:
///
/// ```
/// use boring::nid::Nid;
/// use boring2::nid::Nid;
///
/// assert!(Nid::AES_256_GCM.as_raw() == 901);
/// ```

View File

@ -29,8 +29,8 @@
//! Generate a 2048-bit RSA public/private key pair and print the public key.
//!
//! ```rust
//! use boring::rsa::Rsa;
//! use boring::pkey::PKey;
//! use boring2::rsa::Rsa;
//! use boring2::pkey::PKey;
//! use std::str;
//!
//! let rsa = Rsa::generate(2048).unwrap();

View File

@ -5,7 +5,7 @@
//! To generate a buffer with cryptographically strong bytes:
//!
//! ```
//! use boring::rand::rand_bytes;
//! use boring2::rand::rand_bytes;
//!
//! let mut buf = [0; 256];
//! rand_bytes(&mut buf).unwrap();
@ -25,7 +25,7 @@ use crate::error::ErrorStack;
/// To generate a buffer with cryptographically strong bytes:
///
/// ```
/// use boring::rand::rand_bytes;
/// use boring2::rand::rand_bytes;
///
/// let mut buf = [0; 256];
/// rand_bytes(&mut buf).unwrap();

View File

@ -16,7 +16,7 @@
//! Generate a 2048-bit RSA key pair and use the public key to encrypt some data.
//!
//! ```rust
//! use boring::rsa::{Rsa, Padding};
//! use boring2::rsa::{Rsa, Padding};
//!
//! let rsa = Rsa::generate(2048).unwrap();
//! let data = b"foobar";

View File

@ -16,7 +16,7 @@
//! ```rust
//! extern crate hex;
//!
//! use boring::sha;
//! use boring2::sha;
//!
//! fn main() {
//! let mut hasher = sha::Sha256::new();
@ -36,7 +36,7 @@
//! ```rust
//! extern crate hex;
//!
//! use boring::sha::sha256;
//! use boring2::sha::sha256;
//!
//! fn main() {
//! let hash = sha256(b"your data or message");

View File

@ -10,10 +10,10 @@
//! Sign and verify data given an RSA keypair:
//!
//! ```rust
//! use boring::sign::{Signer, Verifier};
//! use boring::rsa::Rsa;
//! use boring::pkey::PKey;
//! use boring::hash::MessageDigest;
//! use boring2::sign::{Signer, Verifier};
//! use boring2::rsa::Rsa;
//! use boring2::pkey::PKey;
//! use boring2::hash::MessageDigest;
//!
//! // Generate a keypair
//! let keypair = Rsa::generate(2048).unwrap();

View File

@ -1,13 +1,16 @@
use std::io::{Read, Write};
use std::ops::{Deref, DerefMut};
use foreign_types::ForeignTypeRef;
use openssl_macros::corresponds;
use crate::dh::Dh;
use crate::error::ErrorStack;
use crate::ssl::{
HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode,
SslOptions, SslRef, SslStream, SslVerifyMode,
};
use crate::version;
use crate::{cvt, version};
use std::net::IpAddr;
use super::MidHandshakeSslStream;
@ -25,16 +28,12 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
enum ContextType {
WithMethod(SslMethod),
#[cfg(feature = "rpk")]
Rpk,
}
#[allow(clippy::inconsistent_digit_grouping)]
fn ctx(ty: ContextType) -> Result<SslContextBuilder, ErrorStack> {
let mut ctx = match ty {
ContextType::WithMethod(method) => SslContextBuilder::new(method),
#[cfg(feature = "rpk")]
ContextType::Rpk => SslContextBuilder::new_rpk(),
}?;
let mut opts = SslOptions::ALL
@ -87,10 +86,11 @@ impl SslConnector {
Ok(SslConnectorBuilder(ctx))
}
/// Creates a new builder for TLS connections with raw public key.
#[cfg(feature = "rpk")]
pub fn rpk_builder() -> Result<SslConnectorBuilder, ErrorStack> {
let mut ctx = ctx(ContextType::Rpk)?;
/// Creates a new builder for TLS connections with no verification.
///
/// This is useful for testing and other purposes where you want to skip verification.
pub fn no_default_verify_builder(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
let mut ctx = ctx(ContextType::WithMethod(method))?;
ctx.set_cipher_list(
"DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
)?;
@ -224,13 +224,7 @@ impl ConnectConfiguration {
self.ssl.set_hostname(domain)?;
}
#[cfg(feature = "rpk")]
let verify_hostname = !self.ssl.ssl_context().is_rpk() && self.verify_hostname;
#[cfg(not(feature = "rpk"))]
let verify_hostname = self.verify_hostname;
if verify_hostname {
if self.verify_hostname {
setup_verify_hostname(&mut self.ssl, domain)?;
}
@ -270,6 +264,49 @@ impl ConnectConfiguration {
}
}
impl ConnectConfiguration {
/// Enables or disables ECH grease.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_set_enable_ech_grease)]
pub fn set_enable_ech_grease(&mut self, enable: bool) {
unsafe { ffi::SSL_set_enable_ech_grease(self.as_ptr(), enable as _) }
}
/// Sets whether the aes hardware override should be enabled.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_set_aes_hw_override)]
pub fn set_aes_hw_override(&mut self, enable: bool) {
unsafe { ffi::SSL_set_aes_hw_override(self.as_ptr(), enable as _) }
}
/// Sets application settings flag for ALPS (Application-Layer Protocol Negotiation).
#[corresponds(SSL_add_application_settings)]
pub fn add_application_settings(&mut self, alps: &[u8]) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::SSL_add_application_settings(
self.as_ptr(),
alps.as_ptr(),
alps.len(),
std::ptr::null(),
0,
))
.map(|_| ())
}
}
/// Sets the ALPS use new codepoint flag.
#[corresponds(SSL_set_alps_use_new_codepoint)]
pub fn set_alps_use_new_codepoint(&mut self, use_new: bool) {
unsafe { ffi::SSL_set_alps_use_new_codepoint(self.as_ptr(), use_new as _) }
}
/// Sets the SSL options.
#[corresponds(SSL_set_options)]
pub fn set_options(&mut self, options: SslOptions) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::SSL_set_options(self.as_ptr(), options.bits()) as _).map(|_| ()) }
}
}
impl Deref for ConnectConfiguration {
type Target = SslRef;
@ -292,21 +329,6 @@ impl DerefMut for ConnectConfiguration {
pub struct SslAcceptor(SslContext);
impl SslAcceptor {
/// Creates a new builder configured to connect to clients that support Raw Public Keys.
#[cfg(feature = "rpk")]
pub fn rpk() -> Result<SslAcceptorBuilder, ErrorStack> {
let mut ctx = ctx(ContextType::Rpk)?;
ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1);
let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
ctx.set_tmp_dh(&dh)?;
ctx.set_cipher_list(
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
)?;
Ok(SslAcceptorBuilder(ctx))
}
/// Creates a new builder configured to connect to non-legacy clients. This should generally be
/// considered a reasonable default choice.
///

View File

@ -249,12 +249,6 @@ fn fmt_mid_handshake_error(
f: &mut fmt::Formatter,
prefix: &str,
) -> fmt::Result {
#[cfg(feature = "rpk")]
if s.ssl().ssl_context().is_rpk() {
write!(f, "{}", prefix)?;
return write!(f, " {}", s.error());
}
match s.ssl().verify_result() {
// INVALID_CALL is returned if no verification took place,
// such as before a cert is sent.

View File

@ -8,7 +8,7 @@
//! To connect as a client to a remote server:
//!
//! ```no_run
//! use boring::ssl::{SslMethod, SslConnector};
//! use boring2::ssl::{SslMethod, SslConnector};
//! use std::io::{Read, Write};
//! use std::net::TcpStream;
//!
@ -26,7 +26,7 @@
//! To accept connections as a server from remote clients:
//!
//! ```no_run
//! use boring::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype};
//! use boring2::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype};
//! use std::net::{TcpListener, TcpStream};
//! use std::sync::Arc;
//! use std::thread;
@ -87,6 +87,8 @@ use crate::pkey::{HasPrivate, PKeyRef, Params, Private};
use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef};
use crate::ssl::bio::BioMethod;
use crate::ssl::callbacks::*;
#[cfg(not(feature = "fips"))]
use crate::ssl::ech::SslEchKeys;
use crate::ssl::error::InnerError;
use crate::stack::{Stack, StackRef, Stackable};
use crate::symm::CipherCtxRef;
@ -106,7 +108,8 @@ pub use self::async_callbacks::{
pub use self::connector::{
ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder,
};
pub use self::ech::{SslEchKeys, SslEchKeysRef};
#[cfg(not(feature = "fips"))]
pub use self::ech::SslEchKeysRef;
pub use self::error::{Error, ErrorCode, HandshakeError};
mod async_callbacks;
@ -189,6 +192,9 @@ bitflags! {
/// Disallow all renegotiation in TLSv1.2 and earlier.
const NO_RENEGOTIATION = ffi::SSL_OP_NO_RENEGOTIATION as _;
/// Disables PSK with DHE.
const NO_PSK_DHE_KE = ffi::SSL_OP_NO_PSK_DHE_KE as _;
}
}
@ -253,12 +259,6 @@ impl SslMethod {
unsafe { SslMethod(TLS_method()) }
}
/// Same as `tls`, but doesn't create X509 for certificates.
#[cfg(feature = "rpk")]
pub fn tls_with_buffer() -> SslMethod {
unsafe { SslMethod(ffi::TLS_with_buffers_method()) }
}
/// Support all versions of the DTLS protocol.
#[corresponds(DTLS_method)]
#[must_use]
@ -447,9 +447,6 @@ static SSL_INDEXES: LazyLock<Mutex<HashMap<TypeId, c_int>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static SESSION_CTX_INDEX: LazyLock<Index<Ssl, SslContext>> =
LazyLock::new(|| Ssl::new_ex_index().unwrap());
#[cfg(feature = "rpk")]
static RPK_FLAG_INDEX: LazyLock<Index<SslContext, bool>> =
LazyLock::new(|| SslContext::new_ex_index().unwrap());
/// An error returned from the SNI callback.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -536,7 +533,8 @@ impl SelectCertError {
/// **WARNING**: The current implementation of `From` is unsound, as it's possible to create an
/// ExtensionType that is not defined by the impl. `From` will be deprecated in favor of `TryFrom`
/// in the next major bump of the library.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ExtensionType(u16);
impl ExtensionType {
@ -568,10 +566,13 @@ impl ExtensionType {
pub const RENEGOTIATE: Self = Self(ffi::TLSEXT_TYPE_renegotiate as u16);
pub const DELEGATED_CREDENTIAL: Self = Self(ffi::TLSEXT_TYPE_delegated_credential as u16);
pub const APPLICATION_SETTINGS: Self = Self(ffi::TLSEXT_TYPE_application_settings as u16);
pub const APPLICATION_SETTINGS_NEW: Self =
Self(ffi::TLSEXT_TYPE_application_settings_new as u16);
pub const ENCRYPTED_CLIENT_HELLO: Self = Self(ffi::TLSEXT_TYPE_encrypted_client_hello as u16);
pub const CERTIFICATE_TIMESTAMP: Self = Self(ffi::TLSEXT_TYPE_certificate_timestamp as u16);
pub const NEXT_PROTO_NEG: Self = Self(ffi::TLSEXT_TYPE_next_proto_neg as u16);
pub const CHANNEL_ID: Self = Self(ffi::TLSEXT_TYPE_channel_id as u16);
pub const RECORD_SIZE_LIMIT: Self = Self(ffi::TLSEXT_TYPE_record_size_limit as u16);
}
impl From<u16> for ExtensionType {
@ -581,7 +582,7 @@ impl From<u16> for ExtensionType {
}
/// An SSL/TLS protocol version.
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
pub struct SslVersion(u16);
impl SslVersion {
@ -719,13 +720,15 @@ impl CompliancePolicy {
}
// IANA assigned identifier of compression algorithm. See https://www.rfc-editor.org/rfc/rfc8879.html#name-compression-algorithms
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct CertificateCompressionAlgorithm(u16);
impl CertificateCompressionAlgorithm {
pub const ZLIB: Self = Self(ffi::TLSEXT_cert_compression_zlib as u16);
pub const BROTLI: Self = Self(ffi::TLSEXT_cert_compression_brotli as u16);
pub const ZSTD: Self = Self(ffi::TLSEXT_cert_compression_zstd as u16);
}
/// A standard implementation of protocol selection for Application Layer Protocol Negotiation
@ -880,67 +883,11 @@ impl Ssl3AlertLevel {
pub const FATAL: Ssl3AlertLevel = Self(ffi::SSL3_AL_FATAL);
}
#[cfg(feature = "rpk")]
extern "C" fn rpk_verify_failure_callback(
_ssl: *mut ffi::SSL,
_out_alert: *mut u8,
) -> ffi::ssl_verify_result_t {
// Always verify the peer.
ffi::ssl_verify_result_t::ssl_verify_invalid
}
/// A builder for `SslContext`s.
pub struct SslContextBuilder {
ctx: SslContext,
/// If it's not shared, it can be exposed as mutable
has_shared_cert_store: bool,
#[cfg(feature = "rpk")]
is_rpk: bool,
}
#[cfg(feature = "rpk")]
impl SslContextBuilder {
/// Creates a new `SslContextBuilder` to be used with Raw Public Key.
#[corresponds(SSL_CTX_new)]
pub fn new_rpk() -> Result<SslContextBuilder, ErrorStack> {
unsafe {
init();
let ctx = cvt_p(ffi::SSL_CTX_new(SslMethod::tls_with_buffer().as_ptr()))?;
let mut builder = SslContextBuilder::from_ptr(ctx);
builder.is_rpk = true;
builder.set_ex_data(*RPK_FLAG_INDEX, true);
Ok(builder)
}
}
/// Sets raw public key certificate in DER format.
pub fn set_rpk_certificate(&mut self, cert: &[u8]) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::SSL_CTX_set_server_raw_public_key_certificate(
self.as_ptr(),
cert.as_ptr(),
cert.len() as u32,
))
.map(|_| ())
}
}
/// Sets RPK null chain private key.
pub fn set_null_chain_private_key<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
where
T: HasPrivate,
{
unsafe {
cvt(ffi::SSL_CTX_set_nullchain_and_key(
self.as_ptr(),
key.as_ptr(),
ptr::null_mut(),
))
.map(|_| ())
}
}
}
impl SslContextBuilder {
@ -956,20 +903,12 @@ impl SslContextBuilder {
/// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value.
///
#[cfg_attr(
feature = "rpk",
doc = "Keeps previous RPK state. Use `new_rpk()` to enable RPK."
)]
///
/// # Safety
///
/// The caller must ensure that the pointer is valid and uniquely owned by the builder.
/// The context must own its cert store exclusively.
pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder {
let ctx = SslContext::from_ptr(ctx);
SslContextBuilder {
#[cfg(feature = "rpk")]
is_rpk: ctx.is_rpk(),
has_shared_cert_store: false,
ctx,
}
@ -1005,9 +944,6 @@ impl SslContextBuilder {
where
F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send,
{
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
// NOTE(jlarisch): Q: Why don't we wrap the callback in an Arc, since
// `set_verify_callback` does?
// A: I don't think that Arc is necessary, and I don't think one is necessary here.
@ -1027,9 +963,6 @@ impl SslContextBuilder {
/// Configures the certificate verification method for new connections.
#[corresponds(SSL_CTX_set_verify)]
pub fn set_verify(&mut self, mode: SslVerifyMode) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None);
}
@ -1056,9 +989,6 @@ impl SslContextBuilder {
where
F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send,
{
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, Some(raw_verify::<F>));
@ -1084,9 +1014,6 @@ impl SslContextBuilder {
where
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
{
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
ffi::SSL_CTX_set_custom_verify(
@ -1166,9 +1093,6 @@ impl SslContextBuilder {
+ Sync
+ Send,
{
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
ffi::SSL_CTX_set_tlsext_ticket_key_cb(self.as_ptr(), Some(raw_ticket_key::<F>))
@ -1180,9 +1104,6 @@ impl SslContextBuilder {
/// If the peer's certificate chain is longer than this value, verification will fail.
#[corresponds(SSL_CTX_set_verify_depth)]
pub fn set_verify_depth(&mut self, depth: u32) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
ffi::SSL_CTX_set_verify_depth(self.as_ptr(), depth as c_int);
}
@ -1191,9 +1112,6 @@ impl SslContextBuilder {
/// Sets a custom certificate store for verifying peer certificates.
#[corresponds(SSL_CTX_set0_verify_cert_store)]
pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
cvt(
ffi::SSL_CTX_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int,
@ -1209,9 +1127,6 @@ impl SslContextBuilder {
#[corresponds(SSL_CTX_set_cert_store)]
#[deprecated(note = "Use set_cert_store_builder or set_cert_store_ref instead")]
pub fn set_cert_store(&mut self, cert_store: X509Store) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
self.has_shared_cert_store = false;
unsafe {
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
@ -1221,9 +1136,6 @@ impl SslContextBuilder {
/// Replaces the context's certificate store, and allows mutating the store afterwards.
#[corresponds(SSL_CTX_set_cert_store)]
pub fn set_cert_store_builder(&mut self, cert_store: X509StoreBuilder) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
self.has_shared_cert_store = false;
unsafe {
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
@ -1235,9 +1147,6 @@ impl SslContextBuilder {
/// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic.
#[corresponds(SSL_CTX_set_cert_store)]
pub fn set_cert_store_ref(&mut self, cert_store: &X509Store) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
self.has_shared_cert_store = true;
unsafe {
ffi::X509_STORE_up_ref(cert_store.as_ptr());
@ -1283,9 +1192,6 @@ impl SslContextBuilder {
/// if present, or defaults specified at OpenSSL build time otherwise.
#[corresponds(SSL_CTX_set_default_verify_paths)]
pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) }
}
@ -1294,9 +1200,6 @@ impl SslContextBuilder {
/// The file should contain a sequence of PEM-formatted CA certificates.
#[corresponds(SSL_CTX_load_verify_locations)]
pub fn set_ca_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ErrorStack> {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
.map_err(ErrorStack::internal_error)?;
unsafe {
@ -1315,9 +1218,6 @@ impl SslContextBuilder {
/// as trusted by this method.
#[corresponds(SSL_CTX_set_client_CA_list)]
pub fn set_client_ca_list(&mut self, list: Stack<X509Name>) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
ffi::SSL_CTX_set_client_CA_list(self.as_ptr(), list.as_ptr());
mem::forget(list);
@ -1328,9 +1228,6 @@ impl SslContextBuilder {
/// requesting client-side TLS authentication.
#[corresponds(SSL_CTX_add_client_CA)]
pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) }
}
@ -1366,9 +1263,6 @@ impl SslContextBuilder {
file: P,
file_type: SslFiletype,
) -> Result<(), ErrorStack> {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
.map_err(ErrorStack::internal_error)?;
unsafe {
@ -1416,9 +1310,6 @@ impl SslContextBuilder {
/// `set_certificate` to a trusted root.
#[corresponds(SSL_CTX_add_extra_chain_cert)]
pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe {
cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.into_ptr()) as c_int)?;
Ok(())
@ -1728,9 +1619,6 @@ impl SslContextBuilder {
#[corresponds(SSL_CTX_get_cert_store)]
#[must_use]
pub fn cert_store(&self) -> &X509StoreBuilderRef {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
unsafe { X509StoreBuilderRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) }
}
@ -1745,9 +1633,6 @@ impl SslContextBuilder {
///
#[corresponds(SSL_CTX_get_cert_store)]
pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
assert!(
!self.has_shared_cert_store,
"Shared X509Store can't be mutated. Make a new store"
@ -1973,6 +1858,77 @@ impl SslContextBuilder {
unsafe { ffi::SSL_CTX_set_grease_enabled(self.as_ptr(), enabled as _) }
}
/// Sets whether the context should enable record size limit.
#[corresponds(SSL_CTX_set_record_size_limit)]
pub fn set_record_size_limit(&mut self, limit: u16) {
unsafe { ffi::SSL_CTX_set_record_size_limit(self.as_ptr(), limit as _) }
}
/// Sets whether the context should enable delegated credentials.
#[corresponds(SSL_CTX_set_delegated_credentials)]
pub fn set_delegated_credentials(&mut self, sigalgs: &str) -> Result<(), ErrorStack> {
let sigalgs = CString::new(sigalgs).unwrap();
unsafe {
cvt(ffi::SSL_CTX_set_delegated_credentials(self.as_ptr(), sigalgs.as_ptr()) as c_int)
.map(|_| ())
}
}
/// Sets whether the context should enable there key share extension.
#[corresponds(SSL_CTX_set_key_shares_limit)]
pub fn set_key_shares_limit(&mut self, limit: u8) {
unsafe { ffi::SSL_CTX_set_key_shares_limit(self.as_ptr(), limit as _) }
}
/// Sets whether the aes hardware override should be enabled.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_CTX_set_aes_hw_override)]
pub fn set_aes_hw_override(&mut self, enable: bool) {
unsafe { ffi::SSL_CTX_set_aes_hw_override(self.as_ptr(), enable as _) }
}
/// Sets whether to preserve the TLS 1.3 cipher list as configured by [`Self::set_cipher_list`].
///
/// By default, BoringSSL does not preserve the TLS 1.3 cipher list. When this option is disabled
/// (the default), BoringSSL uses its internal default TLS 1.3 cipher suites in its default order,
/// regardless of what is set via [`Self::set_cipher_list`].
///
/// When enabled, this option ensures that the TLS 1.3 cipher suites explicitly set via
/// [`Self::set_cipher_list`] are retained in their original order, without being reordered or
/// modified by BoringSSL's internal logic. This is useful for maintaining specific cipher suite
/// priorities for TLS 1.3. Note that if [`Self::set_cipher_list`] does not include any TLS 1.3
/// cipher suites, BoringSSL will still fall back to its default TLS 1.3 cipher suites and order.
///
/// This feature isn't available in the certified version of BoringSSL.
///
/// # Note
///
/// This method must be called **before** [`Self::set_cipher_list`] to take effect.
/// If called after [`Self::set_cipher_list`], the setting will be ignored.
///
/// [`Self::set_cipher_list`]: #method.set_cipher_list
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_CTX_set_preserve_tls13_cipher_list)]
pub fn set_preserve_tls13_cipher_list(&mut self, enable: bool) {
unsafe { ffi::SSL_CTX_set_preserve_tls13_cipher_list(self.as_ptr(), enable as _) }
}
/// Sets the indices of the extensions to be permuted.
#[corresponds(SSL_CTX_set_extension_order)]
pub fn set_extension_permutation(
&mut self,
indices: &[ExtensionType],
) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::SSL_CTX_set_extension_order(
self.as_ptr(),
indices.as_ptr() as *const _,
indices.len() as _,
))
.map(|_| ())
}
}
/// Configures whether ClientHello extensions should be permuted.
#[corresponds(SSL_CTX_set_permute_extensions)]
pub fn set_permute_extensions(&mut self, enabled: bool) {
@ -2152,9 +2108,6 @@ impl SslContextRef {
#[corresponds(SSL_CTX_get0_certificate)]
#[must_use]
pub fn certificate(&self) -> Option<&X509Ref> {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk(), "This API is not supported for RPK");
unsafe {
let ptr = ffi::SSL_CTX_get0_certificate(self.as_ptr());
if ptr.is_null() {
@ -2183,9 +2136,6 @@ impl SslContextRef {
#[corresponds(SSL_CTX_get_cert_store)]
#[must_use]
pub fn cert_store(&self) -> &X509StoreRef {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk(), "This API is not supported for RPK");
unsafe { X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) }
}
@ -2294,19 +2244,10 @@ impl SslContextRef {
#[corresponds(SSL_CTX_get_verify_mode)]
#[must_use]
pub fn verify_mode(&self) -> SslVerifyMode {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk(), "This API is not supported for RPK");
let mode = unsafe { ffi::SSL_CTX_get_verify_mode(self.as_ptr()) };
SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode")
}
/// Returns `true` if context was created for Raw Public Key verification
#[cfg(feature = "rpk")]
pub fn is_rpk(&self) -> bool {
self.ex_data(*RPK_FLAG_INDEX).copied().unwrap_or_default()
}
/// Registers a list of ECH keys on the context. This list should contain new and old
/// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function
/// is safe to call even after the `SSL_CTX` has been associated with connections on various
@ -2773,21 +2714,6 @@ impl Ssl {
where
S: Read + Write,
{
#[cfg(feature = "rpk")]
{
let ctx = self.ssl_context();
if ctx.is_rpk() {
unsafe {
ffi::SSL_CTX_set_custom_verify(
ctx.as_ptr(),
SslVerifyMode::PEER.bits(),
Some(rpk_verify_failure_callback),
);
}
}
}
SslStreamBuilder::new(self, stream).setup_accept()
}
@ -2816,12 +2742,6 @@ impl fmt::Debug for SslRef {
builder.field("state", &self.state_string_long());
#[cfg(feature = "rpk")]
if !self.ssl_context().is_rpk() {
builder.field("verify_result", &self.verify_result());
}
#[cfg(not(feature = "rpk"))]
builder.field("verify_result", &self.verify_result());
builder.finish()
@ -2903,12 +2823,6 @@ impl SslRef {
/// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify
#[corresponds(SSL_set_verify)]
pub fn set_verify(&mut self, mode: SslVerifyMode) {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) }
}
@ -2917,12 +2831,6 @@ impl SslRef {
/// If the peer's certificate chain is longer than this value, verification will fail.
#[corresponds(SSL_set_verify_depth)]
pub fn set_verify_depth(&mut self, depth: u32) {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe {
ffi::SSL_set_verify_depth(self.as_ptr(), depth as c_int);
}
@ -2932,12 +2840,6 @@ impl SslRef {
#[corresponds(SSL_get_verify_mode)]
#[must_use]
pub fn verify_mode(&self) -> SslVerifyMode {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
let mode = unsafe { ffi::SSL_get_verify_mode(self.as_ptr()) };
SslVerifyMode::from_bits(mode).expect("SSL_get_verify_mode returned invalid mode")
}
@ -2963,12 +2865,6 @@ impl SslRef {
where
F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send,
{
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe {
// this needs to be in an Arc since the callback can register a new callback!
self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback));
@ -2983,12 +2879,6 @@ impl SslRef {
/// Sets a custom certificate store for verifying peer certificates.
#[corresponds(SSL_set0_verify_cert_store)]
pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe {
cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int)?;
Ok(())
@ -3005,12 +2895,6 @@ impl SslRef {
where
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
{
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe {
// this needs to be in an Arc since the callback can register a new callback!
self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback));
@ -3140,12 +3024,6 @@ impl SslRef {
#[corresponds(SSL_get_peer_certificate)]
#[must_use]
pub fn peer_certificate(&self) -> Option<X509> {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe {
let ptr = ffi::SSL_get_peer_certificate(self.as_ptr());
if ptr.is_null() {
@ -3163,12 +3041,6 @@ impl SslRef {
#[corresponds(SSL_get_peer_certificate)]
#[must_use]
pub fn peer_cert_chain(&self) -> Option<&StackRef<X509>> {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe {
let ptr = ffi::SSL_get_peer_cert_chain(self.as_ptr());
if ptr.is_null() {
@ -3183,12 +3055,6 @@ impl SslRef {
#[corresponds(SSL_get_certificate)]
#[must_use]
pub fn certificate(&self) -> Option<&X509Ref> {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe {
let ptr = ffi::SSL_get_certificate(self.as_ptr());
if ptr.is_null() {
@ -3442,12 +3308,6 @@ impl SslRef {
/// Returns a mutable reference to the X509 verification configuration.
#[corresponds(SSL_get0_param)]
pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) }
}
@ -3459,12 +3319,6 @@ impl SslRef {
/// Returns the certificate verification result.
#[corresponds(SSL_get_verify_result)]
pub fn verify_result(&self) -> X509VerifyResult {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe { X509VerifyError::from_raw(ffi::SSL_get_verify_result(self.as_ptr()) as c_int) }
}
@ -3719,12 +3573,6 @@ impl SslRef {
/// as trusted by this method.
#[corresponds(SSL_set_client_CA_list)]
pub fn set_client_ca_list(&mut self, list: Stack<X509Name>) {
#[cfg(feature = "rpk")]
assert!(
!self.ssl_context().is_rpk(),
"This API is not supported for RPK"
);
unsafe { ffi::SSL_set_client_CA_list(self.as_ptr(), list.as_ptr()) }
mem::forget(list);
}

View File

@ -1118,3 +1118,84 @@ fn test_ssl_set_compliance() {
ssl.set_compliance_policy(CompliancePolicy::NONE)
.expect_err("Testing expect err if set compliance policy to NONE");
}
#[test]
fn set_extension_permutation() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
// firefox extension permutation
ctx.set_extension_permutation(&[
ExtensionType::SERVER_NAME,
ExtensionType::EXTENDED_MASTER_SECRET,
ExtensionType::RENEGOTIATE,
ExtensionType::SUPPORTED_GROUPS,
ExtensionType::EC_POINT_FORMATS,
ExtensionType::SESSION_TICKET,
ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION,
ExtensionType::STATUS_REQUEST,
ExtensionType::DELEGATED_CREDENTIAL,
ExtensionType::CERTIFICATE_TIMESTAMP,
ExtensionType::KEY_SHARE,
ExtensionType::SUPPORTED_VERSIONS,
ExtensionType::SIGNATURE_ALGORITHMS,
ExtensionType::PSK_KEY_EXCHANGE_MODES,
ExtensionType::RECORD_SIZE_LIMIT,
ExtensionType::CERT_COMPRESSION,
ExtensionType::ENCRYPTED_CLIENT_HELLO,
])
.unwrap();
}
#[test]
fn test_connect_with_set_extension_permutation_client_ctx() {
let server = Server::builder();
let server = server.build();
let mut client = server.client();
// firefox extension permutation
client
.ctx()
.set_extension_permutation(&[
ExtensionType::SERVER_NAME,
ExtensionType::EXTENDED_MASTER_SECRET,
ExtensionType::RENEGOTIATE,
ExtensionType::SUPPORTED_GROUPS,
ExtensionType::EC_POINT_FORMATS,
ExtensionType::SESSION_TICKET,
ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION,
ExtensionType::STATUS_REQUEST,
ExtensionType::DELEGATED_CREDENTIAL,
ExtensionType::CERTIFICATE_TIMESTAMP,
ExtensionType::KEY_SHARE,
ExtensionType::SUPPORTED_VERSIONS,
ExtensionType::SIGNATURE_ALGORITHMS,
ExtensionType::PSK_KEY_EXCHANGE_MODES,
ExtensionType::RECORD_SIZE_LIMIT,
ExtensionType::CERT_COMPRESSION,
ExtensionType::ENCRYPTED_CLIENT_HELLO,
])
.unwrap();
let _ = client.connect();
}
#[test]
fn test_connect_with_set_extension_permutation_empty_client_ctx() {
let server = Server::builder();
let server = server.build();
let mut client = server.client();
client.ctx().set_extension_permutation(&[]).unwrap();
let _ = client.connect();
}
#[test]
fn test_connect_with_set_extension_permutation_server_name_client_ctx() {
let server = Server::builder();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_extension_permutation(&[ExtensionType::SERVER_NAME])
.unwrap();
let _ = client.connect();
}

View File

@ -5,7 +5,7 @@
//! Encrypt data in AES128 CBC mode
//!
//! ```
//! use boring::symm::{encrypt, Cipher};
//! use boring2::symm::{encrypt, Cipher};
//!
//! let cipher = Cipher::aes_128_cbc();
//! let data = b"Some Crypto Text";
@ -26,8 +26,8 @@
//! Encrypting an asymmetric key with a symmetric cipher
//!
//! ```
//! use boring::rsa::{Padding, Rsa};
//! use boring::symm::Cipher;
//! use boring2::rsa::{Padding, Rsa};
//! use boring2::symm::Cipher;
//!
//! // Generate keypair and encrypt private key:
//! let keypair = Rsa::generate(2048).unwrap();
@ -318,7 +318,7 @@ unsafe impl Send for Cipher {}
/// CBC mode.
///
/// ```
/// use boring::symm::{Cipher, Mode, Crypter};
/// use boring2::symm::{Cipher, Mode, Crypter};
///
/// let plaintexts: [&[u8]; 2] = [b"Some Stream of", b" Crypto Text"];
/// let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
@ -642,7 +642,7 @@ impl Drop for Crypter {
/// Encrypt data in AES128 CBC mode
///
/// ```
/// use boring::symm::{encrypt, Cipher};
/// use boring2::symm::{encrypt, Cipher};
///
/// let cipher = Cipher::aes_128_cbc();
/// let data = b"Some Crypto Text";
@ -681,7 +681,7 @@ pub fn encrypt(
/// Decrypt data in AES128 CBC mode
///
/// ```
/// use boring::symm::{decrypt, Cipher};
/// use boring2::symm::{decrypt, Cipher};
///
/// let cipher = Cipher::aes_128_cbc();
/// let data = b"\xB4\xB9\xE7\x30\xD6\xD6\xF7\xDE\x77\x3F\x1C\xFF\xB3\x3E\x44\x5A\x91\xD7\x27\x62\

View File

@ -8,8 +8,8 @@
//! # Example
//!
//! ```rust
//! use boring::x509::extension::BasicConstraints;
//! use boring::x509::X509Extension;
//! use boring2::x509::extension::BasicConstraints;
//! use boring2::x509::X509Extension;
//!
//! let mut bc = BasicConstraints::new();
//! let bc = bc.critical().ca().pathlen(1);

View File

@ -416,16 +416,16 @@ impl X509Builder {
/// The `CN` field is used for the common name, such as a DNS name.
///
/// ```
/// use boring::x509::{X509, X509NameBuilder};
/// use boring2::x509::{X509, X509NameBuilder};
///
/// let mut x509_name = boring::x509::X509NameBuilder::new().unwrap();
/// let mut x509_name = boring2::x509::X509NameBuilder::new().unwrap();
/// x509_name.append_entry_by_text("C", "US").unwrap();
/// x509_name.append_entry_by_text("ST", "CA").unwrap();
/// x509_name.append_entry_by_text("O", "Some organization").unwrap();
/// x509_name.append_entry_by_text("CN", "www.example.com").unwrap();
/// let x509_name = x509_name.build();
///
/// let mut x509 = boring::x509::X509::builder().unwrap();
/// let mut x509 = boring2::x509::X509::builder().unwrap();
/// x509.set_subject_name(&x509_name).unwrap();
/// ```
#[corresponds(X509_set_subject_name)]

View File

@ -6,13 +6,13 @@
//! # Example
//!
//! ```rust
//! use boring::x509::store::{X509StoreBuilder, X509Store};
//! use boring::x509::{X509, X509Name};
//! use boring::asn1::Asn1Time;
//! use boring::pkey::PKey;
//! use boring::hash::MessageDigest;
//! use boring::rsa::Rsa;
//! use boring::nid::Nid;
//! use boring2::x509::store::{X509StoreBuilder, X509Store};
//! use boring2::x509::{X509, X509Name};
//! use boring2::asn1::Asn1Time;
//! use boring2::pkey::PKey;
//! use boring2::hash::MessageDigest;
//! use boring2::rsa::Rsa;
//! use boring2::nid::Nid;
//!
//! let rsa = Rsa::generate(2048).unwrap();
//! let pkey = PKey::from_rsa(rsa).unwrap();

30
compio-boring/Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
name = "compio-boring2"
version = { workspace = true }
authors = ["0x676e67 <gngppz@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = { workspace = true }
repository = { workspace = true }
homepage = "https://github.com/0x676e67/boring2"
documentation = "https://docs.rs/compio-boring2"
description = """
An implementation of SSL streams for Compio backed by BoringSSL
"""
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
[features]
# Use a FIPS-validated version of boringssl.
fips = ["boring/fips", "boring-sys/fips"]
[dependencies]
boring = { workspace = true }
boring-sys = { workspace = true }
compio = { workspace = true }
compio-io = { workspace = true, features = ["compat"]}
[dev-dependencies]
futures = { workspace = true }
compio = { workspace = true, features = [ "macros"] }
anyhow = { workspace = true }

View File

@ -186,7 +186,8 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2016 Tokio contributors
Copyright 2025 0x676e67 <gngppz@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

27
compio-boring/LICENSE-MIT Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2016 Tokio contributors
Copyright (c) 2020 Ivan Nikulin <ifaaan@gmail.com>
Copyright (c) 2025 0x676e67 <gngppz@gmail.com>
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

63
compio-boring/README.md Normal file
View File

@ -0,0 +1,63 @@
# compio-boring2
An implementation of SSL streams for Compio built on top of the BoringSSL.
[Documentation](https://docs.rs/compio-boring2)
## Usage
First, add this to your `Cargo.toml`:
```toml
[dependencies]
compio-boring2 = "5.0.0-alpha.4"
```
Then, use either `accept` or `connect` as appropriate.
```rust
use boring::ssl;
use compio::net::TcpListener;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
let (tcp_stream, _addr) = listener.accept().await?;
let server = ssl::SslMethod::tls_server();
let mut ssl_builder = boring2::ssl::SslAcceptor::mozilla_modern(server)?;
ssl_builder.set_default_verify_paths()?;
ssl_builder.set_verify(ssl::SslVerifyMode::PEER);
let acceptor = ssl_builder.build();
let _ssl_stream = compio_boring2::accept(&acceptor, tcp_stream).await?;
Ok(())
}
```
This library is an implementation of TLS streams using BoringSSL for
negotiating the connection. Each TLS stream implements the `Read` and
`Write` traits to interact and interoperate with the rest of the futures I/O
ecosystem. Client connections initiated from this crate verify hostnames
automatically and by default.
`compio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
be used by servers, and `connect` by clients. These augment the functionality provided by the
[`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
primarily done through the [`boring2`] crate.
# License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

View File

@ -0,0 +1,77 @@
use boring::ssl::{
AsyncPrivateKeyMethod, AsyncSelectCertError, BoxGetSessionFuture, BoxSelectCertFuture,
ClientHello, SslContextBuilder, SslRef,
};
/// Extensions to [`SslContextBuilder`].
///
/// This trait provides additional methods to use async callbacks with boring.
pub trait SslContextBuilderExt: private::Sealed {
/// Sets a callback that is called before most [`ClientHello`] processing
/// and before the decision whether to resume a session is made. The
/// callback may inspect the [`ClientHello`] and configure the connection.
///
/// This method uses a function that returns a future whose output is
/// itself a closure that will be passed [`ClientHello`] to configure
/// the connection based on the computations done in the future.
///
/// See [`SslContextBuilder::set_select_certificate_callback`] for the sync
/// setter of this callback.
fn set_async_select_certificate_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ClientHello<'_>) -> Result<BoxSelectCertFuture, AsyncSelectCertError>
+ Send
+ Sync
+ 'static;
/// Configures a custom private key method on the context.
///
/// See [`AsyncPrivateKeyMethod`] for more details.
fn set_async_private_key_method(&mut self, method: impl AsyncPrivateKeyMethod);
/// Sets a callback that is called when a client proposed to resume a session
/// but it was not found in the internal cache.
///
/// The callback is passed a reference to the session ID provided by the client.
/// It should return the session corresponding to that ID if available. This is
/// only used for servers, not clients.
///
/// See [`SslContextBuilder::set_get_session_callback`] for the sync setter
/// of this callback.
///
/// # Safety
///
/// The returned [`SslSession`] must not be associated with a different [`SslContext`].
unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static;
}
impl SslContextBuilderExt for SslContextBuilder {
fn set_async_select_certificate_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ClientHello<'_>) -> Result<BoxSelectCertFuture, AsyncSelectCertError>
+ Send
+ Sync
+ 'static,
{
self.set_async_select_certificate_callback(callback);
}
fn set_async_private_key_method(&mut self, method: impl AsyncPrivateKeyMethod) {
self.set_async_private_key_method(method);
}
unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static,
{
self.set_async_get_session_callback(callback);
}
}
mod private {
pub trait Sealed {}
}
impl private::Sealed for SslContextBuilder {}

329
compio-boring/src/lib.rs Normal file
View File

@ -0,0 +1,329 @@
//! Async TLS streams backed by BoringSSL
//!
//! This library is an implementation of TLS streams using BoringSSL for
//! negotiating the connection. Each TLS stream implements the `Read` and
//! `Write` traits to interact and interoperate with the rest of the futures I/O
//! ecosystem. Client connections initiated from this crate verify hostnames
//! automatically and by default.
//!
//! `compio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
//! be used by servers, and `connect` by clients. These augment the functionality provided by the
//! [`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
//! primarily done through the [`boring2`] crate.
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod async_callbacks;
use boring::ssl::{self, ConnectConfiguration, ErrorCode, SslAcceptor, SslRef};
use boring_sys as ffi;
use compio::buf::{IoBuf, IoBufMut};
use compio::io::{AsyncRead, AsyncWrite};
use compio::BufResult;
use compio_io::compat::SyncStream;
use std::error::Error;
use std::fmt;
use std::io;
use std::mem::MaybeUninit;
pub use crate::async_callbacks::SslContextBuilderExt;
pub use boring::ssl::{
AsyncPrivateKeyMethod, AsyncPrivateKeyMethodError, AsyncSelectCertError, BoxGetSessionFinish,
BoxGetSessionFuture, BoxPrivateKeyMethodFinish, BoxPrivateKeyMethodFuture, BoxSelectCertFinish,
BoxSelectCertFuture, ExDataFuture,
};
/// Asynchronously performs a client-side TLS handshake over the provided stream.
///
/// This function automatically sets the task waker on the `Ssl` from `config` to
/// allow to make use of async callbacks provided by the boring crate.
pub async fn connect<S>(
config: ConnectConfiguration,
domain: &str,
stream: S,
) -> Result<SslStream<S>, HandshakeError<S>>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let res = config.connect(domain, SyncStream::new(stream));
perform_tls_handshake(res).await
}
/// Asynchronously performs a server-side TLS handshake over the provided stream.
///
/// This function automatically sets the task waker on the `Ssl` from `config` to
/// allow to make use of async callbacks provided by the boring crate.
pub async fn accept<S>(acceptor: &SslAcceptor, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let res = acceptor.accept(SyncStream::new(stream));
perform_tls_handshake(res).await
}
/// A partially constructed `SslStream`, useful for unusual handshakes.
pub struct SslStreamBuilder<S> {
inner: ssl::SslStreamBuilder<SyncStream<S>>,
}
impl<S> SslStreamBuilder<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
/// Begins creating an `SslStream` atop `stream`.
pub fn new(ssl: ssl::Ssl, stream: S) -> Self {
Self {
inner: ssl::SslStreamBuilder::new(ssl, SyncStream::new(stream)),
}
}
/// Initiates a client-side TLS handshake.
pub async fn accept(self) -> Result<SslStream<S>, HandshakeError<S>> {
let res = self.inner.connect();
perform_tls_handshake(res).await
}
/// Initiates a server-side TLS handshake.
pub async fn connect(self) -> Result<SslStream<S>, HandshakeError<S>> {
let res = self.inner.connect();
perform_tls_handshake(res).await
}
}
impl<S> SslStreamBuilder<S> {
/// Returns a shared reference to the `Ssl` object associated with this builder.
#[must_use]
pub fn ssl(&self) -> &SslRef {
self.inner.ssl()
}
/// Returns a mutable reference to the `Ssl` object associated with this builder.
pub fn ssl_mut(&mut self) -> &mut SslRef {
self.inner.ssl_mut()
}
}
/// A wrapper around an underlying raw stream which implements the SSL
/// protocol.
///
/// A `SslStream<S>` represents a handshake that has been completed successfully
/// and both the server and the client are ready for receiving and sending
/// data. Bytes read from a `SslStream` are decrypted from `S` and bytes written
/// to a `SslStream` are encrypted when passing through to `S`.
#[derive(Debug)]
pub struct SslStream<S>(ssl::SslStream<SyncStream<S>>);
impl<S> SslStream<S> {
/// Returns a shared reference to the `Ssl` object associated with this stream.
#[must_use]
pub fn ssl(&self) -> &SslRef {
self.0.ssl()
}
/// Returns a mutable reference to the `Ssl` object associated with this stream.
pub fn ssl_mut(&mut self) -> &mut SslRef {
self.0.ssl_mut()
}
/// Returns a shared reference to the underlying stream.
#[must_use]
pub fn get_ref(&self) -> &S {
self.0.get_ref().get_ref()
}
/// Returns a mutable reference to the underlying stream.
pub fn get_mut(&mut self) -> &mut S {
self.0.get_mut().get_mut()
}
}
impl<S> SslStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
/// Constructs an `SslStream` from a pointer to the underlying OpenSSL `SSL` struct.
///
/// This is useful if the handshake has already been completed elsewhere.
///
/// # Safety
///
/// The caller must ensure the pointer is valid.
pub unsafe fn from_raw_parts(ssl: *mut ffi::SSL, stream: S) -> Self {
Self(ssl::SslStream::from_raw_parts(ssl, SyncStream::new(stream)))
}
}
impl<S: AsyncRead> AsyncRead for SslStream<S> {
async fn read<B: IoBufMut>(&mut self, mut buf: B) -> BufResult<usize, B> {
let slice = buf.as_mut_slice();
let mut f = {
slice.fill(MaybeUninit::new(0));
// SAFETY: The memory has been initialized
let slice =
unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), slice.len()) };
|s: &mut _| std::io::Read::read(s, slice)
};
loop {
match f(&mut self.0) {
Ok(res) => {
unsafe { buf.set_buf_init(res) };
return BufResult(Ok(res), buf);
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
match self.0.get_mut().fill_read_buf().await {
Ok(_) => continue,
Err(e) => return BufResult(Err(e), buf),
}
}
res => return BufResult(res, buf),
}
}
}
}
/// `AsyncRead` is needed for shutting down stream.
impl<S: AsyncWrite + AsyncRead> AsyncWrite for SslStream<S> {
async fn write<T: IoBuf>(&mut self, buf: T) -> BufResult<usize, T> {
let slice = buf.as_slice();
loop {
let res = io::Write::write(&mut self.0, slice);
match res {
Err(e) if e.kind() == io::ErrorKind::WouldBlock => match self.flush().await {
Ok(_) => continue,
Err(e) => return BufResult(Err(e), buf),
},
_ => return BufResult(res, buf),
}
}
}
async fn flush(&mut self) -> io::Result<()> {
loop {
match io::Write::flush(&mut self.0) {
Ok(()) => break,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
self.0.get_mut().flush_write_buf().await?;
}
Err(e) => return Err(e),
}
}
self.0.get_mut().flush_write_buf().await?;
Ok(())
}
async fn shutdown(&mut self) -> io::Result<()> {
self.flush().await?;
self.0.get_mut().get_mut().shutdown().await
}
}
/// The error type returned after a failed handshake.
pub enum HandshakeError<S> {
/// An error that occurred during the handshake.
Inner(ssl::HandshakeError<SyncStream<S>>),
/// An I/O error that occurred during the handshake.
Io(io::Error),
}
impl<S> HandshakeError<S> {
/// Returns a shared reference to the `Ssl` object associated with this error.
#[must_use]
pub fn ssl(&self) -> Option<&SslRef> {
match self {
HandshakeError::Inner(ssl::HandshakeError::Failure(s)) => Some(s.ssl()),
_ => None,
}
}
/// Returns the error code, if any.
#[must_use]
pub fn code(&self) -> Option<ErrorCode> {
match self {
HandshakeError::Inner(ssl::HandshakeError::Failure(s)) => Some(s.error().code()),
_ => None,
}
}
/// Returns a reference to the inner I/O error, if any.
#[must_use]
pub fn as_io_error(&self) -> Option<&io::Error> {
match self {
HandshakeError::Inner(ssl::HandshakeError::Failure(s)) => s.error().io_error(),
HandshakeError::Io(e) => Some(e),
_ => None,
}
}
}
impl<S> fmt::Debug for HandshakeError<S>
where
S: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HandshakeError::Inner(e) => fmt::Debug::fmt(e, fmt),
HandshakeError::Io(e) => fmt::Debug::fmt(e, fmt),
}
}
}
impl<S> fmt::Display for HandshakeError<S> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HandshakeError::Inner(e) => fmt::Display::fmt(e, fmt),
HandshakeError::Io(e) => fmt::Display::fmt(e, fmt),
}
}
}
impl<S> Error for HandshakeError<S>
where
S: fmt::Debug,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
HandshakeError::Inner(e) => e.source(),
HandshakeError::Io(e) => Some(e),
}
}
}
async fn perform_tls_handshake<S: AsyncRead + AsyncWrite>(
mut res: Result<ssl::SslStream<SyncStream<S>>, ssl::HandshakeError<SyncStream<S>>>,
) -> Result<SslStream<S>, HandshakeError<S>> {
loop {
match res {
Ok(mut s) => {
s.get_mut()
.flush_write_buf()
.await
.map_err(HandshakeError::Io)?;
return Ok(SslStream(s));
}
Err(e) => match e {
ssl::HandshakeError::Failure(_) => return Err(HandshakeError::Inner(e)),
ssl::HandshakeError::SetupFailure(_) => {
return Err(HandshakeError::Inner(e));
}
ssl::HandshakeError::WouldBlock(mut mid_stream) => {
if mid_stream
.get_mut()
.flush_write_buf()
.await
.map_err(HandshakeError::Io)?
== 0
{
mid_stream
.get_mut()
.fill_read_buf()
.await
.map_err(HandshakeError::Io)?;
}
res = mid_stream.handshake();
}
},
}
}
}

View File

@ -0,0 +1,32 @@
use boring::ssl::{SslConnector, SslMethod};
use compio::io::AsyncReadExt;
use compio::net::TcpStream;
use compio_io::AsyncWrite;
use std::net::ToSocketAddrs;
#[compio::test]
async fn google() {
let addr = "google.com:443".to_socket_addrs().unwrap().next().unwrap();
let stream = TcpStream::connect(&addr).await.unwrap();
let config = SslConnector::builder(SslMethod::tls())
.unwrap()
.build()
.configure()
.unwrap();
let mut stream = compio_boring2::connect(config, "google.com", stream)
.await
.unwrap();
stream.write(b"GET / HTTP/1.0\r\n\r\n").await.unwrap();
stream.flush().await.unwrap();
let (_, buf) = stream.read_to_end(vec![]).await.unwrap();
stream.shutdown().await.unwrap();
let response = String::from_utf8_lossy(&buf);
let response = response.trim_end();
// any response code is fine
assert!(response.starts_with("HTTP/1.0 "));
assert!(response.ends_with("</html>") || response.ends_with("</HTML>"));
}

View File

@ -1,5 +0,0 @@
target/
Cargo.lock
.vscode/
.idea/
*.iml

View File

@ -1,52 +0,0 @@
[package]
name = "hyper-boring"
version = { workspace = true }
authors = ["Steven Fackler <sfackler@gmail.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
edition = { workspace = true }
description = "Hyper TLS support via BoringSSL"
license = "MIT OR Apache-2.0"
repository = { workspace = true }
documentation = "https://docs.rs/hyper-boring"
readme = "README.md"
exclude = ["test/*"]
rust-version = "1.80"
[package.metadata.docs.rs]
features = ["pq-experimental"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
# Use a FIPS-validated version of boringssl.
fips = ["boring/fips", "tokio-boring/fips"]
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
pq-experimental = ["tokio-boring/pq-experimental"]
[dependencies]
antidote = { workspace = true }
http = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true, features = ["client", "client-legacy"] }
linked_hash_set = { workspace = true }
boring = { workspace = true }
tokio = { workspace = true }
tokio-boring = { workspace = true }
tower-layer = { workspace = true }
tower-service = { workspace = true }
[dev-dependencies]
bytes = { workspace = true }
http-body-util = { workspace = true }
hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] }
hyper = { workspace = true, features = ["server"] }
tokio = { workspace = true, features = [ "full" ] }
tower = { workspace = true, features = ["util"] }
futures = { workspace = true }
[package.metadata.release]
pre-release-hook = [
"git-cliff",
"--workdir", "..",
"-o", "../RELEASE_NOTES",
"--tag", "{{version}}"
]

View File

@ -1,23 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-2016 Steven Fackler
Copyright (c) 2020 Ivan Nikulin <ifaaan@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,25 +0,0 @@
# hyper-boring
[Documentation](https://docs.rs/hyper-boring)
Hyper SSL support via BoringSSL.
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.
## Accolades
The project is based on a fork of [hyper-openssl](https://github.com/sfackler/hyper-openssl).

View File

@ -1,101 +0,0 @@
use boring::ssl::SslVersion;
use boring::ssl::{SslSession, SslSessionRef};
use linked_hash_set::LinkedHashSet;
use std::borrow::Borrow;
use std::collections::hash_map::{Entry, HashMap};
use std::hash::{Hash, Hasher};
#[derive(Hash, PartialEq, Eq, Clone)]
pub struct SessionKey {
pub host: String,
pub port: u16,
}
#[derive(Clone)]
struct HashSession(SslSession);
impl PartialEq for HashSession {
fn eq(&self, other: &HashSession) -> bool {
self.0.id() == other.0.id()
}
}
impl Eq for HashSession {}
impl Hash for HashSession {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.0.id().hash(state);
}
}
impl Borrow<[u8]> for HashSession {
fn borrow(&self) -> &[u8] {
self.0.id()
}
}
pub struct SessionCache {
sessions: HashMap<SessionKey, LinkedHashSet<HashSession>>,
reverse: HashMap<HashSession, SessionKey>,
/// Maximum capacity of LinkedHashSet per SessionKey
per_key_session_capacity: usize,
}
impl SessionCache {
pub fn with_capacity(per_key_session_capacity: usize) -> SessionCache {
SessionCache {
sessions: HashMap::new(),
reverse: HashMap::new(),
per_key_session_capacity,
}
}
pub fn insert(&mut self, key: SessionKey, session: SslSession) {
let session = HashSession(session);
let sessions = self.sessions.entry(key.clone()).or_default();
// if sessions exceed capacity, discard oldest
if sessions.len() >= self.per_key_session_capacity {
if let Some(hash) = sessions.pop_front() {
self.reverse.remove(&hash);
}
}
sessions.insert(session.clone());
self.reverse.insert(session, key);
}
pub fn get(&mut self, key: &SessionKey) -> Option<SslSession> {
let session = {
let sessions = self.sessions.get_mut(key)?;
sessions.front().cloned()?.0
};
// https://tools.ietf.org/html/rfc8446#appendix-C.4
// OpenSSL will remove the session from its cache after the handshake completes anyway, but this ensures
// that concurrent handshakes don't end up with the same session.
if session.protocol_version() == SslVersion::TLS1_3 {
self.remove(&session);
}
Some(session)
}
pub fn remove(&mut self, session: &SslSessionRef) {
let key = match self.reverse.remove(session.id()) {
Some(key) => key,
None => return,
};
if let Entry::Occupied(mut sessions) = self.sessions.entry(key) {
sessions.get_mut().remove(session.id());
if sessions.get().is_empty() {
sessions.remove();
}
}
}
}

View File

@ -1,76 +0,0 @@
//! Hyper SSL support via BoringSSL.
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use crate::cache::SessionKey;
use boring::error::ErrorStack;
use boring::ex_data::Index;
use boring::ssl::Ssl;
use std::fmt;
use std::sync::LazyLock;
use tokio_boring::SslStream;
mod cache;
mod v1;
pub use self::v1::*;
fn key_index() -> Result<Index<Ssl, SessionKey>, ErrorStack> {
static IDX: LazyLock<Index<Ssl, SessionKey>> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
Ok(*IDX)
}
/// Settings for [`HttpsLayer`]
pub struct HttpsLayerSettings {
session_cache_capacity: usize,
}
impl HttpsLayerSettings {
/// Constructs an [`HttpsLayerSettingsBuilder`] for configuring settings
#[must_use]
pub fn builder() -> HttpsLayerSettingsBuilder {
HttpsLayerSettingsBuilder(HttpsLayerSettings::default())
}
}
impl Default for HttpsLayerSettings {
fn default() -> Self {
Self {
session_cache_capacity: 8,
}
}
}
/// Builder for [`HttpsLayerSettings`]
pub struct HttpsLayerSettingsBuilder(HttpsLayerSettings);
impl HttpsLayerSettingsBuilder {
/// Sets maximum number of sessions to cache. Session capacity is per session key (domain).
/// Defaults to 8.
pub fn set_session_cache_capacity(&mut self, capacity: usize) {
self.0.session_cache_capacity = capacity;
}
/// Consumes the builder, returning a new [`HttpsLayerSettings`]
#[must_use]
pub fn build(self) -> HttpsLayerSettings {
self.0
}
}
/// A stream which may be wrapped with TLS.
pub enum MaybeHttpsStream<T> {
/// A raw HTTP stream.
Http(T),
/// An SSL-wrapped HTTP stream.
Https(SslStream<T>),
}
impl<T> fmt::Debug for MaybeHttpsStream<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MaybeHttpsStream::Http(..) => f.pad("Http(..)"),
MaybeHttpsStream::Https(..) => f.pad("Https(..)"),
}
}
}

View File

@ -1,347 +0,0 @@
use crate::cache::{SessionCache, SessionKey};
use crate::{key_index, HttpsLayerSettings, MaybeHttpsStream};
use antidote::Mutex;
use boring::error::ErrorStack;
use boring::ssl::{
ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef,
SslSessionCacheMode,
};
use http::uri::Scheme;
use http::Uri;
use hyper::rt::{Read, ReadBufCursor, Write};
use hyper_util::client::legacy::connect::{Connected, Connection, HttpConnector};
use hyper_util::rt::TokioIo;
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{io, net};
use tokio::io::{AsyncRead, AsyncWrite};
use tower_layer::Layer;
use tower_service::Service;
/// A Connector using BoringSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
inner: Inner,
}
impl HttpsConnector<HttpConnector> {
/// Creates a a new `HttpsConnector` using default settings.
///
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
/// HTTP/2 and HTTP/1.1.
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new();
http.enforce_http(false);
HttpsLayer::new().map(|l| l.layer(http))
}
}
impl<S, T> HttpsConnector<S>
where
S: Service<Uri, Response = TokioIo<T>> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
{
/// Creates a new `HttpsConnector`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(
http: S,
ssl: SslConnectorBuilder,
) -> Result<HttpsConnector<S>, ErrorStack> {
HttpsLayer::with_connector(ssl).map(|l| l.layer(http))
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
/// A layer which wraps services in an `HttpsConnector`.
pub struct HttpsLayer {
inner: Inner,
}
#[derive(Clone)]
struct Inner {
ssl: SslConnector,
cache: Arc<Mutex<SessionCache>>,
callback: Option<Callback>,
ssl_callback: Option<SslCallback>,
}
type Callback =
Arc<dyn Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
type SslCallback = Arc<dyn Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
impl HttpsLayer {
/// Creates a new `HttpsLayer` with default settings.
///
/// ALPN is configured to support both HTTP/1 and HTTP/1.1.
pub fn new() -> Result<HttpsLayer, ErrorStack> {
let mut ssl = SslConnector::builder(SslMethod::tls())?;
ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?;
Self::with_connector(ssl)
}
/// Creates a new `HttpsLayer`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
Self::with_connector_and_settings(ssl, Default::default())
}
/// Creates a new `HttpsLayer` with settings
pub fn with_connector_and_settings(
mut ssl: SslConnectorBuilder,
settings: HttpsLayerSettings,
) -> Result<HttpsLayer, ErrorStack> {
let cache = Arc::new(Mutex::new(SessionCache::with_capacity(
settings.session_cache_capacity,
)));
ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
ssl.set_new_session_callback({
let cache = cache.clone();
move |ssl, session| {
if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) {
cache.lock().insert(key.clone(), session);
}
}
});
Ok(HttpsLayer {
inner: Inner {
ssl: ssl.build(),
cache,
callback: None,
ssl_callback: None,
},
})
}
/// Registers a callback which can customize the configuration of each connection.
///
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
/// instead.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
/// Registers a callback which can customize the `Ssl` of each connection.
pub fn set_ssl_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.ssl_callback = Some(Arc::new(callback));
}
}
impl<S> Layer<S> for HttpsLayer {
type Service = HttpsConnector<S>;
fn layer(&self, inner: S) -> HttpsConnector<S> {
HttpsConnector {
http: inner,
inner: self.inner.clone(),
}
}
}
impl Inner {
fn setup_ssl(&self, uri: &Uri, host: &str) -> Result<Ssl, ErrorStack> {
let mut conf = self.ssl.configure()?;
if let Some(ref callback) = self.callback {
callback(&mut conf, uri)?;
}
let key = SessionKey {
host: host.to_string(),
port: uri.port_u16().unwrap_or(443),
};
if let Some(session) = self.cache.lock().get(&key) {
unsafe {
conf.set_session(&session)?;
}
}
let idx = key_index()?;
conf.set_ex_data(idx, key);
let mut ssl = conf.into_ssl(host)?;
if let Some(ref ssl_callback) = self.ssl_callback {
ssl_callback(&mut ssl, uri)?;
}
Ok(ssl)
}
}
impl<T, S> Service<Uri> for HttpsConnector<S>
where
S: Service<Uri, Response = TokioIo<T>> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
{
type Response = MaybeHttpsStream<T>;
type Error = Box<dyn Error + Sync + Send>;
#[allow(clippy::type_complexity)]
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.http.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, uri: Uri) -> Self::Future {
let is_tls_scheme = uri
.scheme()
.map(|s| s == &Scheme::HTTPS || s.as_str() == "wss")
.unwrap_or(false);
let tls_setup = if is_tls_scheme {
Some((self.inner.clone(), uri.clone()))
} else {
None
};
let connect = self.http.call(uri);
let f = async {
let conn = connect.await.map_err(Into::into)?.into_inner();
let (inner, uri) = match tls_setup {
Some((inner, uri)) => (inner, uri),
None => return Ok(MaybeHttpsStream::Http(conn)),
};
let mut host = uri.host().ok_or("URI missing host")?;
// If `host` is an IPv6 address, we must strip away the square brackets that surround
// it (otherwise, boring will fail to parse the host as an IP address, eventually
// causing the handshake to fail due a hostname verification error).
if !host.is_empty() {
let last = host.len() - 1;
let mut chars = host.chars();
if let (Some('['), Some(']')) = (chars.next(), chars.last()) {
if host[1..last].parse::<net::Ipv6Addr>().is_ok() {
host = &host[1..last];
}
}
}
let ssl = inner.setup_ssl(&uri, host)?;
let stream = tokio_boring::SslStreamBuilder::new(ssl, conn)
.connect()
.await?;
Ok(MaybeHttpsStream::Https(stream))
};
Box::pin(f)
}
}
impl<T> Connection for MaybeHttpsStream<T>
where
T: Connection,
{
fn connected(&self) -> Connected {
match self {
MaybeHttpsStream::Http(s) => s.connected(),
MaybeHttpsStream::Https(s) => {
let mut connected = s.get_ref().connected();
if s.ssl().selected_alpn_protocol() == Some(b"h2") {
connected = connected.negotiated_h2();
}
connected
}
}
}
}
impl<T> Read for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: ReadBufCursor<'_>,
) -> Poll<Result<(), std::io::Error>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_read(cx, buf),
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_read(cx, buf),
}
}
}
impl<T> Write for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_write(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => {
Pin::new(&mut TokioIo::new(inner)).poll_write(ctx, buf)
}
MaybeHttpsStream::Https(inner) => {
Pin::new(&mut TokioIo::new(inner)).poll_write(ctx, buf)
}
}
}
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_flush(ctx),
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_flush(ctx),
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_shutdown(ctx),
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_shutdown(ctx),
}
}
}

View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDGzCCAgMCCQCHcfe97pgvpTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE2MDgxNDE3MDAwM1oXDTI2MDgxMjE3MDAwM1owWjELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKZm9vYmFyLmNvbTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKj0JYxEsxejUIX+I5GH0Hg2G0kX/y1H0+Ub
3mw2/Ja5BD/yN96/7zMSumXF8uS3SkmpyiJkbyD01TSRTqjlP7/VCBlyUIChlpLQ
mrGaijZiT/VCyPXqmcwFzXS5IOTpX1olJfW8rA41U1LCIcDUyFf6LtZ/v8rSeKr6
TuE6SGV4WRaBm1SrjWBeHVV866CRrtSS1ieT2asFsAyOZqWhk2fakwwBDFWDhOGI
ubfO+5aq9cBJbNRlzsgB3UZs3gC0O6GzbnZ6oT0TiJMeTsXXjABLUlaq/rrqFF4Y
euZkkbHTFBMz288PUc3m3ZTcpN+E7+ZOUBRZXKD20K07NugqCzUCAwEAATANBgkq
hkiG9w0BAQsFAAOCAQEASvYHuIl5C0NHBELPpVHNuLbQsDQNKVj3a54+9q1JkiMM
6taEJYfw7K1Xjm4RoiFSHpQBh+PWZS3hToToL2Zx8JfMR5MuAirdPAy1Sia/J/qE
wQdJccqmvuLkLTSlsGbEJ/LUUgOAgrgHOZM5lUgIhCneA0/dWJ3PsN0zvn69/faY
oo1iiolWiIHWWBUSdr3jM2AJaVAsTmLh00cKaDNk37JB940xConBGSl98JPrNrf9
dUAiT0iIBngDBdHnn/yTj+InVEFyZSKrNtiDSObFHxPcxGteHNrCPJdP1e+GqkHp
HJMRZVCQpSMzvHlofHSNgzWV1MX5h1CP4SGZdBDTfA==
-----END CERTIFICATE-----

View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCo9CWMRLMXo1CF
/iORh9B4NhtJF/8tR9PlG95sNvyWuQQ/8jfev+8zErplxfLkt0pJqcoiZG8g9NU0
kU6o5T+/1QgZclCAoZaS0Jqxmoo2Yk/1Qsj16pnMBc10uSDk6V9aJSX1vKwONVNS
wiHA1MhX+i7Wf7/K0niq+k7hOkhleFkWgZtUq41gXh1VfOugka7UktYnk9mrBbAM
jmaloZNn2pMMAQxVg4ThiLm3zvuWqvXASWzUZc7IAd1GbN4AtDuhs252eqE9E4iT
Hk7F14wAS1JWqv666hReGHrmZJGx0xQTM9vPD1HN5t2U3KTfhO/mTlAUWVyg9tCt
OzboKgs1AgMBAAECggEBAKLj6IOJBKXolczpzb8UkyAjAkGBektcseV07gelJ/fk
3z0LuWPv5p12E/HlXB24vU2x/ikUbbP3eMsawRzDEahQqmNmPEkYAYUAy/Qpi9GN
DYvn3LqDec4jVgeQKS+p9H2DzUpTogp8zR2//yzbuWBg2+F//xh7vU0S0RQCziPM
x7RSBgbhxSfChfEJbS2sDnzfh0jRQmoY95iFv7puet1FJtzdZ4fgCd1RqmC2lFM5
H0eZtN/Cz19lieVs0b996DErdEBqClVZO00eYbRozCDaBzRU3ybB/dMrGJxhkkXm
wb3kWMtziH9qOYsostuHIFu8eKFLloKxFnq2R4DGxOECgYEA2KUIZISOeGJSBcLJ
JAUK2gvgXPNo4HHWIwOA9xeN3ZJlsnPlffXQNnm6t1st1V2gfMm9I2n0m/F0y2B/
n/XGSa8bghfPA9l0c2h58lkL3JQJR/paa8ycTz+YZPrznEyN7Qa0RrJXUvZv9lQL
Hc3+FHcSHgMqDV2f2bHAEu9YGi0CgYEAx6VEIPNvrHFgjo/jk1RTuk+m0xEWQsZL
Cs+izQMr2TaeJn8LG+93AvFuYn0J0nT3WuStLPrUg8i4IhSS6lf1tId5ivIZPm4r
YwMyblBJXhnHbk7Uqodjfw/3s6V2HAu++B7hTdyVr9DFuST9uv4m8bkPV8rfX1jE
I2rAPVWvgikCgYB+wNAQP547wQrMZBLbCDg5KwmyWJfb+b6X7czexOEz6humNTjo
YZHYzY/5B1fhpk3ntQD8X1nGg5caBvOk21+QbOtjShrM3cXMYCw5JvBRtitX+Zo9
yBEMLOE0877ki8XeEDYZxu5gk98d+D4oygUGZEQtWxyXhVepPt5qNa8OYQKBgQDH
RVgZI6KFlqzv3wMh3PutbS9wYQ+9GrtwUQuIYe/0YSW9+vSVr5E0qNKrD28sV39F
hBauXLady0yvB6YUrjMbPFW+sCMuQzyfGWPO4+g3OrfqjFiM1ZIkE0YEU9Tt7XNx
qTDtTI1D7bhNMnTnniI1B6ge0und+3XafAThs5L48QKBgQCTTpfqMt8kU3tcI9sf
0MK03y7kA76d5uw0pZbWFy7KI4qnzWutCzb+FMPWWsoFtLJLPZy//u/ZCUVFVa4d
0Y/ASNQIESVPXFLAltlLo4MSmsg1vCBsbviEEaPeEjvMrgki93pYtd/aOSgkYC1T
mEq154s5rmqh+h+XRIf7Au0SLw==
-----END PRIVATE KEY-----

View File

@ -1,21 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G
ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV
eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr
7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92
aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc
klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN
XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn
BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv
Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3
AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy
OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3
mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9
GA==
-----END CERTIFICATE-----

View File

@ -1,158 +0,0 @@
use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod};
use bytes::Bytes;
use futures::StreamExt;
use http_body_util::{BodyStream, Empty};
use hyper::{service, Response};
use hyper_boring::HttpsConnector;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
use hyper_util::rt::{TokioExecutor, TokioIo};
use std::convert::Infallible;
use std::{io, iter};
use tokio::net::TcpListener;
#[tokio::test]
async fn google() {
let ssl = HttpsConnector::new().unwrap();
let client = Client::builder(TokioExecutor::new())
.pool_max_idle_per_host(0)
.build::<_, Empty<Bytes>>(ssl);
for _ in 0..3 {
let resp = client
.get("https://www.google.com".parse().unwrap())
.await
.expect("connection should succeed");
let mut body = BodyStream::new(resp.into_body());
while body.next().await.transpose().unwrap().is_some() {}
}
}
#[tokio::test]
async fn localhost() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let port = addr.port();
let server = async move {
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
acceptor.set_session_id_context(b"test").unwrap();
acceptor
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
.unwrap();
acceptor
.set_certificate_chain_file("tests/test/cert.pem")
.unwrap();
let acceptor = acceptor.build();
for _ in 0..3 {
let stream = listener.accept().await.unwrap().0;
let stream = tokio_boring::accept(&acceptor, stream).await.unwrap();
let service = service::service_fn(|_| async {
Ok::<_, io::Error>(Response::new(<Empty<Bytes>>::new()))
});
hyper::server::conn::http1::Builder::new()
.keep_alive(false)
.serve_connection(TokioIo::new(stream), service)
.await
.unwrap();
}
};
tokio::spawn(server);
let resolver =
tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) });
let mut connector = HttpConnector::new_with_resolver(resolver);
connector.enforce_http(false);
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
use std::fs::File;
use std::io::Write;
let file = File::create("../target/keyfile.log").unwrap();
ssl.set_keylog_callback(move |_, line| {
let _ = writeln!(&file, "{}", line);
});
let ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
let client = Client::builder(TokioExecutor::new()).build::<_, Empty<Bytes>>(ssl);
for _ in 0..3 {
let resp = client
.get(format!("https://foobar.com:{}", port).parse().unwrap())
.await
.unwrap();
assert!(resp.status().is_success(), "{}", resp.status());
let mut body = BodyStream::new(resp.into_body());
while body.next().await.transpose().unwrap().is_some() {}
}
}
#[tokio::test]
async fn alpn_h2() {
use boring::ssl::{self, AlpnError};
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let port = addr.port();
let server = async move {
let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
acceptor
.set_certificate_chain_file("tests/test/cert.pem")
.unwrap();
acceptor
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
.unwrap();
acceptor.set_alpn_select_callback(|_, client| {
ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK)
});
let acceptor = acceptor.build();
let stream = listener.accept().await.unwrap().0;
let stream = tokio_boring::accept(&acceptor, stream).await.unwrap();
assert_eq!(stream.ssl().selected_alpn_protocol().unwrap(), b"h2");
let service = service::service_fn(|_| async {
Ok::<_, io::Error>(Response::new(<Empty<Bytes>>::new()))
});
hyper::server::conn::http2::Builder::new(TokioExecutor::new())
.serve_connection(TokioIo::new(stream), service)
.await
.unwrap();
};
tokio::spawn(server);
let resolver =
tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) });
let mut connector = HttpConnector::new_with_resolver(resolver);
connector.enforce_http(false);
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
let mut ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
ssl.set_ssl_callback(|ssl, _| ssl.set_alpn_protos(b"\x02h2\x08http/1.1"));
let client = Client::builder(TokioExecutor::new()).build::<_, Empty<Bytes>>(ssl);
let resp = client
.get(format!("https://foobar.com:{}", port).parse().unwrap())
.await
.unwrap();
assert!(resp.status().is_success(), "{}", resp.status());
let mut body = BodyStream::new(resp.into_body());
while body.next().await.transpose().unwrap().is_some() {}
}

6
quinn-boring/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
/Cargo.lock
.idea
.DS_Store
.vscode

59
quinn-boring/Cargo.toml Normal file
View File

@ -0,0 +1,59 @@
[package]
name = "quinn-boring2"
version = { workspace = true }
authors = ["0x676e67 <gngppz@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = { workspace = true }
repository = { workspace = true }
description = "BoringSSL crypto provider for quinn"
keywords = ["quic"]
categories = ["network-programming", "asynchronous"]
rust-version = "1.85"
[badges]
maintenance = { status = "passively-maintained" }
[features]
default = ["runtime-tokio"]
runtime-tokio = ["quinn/runtime-tokio"]
[dependencies]
boring = { workspace = true }
boring-sys = { workspace = true }
bytes = "1"
foreign-types-shared = "0.3"
lru = "0.16"
quinn = { version = "0.11.8", default-features = false }
quinn-proto = { version = "0.11.12", default-features = false }
rand = "0.9.2"
tracing = "0.1"
[dev-dependencies]
anyhow = "1.0"
assert_matches = "1"
clap = { version = "4", features = ["derive"] }
directories-next = "2"
hex-literal = "1.0"
rcgen = "0.14"
rustls-pemfile = "2"
tokio = { version = "1", features = [
"rt",
"rt-multi-thread",
"time",
"macros",
"sync",
] }
tracing-subscriber = { version = "0.3", default-features = false, features = [
"fmt",
] }
url = "2"
h3 = "0.0.8"
h3-quinn = "0.0.10"
http = "1"
http-body = "1.0.1"
http-body-util = "0.1.3"
futures-core = "0.3"
[[example]]
name = "client"
required-features = ["runtime-tokio"]

201
quinn-boring/LICENSE-APACHE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

8
quinn-boring/LICENSE-MIT Normal file
View File

@ -0,0 +1,8 @@
Copyright (c) 2018 The quinn Developers
Copyright (c) 2025 0x676e67 <gngppz@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7
quinn-boring/README.md Normal file
View File

@ -0,0 +1,7 @@
# quinn-boring2
A crypto provider for [quinn](https://github.com/quinn-rs/quinn) based on [BoringSSL](https://github.com/google/boringssl).
## Accolades
The project is based on a fork of [quinn-boring](https://github.com/quinn-rs/quinn-boring).

View File

@ -0,0 +1,202 @@
//! This example demonstrates an HTTP client that requests files from a server.
//!
//! Checkout the `README.md` for guidance.
use anyhow::{anyhow, Result};
use boring::x509::X509;
use bytes::BytesMut;
use bytes::{Buf, Bytes};
use clap::Parser;
use http::Uri;
use http_body::Body;
use http_body_util::BodyExt;
use quinn_boring2::QuicSslContext;
use std::{
fs,
net::{SocketAddr, ToSocketAddrs},
path::PathBuf,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Instant,
};
use tracing::Level;
use url::Url;
/// HTTP/0.9 over QUIC client
#[derive(Parser, Debug)]
#[clap(name = "client")]
struct Opt {
/// Log level e.g. trace, debug, info, warn, error
#[clap(long, default_value = "info")]
log: Level,
url: Url,
/// Override hostname used for certificate verification
#[clap(long)]
host: Option<String>,
/// Custom certificate authority to trust, in DER format
#[clap(long)]
ca: Option<PathBuf>,
/// Simulate NAT rebinding after connecting
#[clap(long)]
rebind: bool,
/// Address to bind on
#[clap(long, default_value = "[::]:0")]
bind: SocketAddr,
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let options = Opt::parse();
let url = options.url;
let url_host = strip_ipv6_brackets(url.host_str().unwrap());
let remote = (url_host, url.port().unwrap_or(443))
.to_socket_addrs()?
.next()
.ok_or_else(|| anyhow!("couldn't resolve to an address"))?;
let mut client_crypto = quinn_boring2::ClientConfig::new()?;
if let Some(ca_path) = options.ca {
client_crypto
.ctx_mut()
.cert_store_mut()
.add_cert(X509::from_der(&fs::read(ca_path)?)?)?;
} else {
client_crypto
.ctx_mut()
.cert_store_mut()
.set_default_paths()?;
}
let mut endpoint = quinn_boring2::helpers::client_endpoint(options.bind)?;
endpoint.set_default_client_config(quinn::ClientConfig::new(Arc::new(client_crypto)));
let start = Instant::now();
let rebind = options.rebind;
let host = options.host.as_deref().unwrap_or(url_host);
tracing::info!("connecting to {host} at {remote}");
let conn = endpoint
.connect(remote, host)?
.await
.map_err(|e| anyhow!("failed to connect: {}", e))?;
tracing::info!("connected at {:?}", start.elapsed());
if rebind {
let socket = std::net::UdpSocket::bind("[::]:0").unwrap();
let addr = socket.local_addr().unwrap();
tracing::info!("rebinding to {addr}");
endpoint.rebind(socket).expect("rebind failed");
}
let (mut h3_conn, mut tx) = h3::client::new(h3_quinn::Connection::new(conn)).await?;
let req = http::Request::get(Uri::try_from(url.as_str())?).body(())?;
let (mut send, mut recv) = tx.send_request(req).await?.split();
if let Err(e) = send.finish().await {
tracing::error!("failed to send request: {e}");
}
let resp = {
let resp = recv.recv_response().await?;
let resp_body = Incoming::new(recv, resp.headers())
.map_err(Into::<BoxError>::into)
.boxed();
resp.map(|_| resp_body)
};
tracing::info!("response: {:#?}", resp);
let body = BodyExt::collect(resp.into_body())
.await
.map(|buf| buf.to_bytes())
.map_err(|e| anyhow!("failed to collect response body: {e}"))?;
tracing::info!("response body: {}", String::from_utf8_lossy(&body));
h3_conn.shutdown(0).await?;
Ok(())
}
fn strip_ipv6_brackets(host: &str) -> &str {
// An ipv6 url looks like eg https://[::1]:4433/Cargo.toml, wherein the host [::1] is the
// ipv6 address ::1 wrapped in brackets, per RFC 2732. This strips those.
if host.starts_with('[') && host.ends_with(']') {
&host[1..host.len() - 1]
} else {
host
}
}
type BoxError = Box<dyn std::error::Error + Send + Sync>;
struct Incoming<S, B> {
inner: h3::client::RequestStream<S, B>,
content_length: Option<u64>,
}
impl<S, B> Incoming<S, B> {
fn new(stream: h3::client::RequestStream<S, B>, headers: &http::header::HeaderMap) -> Self {
Self {
inner: stream,
content_length: headers
.get(http::header::CONTENT_LENGTH)
.and_then(|h| h.to_str().ok())
.and_then(|v| v.parse().ok()),
}
}
}
impl<S, B> http_body::Body for Incoming<S, B>
where
S: h3::quic::RecvStream,
{
type Data = Bytes;
type Error = BoxError;
fn poll_frame(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
match futures_core::ready!(self.inner.poll_recv_data(cx)) {
Ok(Some(mut b)) => Poll::Ready(Some(Ok(http_body::Frame::data(
b.copy_to_bytes(b.remaining()),
)))),
Ok(None) => Poll::Ready(None),
Err(e) => Poll::Ready(Some(Err(e.into()))),
}
}
fn size_hint(&self) -> http_body::SizeHint {
if let Some(content_length) = self.content_length {
http_body::SizeHint::with_exact(content_length)
} else {
http_body::SizeHint::default()
}
}
}
pub async fn body_to_string<B>(
mut body: B,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>>
where
B: Body<Data = bytes::Bytes> + Unpin,
B::Error: std::error::Error + Send + Sync + 'static,
{
let mut buf = BytesMut::new();
while let Some(frame) = body.frame().await {
let frame = frame?;
if let Some(data) = frame.data_ref() {
buf.extend_from_slice(data);
}
}
Ok(String::from_utf8(buf.to_vec())?)
}

152
quinn-boring/src/aead.rs Normal file
View File

@ -0,0 +1,152 @@
use crate::error::{map_result, Error, Result};
use crate::key::{Key, Nonce, Tag};
use boring_sys as bffi;
use std::mem::MaybeUninit;
use std::sync::LazyLock;
const AES_128_GCM_KEY_LEN: usize = 16;
const AES_256_GCM_KEY_LEN: usize = 32;
const CHACHA20_POLY1305_KEY_LEN: usize = 32;
const AES_GCM_NONCE_LEN: usize = 12;
const POLY1305_NONCE_LEN: usize = 12;
pub(crate) const AES_GCM_TAG_LEN: usize = 16;
const POLY1305_TAG_LEN: usize = 16;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ID {
Aes128Gcm,
Aes256Gcm,
Chacha20Poly1305,
}
/// Wrapper around a raw BoringSSL EVP_AEAD.
#[derive(Copy, Clone, PartialEq, Eq)]
struct AeadPtr(*const bffi::EVP_AEAD);
unsafe impl Send for AeadPtr {}
unsafe impl Sync for AeadPtr {}
impl AeadPtr {
fn aes128_gcm() -> Self {
unsafe { Self(bffi::EVP_aead_aes_128_gcm()) }
}
fn aes256_gcm() -> Self {
unsafe { Self(bffi::EVP_aead_aes_256_gcm()) }
}
fn chacha20_poly1305() -> Self {
unsafe { Self(bffi::EVP_aead_chacha20_poly1305()) }
}
}
/// Wrapper around an BoringSSL EVP_AEAD.
pub(crate) struct Aead {
ptr: AeadPtr,
pub(crate) id: ID,
pub(crate) key_len: usize,
pub(crate) tag_len: usize,
pub(crate) nonce_len: usize,
}
impl PartialEq for Aead {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Aead {}
static AES128_GCM: LazyLock<Aead> = LazyLock::new(|| Aead {
ptr: AeadPtr::aes128_gcm(),
id: ID::Aes128Gcm,
key_len: AES_128_GCM_KEY_LEN,
tag_len: AES_GCM_TAG_LEN,
nonce_len: AES_GCM_NONCE_LEN,
});
static AES256_GCM: LazyLock<Aead> = LazyLock::new(|| Aead {
ptr: AeadPtr::aes256_gcm(),
id: ID::Aes256Gcm,
key_len: AES_256_GCM_KEY_LEN,
tag_len: AES_GCM_TAG_LEN,
nonce_len: AES_GCM_NONCE_LEN,
});
static CHACHA20_POLY1305: LazyLock<Aead> = LazyLock::new(|| Aead {
ptr: AeadPtr::chacha20_poly1305(),
id: ID::Chacha20Poly1305,
key_len: CHACHA20_POLY1305_KEY_LEN,
tag_len: POLY1305_TAG_LEN,
nonce_len: POLY1305_NONCE_LEN,
});
impl Aead {
#[inline]
pub(crate) fn aes128_gcm() -> &'static Self {
&AES128_GCM
}
#[inline]
pub(crate) fn aes256_gcm() -> &'static Self {
&AES256_GCM
}
#[inline]
pub(crate) fn chacha20_poly1305() -> &'static Self {
&CHACHA20_POLY1305
}
/// Creates a new zeroed key of the appropriate length for the AEAD algorithm.
#[inline]
pub(crate) fn zero_key(&self) -> Key {
Key::with_len(self.key_len)
}
/// Creates a new zeroed nonce of the appropriate length for the AEAD algorithm.
#[inline]
pub(crate) fn zero_nonce(&self) -> Nonce {
Nonce::with_len(self.nonce_len)
}
/// Creates a new zeroed tag of the appropriate length for the AEAD algorithm.
#[inline]
pub(crate) fn zero_tag(&self) -> Tag {
Tag::with_len(self.tag_len)
}
#[inline]
pub(crate) fn as_ptr(&self) -> *const bffi::EVP_AEAD {
self.ptr.0
}
#[inline]
pub(crate) fn new_aead_ctx(&self, key: &Key) -> Result<bffi::EVP_AEAD_CTX> {
if key.len() != self.key_len {
return Err(Error::invalid_input(format!(
"key length invalid for AEAD_CTX: {}",
key.len()
)));
}
let ctx = unsafe {
let mut ctx = MaybeUninit::uninit();
map_result(bffi::EVP_AEAD_CTX_init(
ctx.as_mut_ptr(),
self.as_ptr(),
key.as_ptr(),
key.len(),
self.tag_len,
std::ptr::null_mut(),
))?;
ctx.assume_init()
};
Ok(ctx)
}
}

83
quinn-boring/src/alert.rs Normal file
View File

@ -0,0 +1,83 @@
use boring_sys as bffi;
use quinn_proto::{TransportError, TransportErrorCode};
use std::ffi::{c_int, CStr};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
pub(crate) enum AlertType {
Warning,
Fatal,
Unknown,
}
impl AlertType {
const ALERT_TYPE_WARNING: &'static str = "warning";
const ALERT_TYPE_FATAL: &'static str = "fatal";
const ALERT_TYPE_UNKNOWN: &'static str = "unknown";
}
impl Display for AlertType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Warning => f.write_str(Self::ALERT_TYPE_WARNING),
Self::Fatal => f.write_str(Self::ALERT_TYPE_FATAL),
_ => f.write_str(Self::ALERT_TYPE_UNKNOWN),
}
}
}
impl FromStr for AlertType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
Self::ALERT_TYPE_WARNING => Ok(Self::Warning),
Self::ALERT_TYPE_FATAL => Ok(Self::Fatal),
_ => Ok(Self::Unknown),
}
}
}
#[derive(Copy, Clone)]
pub(crate) struct Alert(u8);
impl Alert {
pub(crate) fn from(value: u8) -> Self {
Alert(value)
}
pub(crate) fn handshake_failure() -> Self {
Alert(bffi::SSL_AD_HANDSHAKE_FAILURE as u8)
}
pub(crate) fn get_description(&self) -> &'static str {
unsafe {
CStr::from_ptr(bffi::SSL_alert_desc_string_long(self.0 as c_int))
.to_str()
.unwrap()
}
}
}
impl Display for Alert {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "SSL alert [{}]: {}", self.0, self.get_description())
}
}
impl From<Alert> for TransportErrorCode {
fn from(alert: Alert) -> Self {
TransportErrorCode::crypto(alert.0)
}
}
impl From<Alert> for TransportError {
fn from(alert: Alert) -> Self {
TransportError {
code: alert.into(),
frame: None,
reason: alert.get_description().to_string(),
}
}
}

73
quinn-boring/src/alpn.rs Normal file
View File

@ -0,0 +1,73 @@
use crate::error::{Error, Result};
#[derive(Clone, Debug)]
pub(crate) struct AlpnProtocol(Vec<u8>);
impl AlpnProtocol {
#[inline]
pub(crate) fn encode(&self, encoded: &mut Vec<u8>) {
encoded.push(self.0.len() as u8);
encoded.extend_from_slice(&self.0);
}
}
impl From<Vec<u8>> for AlpnProtocol {
fn from(value: Vec<u8>) -> Self {
Self(value)
}
}
#[derive(Clone, Debug)]
pub(crate) struct AlpnProtocols(Vec<AlpnProtocol>);
impl AlpnProtocols {
pub(crate) const H3: &'static [u8; 2] = b"h3";
/// Performs the server-side ALPN protocol selection.
pub(crate) fn select<'a>(&self, offered: &'a [u8]) -> Result<&'a [u8]> {
for server_proto in &self.0 {
let mut i = 0;
while i < offered.len() {
let len = offered[i] as usize;
i += 1;
let client_proto = &offered[i..i + len];
if server_proto.0 == client_proto {
return Ok(client_proto);
}
i += len;
}
}
Err(Error::other("ALPN selection failed".into()))
}
pub(crate) fn encode(&self) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
for proto in &self.0 {
proto.encode(&mut out);
}
out
}
}
impl Default for AlpnProtocols {
fn default() -> Self {
Self::from(&[Self::H3.to_vec()][..])
}
}
impl From<&[Vec<u8>]> for AlpnProtocols {
fn from(protos: &[Vec<u8>]) -> Self {
let mut out = Vec::with_capacity(protos.len());
for proto in protos {
out.push(AlpnProtocol(proto.clone()))
}
Self(out)
}
}
impl From<&Vec<Vec<u8>>> for AlpnProtocols {
fn from(protos: &Vec<Vec<u8>>) -> Self {
Self::from(protos.as_slice())
}
}

View File

@ -0,0 +1,565 @@
use crate::error::{br, br_zero_is_success, BoringResult};
use boring::error::ErrorStack;
use boring::pkey::{HasPrivate, PKey};
use boring::ssl::{Ssl, SslContext, SslContextRef, SslSession};
use boring::x509::store::X509StoreBuilderRef;
use boring::x509::X509;
use boring_sys as bffi;
use bytes::{Buf, BufMut};
use foreign_types_shared::{ForeignType, ForeignTypeRef};
use std::ffi::{c_char, c_int, c_uint, c_void, CStr};
use std::fmt::{Display, Formatter};
use std::result::Result as StdResult;
use std::{ffi, fmt, mem, ptr, slice};
/// Provides additional methods to [SslContext] needed for QUIC.
pub trait QuicSslContext {
fn set_options(&mut self, options: u32) -> u32;
fn verify_peer(&mut self, verify: bool);
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult;
fn set_session_cache_mode(&mut self, mode: c_int) -> c_int;
fn set_new_session_callback(
&mut self,
cb: Option<
unsafe extern "C" fn(ssl: *mut bffi::SSL, session: *mut bffi::SSL_SESSION) -> c_int,
>,
);
fn set_info_callback(
&mut self,
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, type_: c_int, value: c_int)>,
);
fn set_keylog_callback(
&mut self,
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, line: *const c_char)>,
);
fn set_certificate(&mut self, cert: X509) -> BoringResult;
fn load_certificate_from_pem_file(&mut self, path: &str) -> BoringResult;
fn add_to_cert_chain(&mut self, cert: X509) -> BoringResult;
fn load_cert_chain_from_pem_file(&mut self, path: &str) -> BoringResult;
fn set_private_key<T: HasPrivate>(&mut self, key: PKey<T>) -> BoringResult;
fn load_private_key_from_pem_file(&mut self, path: &str) -> BoringResult;
fn check_private_key(&self) -> BoringResult;
fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef;
fn enable_early_data(&mut self, enable: bool);
fn set_alpn_protos(&mut self, protos: &[u8]) -> BoringResult;
fn set_alpn_select_cb(
&mut self,
cb: Option<
unsafe extern "C" fn(
ssl: *mut bffi::SSL,
out: *mut *const u8,
out_len: *mut u8,
in_: *const u8,
in_len: c_uint,
arg: *mut c_void,
) -> c_int,
>,
);
fn set_server_name_cb(
&mut self,
cb: Option<
unsafe extern "C" fn(
ssl: *mut bffi::SSL,
out_alert: *mut c_int,
arg: *mut c_void,
) -> c_int,
>,
);
fn set_select_certificate_cb(
&mut self,
cb: Option<
unsafe extern "C" fn(
arg1: *const bffi::SSL_CLIENT_HELLO,
) -> bffi::ssl_select_cert_result_t,
>,
);
}
impl QuicSslContext for SslContext {
fn set_options(&mut self, options: u32) -> u32 {
unsafe { bffi::SSL_CTX_set_options(self.as_ptr(), options) }
}
fn verify_peer(&mut self, verify: bool) {
let mode = if verify {
bffi::SSL_VERIFY_PEER | bffi::SSL_VERIFY_FAIL_IF_NO_PEER_CERT
} else {
bffi::SSL_VERIFY_NONE
};
unsafe { bffi::SSL_CTX_set_verify(self.as_ptr(), mode, None) }
}
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult {
unsafe { br(bffi::SSL_CTX_set_quic_method(self.as_ptr(), method)) }
}
fn set_session_cache_mode(&mut self, mode: c_int) -> c_int {
unsafe { bffi::SSL_CTX_set_session_cache_mode(self.as_ptr(), mode) }
}
fn set_new_session_callback(
&mut self,
cb: Option<
unsafe extern "C" fn(ssl: *mut bffi::SSL, session: *mut bffi::SSL_SESSION) -> c_int,
>,
) {
unsafe {
bffi::SSL_CTX_sess_set_new_cb(self.as_ptr(), cb);
}
}
fn set_info_callback(
&mut self,
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, type_: c_int, value: c_int)>,
) {
unsafe { bffi::SSL_CTX_set_info_callback(self.as_ptr(), cb) }
}
fn set_keylog_callback(
&mut self,
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, line: *const c_char)>,
) {
unsafe { bffi::SSL_CTX_set_keylog_callback(self.as_ptr(), cb) }
}
fn set_certificate(&mut self, cert: X509) -> BoringResult {
unsafe {
br(bffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr()))?;
mem::forget(cert);
Ok(())
}
}
fn load_certificate_from_pem_file(&mut self, path: &str) -> BoringResult {
let path = ffi::CString::new(path).unwrap();
unsafe {
br(bffi::SSL_CTX_use_certificate_file(
self.as_ptr(),
path.as_ptr(),
bffi::SSL_FILETYPE_PEM,
))
}
}
fn add_to_cert_chain(&mut self, cert: X509) -> BoringResult {
unsafe {
br(bffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.as_ptr()) as c_int)?;
mem::forget(cert);
Ok(())
}
}
fn load_cert_chain_from_pem_file(&mut self, path: &str) -> BoringResult {
let path = ffi::CString::new(path).unwrap();
unsafe {
br(bffi::SSL_CTX_use_certificate_chain_file(
self.as_ptr(),
path.as_ptr(),
))
}
}
fn set_private_key<T: HasPrivate>(&mut self, key: PKey<T>) -> BoringResult {
unsafe {
br(bffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr()))?;
mem::forget(key);
Ok(())
}
}
fn load_private_key_from_pem_file(&mut self, path: &str) -> BoringResult {
let path = ffi::CString::new(path).unwrap();
unsafe {
br(bffi::SSL_CTX_use_PrivateKey_file(
self.as_ptr(),
path.as_ptr(),
bffi::SSL_FILETYPE_PEM,
))
}
}
fn check_private_key(&self) -> BoringResult {
unsafe { br(bffi::SSL_CTX_check_private_key(self.as_ptr())) }
}
fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef {
unsafe { X509StoreBuilderRef::from_ptr_mut(bffi::SSL_CTX_get_cert_store(self.as_ptr())) }
}
fn enable_early_data(&mut self, enable: bool) {
unsafe { bffi::SSL_CTX_set_early_data_enabled(self.as_ptr(), enable.into()) }
}
fn set_alpn_protos(&mut self, protos: &[u8]) -> BoringResult {
unsafe {
br_zero_is_success(bffi::SSL_CTX_set_alpn_protos(
self.as_ptr(),
protos.as_ptr(),
protos.len() as _,
))
}
}
fn set_alpn_select_cb(
&mut self,
cb: Option<
unsafe extern "C" fn(
*mut bffi::SSL,
*mut *const u8,
*mut u8,
*const u8,
c_uint,
*mut c_void,
) -> c_int,
>,
) {
unsafe { bffi::SSL_CTX_set_alpn_select_cb(self.as_ptr(), cb, ptr::null_mut()) }
}
fn set_server_name_cb(
&mut self,
cb: Option<
unsafe extern "C" fn(
ssl: *mut bffi::SSL,
out_alert: *mut c_int,
arg: *mut c_void,
) -> c_int,
>,
) {
// The function always returns 1.
unsafe {
let _ = bffi::SSL_CTX_set_tlsext_servername_callback(self.as_ptr(), cb);
}
}
fn set_select_certificate_cb(
&mut self,
cb: Option<
unsafe extern "C" fn(
arg1: *const bffi::SSL_CLIENT_HELLO,
) -> bffi::ssl_select_cert_result_t,
>,
) {
unsafe { bffi::SSL_CTX_set_select_certificate_cb(self.as_ptr(), cb) }
}
}
/// Provides additional methods to [Ssl] needed for QUIC.
pub trait QuicSsl {
fn set_connect_state(&mut self);
fn set_accept_state(&mut self);
fn state_string(&self) -> &'static str;
fn set_quic_transport_params(&mut self, params: &[u8]) -> BoringResult;
fn get_peer_quic_transport_params(&self) -> Option<&[u8]>;
fn get_error(&self, raw: c_int) -> SslError;
fn is_handshaking(&self) -> bool;
fn do_handshake(&mut self) -> SslError;
fn provide_quic_data(&mut self, level: Level, data: &[u8]) -> SslError;
fn quic_max_handshake_flight_len(&self, level: Level) -> usize;
fn quic_read_level(&self) -> Level;
fn quic_write_level(&self) -> Level;
fn process_post_handshake(&mut self) -> SslError;
fn set_verify_hostname(&mut self, domain: &str) -> BoringResult;
fn export_keyring_material(
&self,
output: &mut [u8],
label: &[u8],
context: &[u8],
) -> BoringResult;
fn in_early_data(&self) -> bool;
fn early_data_accepted(&self) -> bool;
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult;
fn set_quic_early_data_context(&mut self, value: &[u8]) -> BoringResult;
fn get_early_data_reason(&self) -> bffi::ssl_early_data_reason_t;
fn early_data_reason_string(reason: bffi::ssl_early_data_reason_t) -> &'static str;
fn reset_early_rejected_data(&mut self);
fn set_quic_use_legacy_codepoint(&mut self, use_legacy: bool);
}
impl QuicSsl for Ssl {
fn set_connect_state(&mut self) {
unsafe { bffi::SSL_set_connect_state(self.as_ptr()) }
}
fn set_accept_state(&mut self) {
unsafe { bffi::SSL_set_accept_state(self.as_ptr()) }
}
fn state_string(&self) -> &'static str {
unsafe {
CStr::from_ptr(bffi::SSL_state_string_long(self.as_ptr()))
.to_str()
.unwrap()
}
}
fn set_quic_transport_params(&mut self, params: &[u8]) -> BoringResult {
unsafe {
br(bffi::SSL_set_quic_transport_params(
self.as_ptr(),
params.as_ptr(),
params.len(),
))
}
}
fn get_peer_quic_transport_params(&self) -> Option<&[u8]> {
let mut ptr: *const u8 = ptr::null();
let mut len: usize = 0;
unsafe {
bffi::SSL_get_peer_quic_transport_params(self.as_ptr(), &mut ptr, &mut len);
if len == 0 {
None
} else {
Some(slice::from_raw_parts(ptr, len))
}
}
}
#[inline]
fn get_error(&self, raw: c_int) -> SslError {
unsafe { SslError(bffi::SSL_get_error(self.as_ptr(), raw)) }
}
#[inline]
fn is_handshaking(&self) -> bool {
unsafe { bffi::SSL_in_init(self.as_ptr()) == 1 }
}
#[inline]
fn do_handshake(&mut self) -> SslError {
self.get_error(unsafe { bffi::SSL_do_handshake(self.as_ptr()) })
}
#[inline]
fn provide_quic_data(&mut self, level: Level, plaintext: &[u8]) -> SslError {
unsafe {
self.get_error(bffi::SSL_provide_quic_data(
self.as_ptr(),
level.into(),
plaintext.as_ptr(),
plaintext.len(),
))
}
}
#[inline]
fn quic_max_handshake_flight_len(&self, level: Level) -> usize {
unsafe { bffi::SSL_quic_max_handshake_flight_len(self.as_ptr(), level.into()) }
}
#[inline]
fn quic_read_level(&self) -> Level {
unsafe { bffi::SSL_quic_read_level(self.as_ptr()).into() }
}
#[inline]
fn quic_write_level(&self) -> Level {
unsafe { bffi::SSL_quic_write_level(self.as_ptr()).into() }
}
#[inline]
fn process_post_handshake(&mut self) -> SslError {
self.get_error(unsafe { bffi::SSL_process_quic_post_handshake(self.as_ptr()) })
}
fn set_verify_hostname(&mut self, domain: &str) -> BoringResult {
let param = self.param_mut();
param.set_hostflags(boring::x509::verify::X509CheckFlags::NO_PARTIAL_WILDCARDS);
match domain.parse() {
Ok(ip) => param.set_ip(ip)?,
Err(_) => param.set_host(domain)?,
}
Ok(())
}
#[inline]
fn export_keyring_material(
&self,
output: &mut [u8],
label: &[u8],
context: &[u8],
) -> BoringResult {
unsafe {
br(bffi::SSL_export_keying_material(
self.as_ptr(),
output.as_mut_ptr(),
output.len(),
label.as_ptr() as *const c_char,
label.len(),
context.as_ptr(),
context.len(),
context.is_empty() as _,
))
}
}
#[inline]
fn in_early_data(&self) -> bool {
unsafe { bffi::SSL_in_early_data(self.as_ptr()) == 1 }
}
#[inline]
fn early_data_accepted(&self) -> bool {
unsafe { bffi::SSL_early_data_accepted(self.as_ptr()) == 1 }
}
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult {
unsafe { br(bffi::SSL_set_quic_method(self.as_ptr(), method)) }
}
fn set_quic_early_data_context(&mut self, value: &[u8]) -> BoringResult {
unsafe {
br(bffi::SSL_set_quic_early_data_context(
self.as_ptr(),
value.as_ptr(),
value.len(),
))
}
}
fn get_early_data_reason(&self) -> bffi::ssl_early_data_reason_t {
unsafe { bffi::SSL_get_early_data_reason(self.as_ptr()) }
}
fn early_data_reason_string(reason: bffi::ssl_early_data_reason_t) -> &'static str {
unsafe {
bffi::SSL_early_data_reason_string(reason)
.as_ref()
.map_or("unknown", |reason| CStr::from_ptr(reason).to_str().unwrap())
}
}
#[inline]
fn reset_early_rejected_data(&mut self) {
unsafe { bffi::SSL_reset_early_data_reject(self.as_ptr()) }
}
fn set_quic_use_legacy_codepoint(&mut self, use_legacy: bool) {
unsafe { bffi::SSL_set_quic_use_legacy_codepoint(self.as_ptr(), use_legacy as _) }
}
}
pub trait QuicSslSession {
fn early_data_capable(&self) -> bool;
fn copy_without_early_data(&mut self) -> SslSession;
fn encode<W: BufMut>(&self, out: &mut W) -> BoringResult;
fn decode<R: Buf>(ctx: &SslContextRef, r: &mut R) -> StdResult<SslSession, ErrorStack>;
}
impl QuicSslSession for SslSession {
fn early_data_capable(&self) -> bool {
unsafe { bffi::SSL_SESSION_early_data_capable(self.as_ptr()) == 1 }
}
fn copy_without_early_data(&mut self) -> SslSession {
unsafe { SslSession::from_ptr(bffi::SSL_SESSION_copy_without_early_data(self.as_ptr())) }
}
fn encode<W: BufMut>(&self, out: &mut W) -> BoringResult {
unsafe {
let mut buf: *mut u8 = ptr::null_mut();
let mut len = 0usize;
br(bffi::SSL_SESSION_to_bytes(
self.as_ptr(),
&mut buf,
&mut len,
))?;
out.put_slice(slice::from_raw_parts(buf, len));
bffi::OPENSSL_free(buf as _);
Ok(())
}
}
fn decode<R: Buf>(ctx: &SslContextRef, r: &mut R) -> StdResult<SslSession, ErrorStack> {
unsafe {
let in_len = r.remaining();
let in_ = r.chunk();
bffi::SSL_SESSION_from_bytes(in_.as_ptr(), in_len, ctx.as_ptr())
.as_mut()
.map_or_else(
|| Err(ErrorStack::get()),
|session| Ok(SslSession::from_ptr(session)),
)
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Level {
Initial = 0,
EarlyData = 1,
Handshake = 2,
Application = 3,
}
impl Level {
pub const NUM_LEVELS: usize = 4;
pub fn next(&self) -> Self {
match self {
Level::Initial => Level::Handshake,
Level::EarlyData => Level::Handshake,
_ => Level::Application,
}
}
}
impl From<bffi::ssl_encryption_level_t> for Level {
fn from(value: bffi::ssl_encryption_level_t) -> Self {
match value {
bffi::ssl_encryption_level_t::ssl_encryption_initial => Self::Initial,
bffi::ssl_encryption_level_t::ssl_encryption_early_data => Self::EarlyData,
bffi::ssl_encryption_level_t::ssl_encryption_handshake => Self::Handshake,
bffi::ssl_encryption_level_t::ssl_encryption_application => Self::Application,
_ => unreachable!(),
}
}
}
impl From<Level> for bffi::ssl_encryption_level_t {
fn from(value: Level) -> Self {
match value {
Level::Initial => bffi::ssl_encryption_level_t::ssl_encryption_initial,
Level::EarlyData => bffi::ssl_encryption_level_t::ssl_encryption_early_data,
Level::Handshake => bffi::ssl_encryption_level_t::ssl_encryption_handshake,
Level::Application => bffi::ssl_encryption_level_t::ssl_encryption_application,
}
}
}
#[derive(Copy, Clone)]
pub struct SslError(c_int);
impl SslError {
#[inline]
pub fn value(&self) -> c_int {
self.0
}
#[inline]
pub fn is_none(&self) -> bool {
self.0 == bffi::SSL_ERROR_NONE
}
#[inline]
pub fn get_description(&self) -> &'static str {
unsafe {
CStr::from_ptr(bffi::SSL_error_description(self.0))
.to_str()
.unwrap()
}
}
}
impl Display for SslError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "SSL_ERROR[{}]: {}", self.0, self.get_description())
}
}

View File

@ -0,0 +1,395 @@
use crate::alpn::AlpnProtocols;
use crate::bffi_ext::QuicSslContext;
use crate::error::{map_result, Result};
use crate::session_state::{SessionState, QUIC_METHOD};
use crate::version::QuicVersion;
use crate::{Entry, QuicSsl, QuicSslSession, SessionCache, SimpleCache};
use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslSession, SslVersion};
use boring_sys as bffi;
use bytes::{Bytes, BytesMut};
use foreign_types_shared::ForeignType;
use quinn_proto::{
crypto, transport_parameters::TransportParameters, ConnectError, ConnectionId, Side,
TransportError,
};
use std::any::Any;
use std::ffi::c_int;
use std::io::Cursor;
use std::result::Result as StdResult;
use std::sync::Arc;
use std::sync::LazyLock;
use tracing::{trace, warn};
/// Configuration for a client-side QUIC. Wraps around a BoringSSL [SslContext].
pub struct Config {
ctx: SslContext,
session_cache: Arc<dyn SessionCache>,
}
impl Config {
pub fn new() -> Result<Self> {
let mut builder = SslContextBuilder::new(SslMethod::tls())?;
// QUIC requires TLS 1.3.
builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;
builder.set_default_verify_paths()?;
// We build the context early, since we are not allowed to further mutate the context
// in start_session.
let mut ctx = builder.build();
// By default, enable early data (used for 0-RTT).
ctx.enable_early_data(true);
// Set the default ALPN protocols offered by the client. QUIC requires ALPN be configured
// (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
ctx.set_alpn_protos(&AlpnProtocols::default().encode())?;
// Configure session caching.
ctx.set_session_cache_mode(bffi::SSL_SESS_CACHE_CLIENT | bffi::SSL_SESS_CACHE_NO_INTERNAL);
ctx.set_new_session_callback(Some(Session::new_session_callback));
// Set callbacks for the SessionState.
ctx.set_quic_method(&QUIC_METHOD)?;
ctx.set_info_callback(Some(SessionState::info_callback));
// For clients, verification of the server is on by default.
ctx.verify_peer(true);
Ok(Self {
ctx,
session_cache: Arc::new(SimpleCache::new(256)),
})
}
/// Returns the underlying [SslContext] backing all created sessions.
pub fn ctx(&self) -> &SslContext {
&self.ctx
}
/// Returns the underlying [SslContext] backing all created sessions. Wherever possible use
/// the provided methods to modify settings rather than accessing this directly.
///
/// Care should be taken to avoid overriding required behavior. In particular, this
/// configuration will set callbacks for QUIC events, alpn selection, server name,
/// as well as info and key logging.
pub fn ctx_mut(&mut self) -> &mut SslContext {
&mut self.ctx
}
/// Sets whether or not the peer certificate should be verified. If `true`, any error
/// during verification will be fatal. If not called, verification of the server is
/// enabled by default.
pub fn verify_peer(&mut self, verify: bool) {
self.ctx.verify_peer(verify)
}
/// Gets the [SessionCache] used to cache all client sessions.
pub fn get_session_cache(&self) -> Arc<dyn SessionCache> {
self.session_cache.clone()
}
/// Sets the [SessionCache] to be shared by all created client sessions.
pub fn set_session_cache(&mut self, session_cache: Arc<dyn SessionCache>) {
self.session_cache = session_cache;
}
/// Sets the ALPN protocols supported by the client. QUIC requires that
/// ALPN be used (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
/// By default, the client will offer "h3".
pub fn set_alpn(&mut self, alpn_protocols: &[Vec<u8>]) -> Result<()> {
self.ctx
.set_alpn_protos(&AlpnProtocols::from(alpn_protocols).encode())?;
Ok(())
}
}
impl crypto::ClientConfig for Config {
fn start_session(
self: Arc<Self>,
version: u32,
server_name: &str,
params: &TransportParameters,
) -> StdResult<Box<dyn crypto::Session>, ConnectError> {
let version = QuicVersion::parse(version).unwrap();
Ok(Session::new(self, version, server_name, params)
.map_err(|_| ConnectError::EndpointStopping)?)
}
}
static SESSION_INDEX: LazyLock<c_int> = LazyLock::new(|| unsafe {
bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
});
/// The [crypto::Session] implementation for BoringSSL.
struct Session {
state: Box<SessionState>,
server_name: Bytes,
session_cache: Arc<dyn SessionCache>,
zero_rtt_peer_params: Option<TransportParameters>,
handshake_data_available: bool,
handshake_data_sent: bool,
}
impl Session {
fn new(
cfg: Arc<Config>,
version: QuicVersion,
server_name: &str,
params: &TransportParameters,
) -> Result<Box<Self>> {
let session_cache = cfg.session_cache.clone();
let mut ssl = Ssl::new(&cfg.ctx).unwrap();
// Configure the TLS extension based on the QUIC version used.
ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());
// Configure the SSL to be a client.
ssl.set_connect_state();
// Configure verification for the server hostname.
ssl.set_verify_hostname(server_name)
.map_err(|_| ConnectError::InvalidServerName(server_name.into()))?;
// Set the SNI hostname.
// TODO: should we validate the hostname?
ssl.set_hostname(server_name)
.map_err(|_| ConnectError::InvalidServerName(server_name.into()))?;
// Set the transport parameters.
ssl.set_quic_transport_params(&encode_params(params))
.map_err(|_| ConnectError::EndpointStopping)?;
let server_name_bytes = Bytes::copy_from_slice(server_name.as_bytes());
// If we have a cached session, use it.
let mut zero_rtt_peer_params = None;
if let Some(entry) = session_cache.get(server_name_bytes.clone()) {
match Entry::decode(ssl.ssl_context(), entry) {
Ok(entry) => {
zero_rtt_peer_params = Some(entry.params);
match unsafe { ssl.set_session(entry.session.as_ref()) } {
Ok(()) => {
trace!("attempting resumption (0-RTT) for server: {}.", server_name);
}
Err(e) => {
warn!(
"failed setting cached session for server {}: {:?}",
server_name, e
)
}
}
}
Err(e) => {
warn!(
"failed decoding session entry for server {}: {:?}",
server_name, e
)
}
}
} else {
trace!(
"no cached session found for server: {}. Will continue with 1-RTT.",
server_name
);
}
let mut session = Box::new(Self {
state: SessionState::new(ssl, Side::Client, version)?,
server_name: server_name_bytes,
session_cache,
zero_rtt_peer_params,
handshake_data_available: false,
handshake_data_sent: false,
});
// Register the instance in SSL ex_data. This allows the static callbacks to
// reference the instance.
unsafe {
map_result(bffi::SSL_set_ex_data(
session.state.ssl.as_ptr(),
*SESSION_INDEX,
&mut *session as *mut Self as *mut _,
))?;
}
// Start the handshake in order to emit the Client Hello on the first
// call to write_handshake.
session.state.advance_handshake()?;
Ok(session)
}
/// Handler for the rejection of a 0-RTT attempt. Will continue with 1-RTT.
fn on_zero_rtt_rejected(&mut self) {
trace!(
"0-RTT handshake attempted but was rejected by the server: {}",
Ssl::early_data_reason_string(self.state.ssl.get_early_data_reason())
);
self.zero_rtt_peer_params = None;
// Removed the failed cache entry.
self.session_cache.remove(self.server_name.clone());
// Now retry advancing the handshake, this time in 1-RTT mode.
if let Err(e) = self.state.advance_handshake() {
warn!("failed advancing 1-RTT handshake: {:?}", e)
}
}
/// Client-side only callback from BoringSSL to allow caching of a new session.
fn on_new_session(&mut self, session: SslSession) {
if !session.early_data_capable() {
warn!("failed caching session: not early data capable");
return;
}
// Get the server transport parameters.
let params = match self.state.ssl.get_peer_quic_transport_params() {
Some(params) => {
match TransportParameters::read(Side::Client, &mut Cursor::new(&params)) {
Ok(params) => params,
Err(e) => {
warn!("failed parsing server transport parameters: {:?}", e);
return;
}
}
}
None => {
warn!("failed caching session: server transport parameters are not available");
return;
}
};
// Encode the session cache entry, including both the session and the server params.
let entry = Entry { session, params };
match entry.encode() {
Ok(value) => {
// Cache the session.
self.session_cache.put(self.server_name.clone(), value)
}
Err(e) => {
warn!("failed caching session: unable to encode entry: {:?}", e);
}
}
}
/// Called by the static callbacks to retrieve the instance pointer.
#[inline]
fn get_instance(ssl: *const bffi::SSL) -> &'static mut Session {
unsafe {
let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
if data.is_null() {
panic!("BUG: Session instance missing")
}
&mut *(data as *mut Session)
}
}
/// Raw callback from BoringSSL.
extern "C" fn new_session_callback(
ssl: *mut bffi::SSL,
session: *mut bffi::SSL_SESSION,
) -> c_int {
let inst = Self::get_instance(ssl);
let session = unsafe { SslSession::from_ptr(session) };
inst.on_new_session(session);
// Return 1 to indicate we've taken ownership of the session.
1
}
}
impl crypto::Session for Session {
fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
self.state.initial_keys(dcid, side)
}
fn handshake_data(&self) -> Option<Box<dyn Any>> {
self.state.handshake_data()
}
fn peer_identity(&self) -> Option<Box<dyn Any>> {
self.state.peer_identity()
}
fn early_crypto(&self) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
self.state.early_crypto()
}
fn early_data_accepted(&self) -> Option<bool> {
Some(self.state.ssl.early_data_accepted())
}
fn is_handshaking(&self) -> bool {
self.state.is_handshaking()
}
fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<bool, TransportError> {
self.state.read_handshake(plaintext)?;
if self.state.early_data_rejected {
self.on_zero_rtt_rejected();
}
// Only indicate that handshake data is available once.
// On the client side there is no ALPN callback, so we need to manually check
// if the ALPN protocol has been selected.
if !self.handshake_data_sent {
if self.state.ssl.selected_alpn_protocol().is_some() {
self.handshake_data_available = true;
}
if self.handshake_data_available {
self.handshake_data_sent = true;
return Ok(true);
}
}
Ok(false)
}
fn transport_parameters(&self) -> StdResult<Option<TransportParameters>, TransportError> {
match self.state.transport_parameters()? {
Some(params) => Ok(Some(params)),
None => {
if self.state.ssl.in_early_data() {
Ok(self.zero_rtt_peer_params)
} else {
Ok(None)
}
}
}
}
fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
self.state.write_handshake(buf)
}
fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
self.state.next_1rtt_keys()
}
fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool {
self.state.is_valid_retry(orig_dst_cid, header, payload)
}
fn export_keying_material(
&self,
output: &mut [u8],
label: &[u8],
context: &[u8],
) -> StdResult<(), crypto::ExportKeyingMaterialError> {
self.state.export_keying_material(output, label, context)
}
}
fn encode_params(params: &TransportParameters) -> Bytes {
let mut out = BytesMut::with_capacity(128);
params.write(&mut out);
out.freeze()
}

150
quinn-boring/src/error.rs Normal file
View File

@ -0,0 +1,150 @@
use boring::error::ErrorStack;
use quinn_proto::{crypto, ConnectError, TransportError};
use std::ffi::c_int;
use std::fmt::{Debug, Display, Formatter};
use std::io::ErrorKind;
use std::result::Result as StdResult;
use std::{fmt, io};
// Error conversion:
pub enum Error {
SslError(ErrorStack),
IoError(io::Error),
ConnectError(ConnectError),
TransportError(TransportError),
}
impl Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::SslError(e) => Debug::fmt(&e, f),
Self::IoError(e) => Debug::fmt(&e, f),
Self::ConnectError(e) => Debug::fmt(&e, f),
Self::TransportError(e) => Debug::fmt(&e, f),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::SslError(e) => Display::fmt(&e, f),
Self::IoError(e) => Display::fmt(&e, f),
Self::ConnectError(e) => Display::fmt(&e, f),
Self::TransportError(e) => Display::fmt(&e, f),
}
}
}
impl std::error::Error for Error {}
impl Error {
pub(crate) fn ssl() -> Self {
Error::SslError(ErrorStack::get())
}
pub(crate) fn invalid_input(msg: String) -> Self {
Error::IoError(io::Error::new(ErrorKind::InvalidInput, msg))
}
pub(crate) fn other(msg: String) -> Self {
Error::IoError(io::Error::other(msg))
}
}
/// Support conversion to CryptoError.
impl From<Error> for crypto::CryptoError {
fn from(_: Error) -> Self {
crypto::CryptoError
}
}
/// Support conversion to ConnectError.
impl From<Error> for ConnectError {
fn from(e: Error) -> Self {
match e {
Error::SslError(_) => Self::EndpointStopping,
Error::IoError(_) => Self::EndpointStopping,
Error::ConnectError(e) => e,
Error::TransportError(_) => Self::EndpointStopping,
}
}
}
impl From<ErrorStack> for Error {
fn from(e: ErrorStack) -> Self {
Error::SslError(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::IoError(e)
}
}
impl From<ConnectError> for Error {
fn from(e: ConnectError) -> Self {
Error::ConnectError(e)
}
}
impl From<TransportError> for Error {
fn from(e: TransportError) -> Self {
Error::TransportError(e)
}
}
/// The main result type for this (crypto boring) module.
pub type Result<T> = StdResult<T, Error>;
/// The result returned by the Cloudflare Boring library API functions.
pub(crate) type BoringResult = StdResult<(), ErrorStack>;
/// Maps BoringSSL ffi return values to the Result type consistent with the Boring APIs.
pub(crate) fn br(bssl_result: c_int) -> BoringResult {
match bssl_result {
1 => Ok(()),
_ => Err(ErrorStack::get()),
}
}
pub(crate) fn br_zero_is_success(bssl_result: c_int) -> BoringResult {
match bssl_result {
0 => Ok(()),
_ => Err(ErrorStack::get()),
}
}
/// Maps BoringSSL ffi return values to a Result.
pub(crate) fn map_result(bssl_result: c_int) -> Result<()> {
match bssl_result {
1 => Ok(()),
_ => Err(Error::SslError(ErrorStack::get())),
}
}
/// Maps a result from a Rust callback to a BoringSSL result error code.
pub(crate) fn map_cb_result<T>(result: Result<T>) -> c_int {
match result {
Ok(_) => 1,
_ => 0,
}
}
/// Like map_result, but for BoringSSL method that break the standard return value convention.
pub(crate) fn map_result_zero_is_success(bssl_result: c_int) -> Result<()> {
match bssl_result {
0 => Ok(()),
_ => Err(Error::SslError(ErrorStack::get())),
}
}
/// Like map_result, but ensures that the resulting pointer is non-null.
pub(crate) fn map_ptr_result<T>(r: *mut T) -> Result<*mut T> {
if r.is_null() {
Err(Error::ssl())
} else {
Ok(r)
}
}

View File

@ -0,0 +1,68 @@
use crate::error::Result;
use crate::hkdf::Hkdf;
use crate::key::{AeadKey, Key};
use crate::secret::Secret;
use crate::suite::CipherSuite;
use quinn_proto::crypto;
pub struct HandshakeTokenKey(Key);
impl HandshakeTokenKey {
/// Creates a new randomized HandshakeTokenKey.
pub fn new() -> Result<Self> {
Self::new_for(Secret::random())
}
fn new_for(secret: Secret) -> Result<Self> {
// Extract the key.
let mut key = [0u8; Key::MAX_LEN];
let len = Hkdf::sha256().extract(&[], secret.slice(), &mut key)?;
Ok(Self(Key::new(key, len)))
}
}
impl crypto::HandshakeTokenKey for HandshakeTokenKey {
fn aead_from_hkdf(&self, random_bytes: &[u8]) -> Box<dyn crypto::AeadKey> {
let suite = CipherSuite::aes256_gcm_sha384();
let prk = self.0.slice();
let mut key = suite.aead.zero_key();
Hkdf::sha256()
.expand(prk, random_bytes, key.slice_mut())
.unwrap();
Box::new(AeadKey::new(suite, key).unwrap())
}
}
#[cfg(test)]
mod test {
use hex_literal::hex;
use quinn_proto::crypto::HandshakeTokenKey;
#[test]
fn round_trip() {
// Create a random token key.
let master_key = hex!("ab35ad55e9957c0e67aedbbd76f6a781528a5b43cc57bfd633"
"ccca412327aa23e0d7140d5fc290d1637746706c7d703e3bf405687a69ee82284a5ede49f59e19");
let htk = super::HandshakeTokenKey::new_for(super::Secret::from(&master_key)).unwrap();
// Generate an AEAD from the given random.
let random_bytes = hex!("b088e52e27da85f8838e163ddb90fd35d633fad44f0ab9c39f05459297178599");
let aead_key = htk.aead_from_hkdf(&random_bytes);
// Inputs for the seal/open operations.
let data = hex!("146a6d36221e4f24eda3a16f71a816a8a72dd7efbb0000000064076bde");
let additional_data = hex!("00000000000000000000000000000001d60c084b239b74c31de86f");
// Seal the buffer and verify the expected output.
let mut buf = data.to_vec();
aead_key.seal(&mut buf, &additional_data).unwrap();
let expected = hex!("504a8f0841f3fb7dbf8b3df90b5be913cb5a28000918510baeff64"
"5d72b67ab34a47da820a97416d68d0b605af");
assert_eq!(&expected, buf.as_slice());
// Now open and verify we get back the original data.
let out = aead_key.open(&mut buf, &additional_data).unwrap();
assert_eq!(&data, out);
}
}

129
quinn-boring/src/hkdf.rs Normal file
View File

@ -0,0 +1,129 @@
use crate::error::{map_result, Error, Result};
use boring::hash::MessageDigest;
use boring_sys as bffi;
use bytes::{BufMut, BytesMut};
use std::sync::LazyLock;
/// The block size used by the supported digest algorithms (64).
pub(crate) const DIGEST_BLOCK_LEN: usize = bffi::SHA_CBLOCK as _;
// /// The digest size for SHA256 (32).
// pub(crate) const SHA256_DIGEST_LEN: usize = bffi::SHA256_DIGEST_LENGTH as _;
//
// /// The digest size for SHA384 (48).
// pub(crate) const SHA384_DIGEST_LEN: usize = bffi::SHA384_DIGEST_LENGTH as _;
/// Implementation of [HKDF](https://www.rfc-editor.org/rfc/rfc5869) used for
/// creating the initial secrets for
/// [QUIC](https://www.rfc-editor.org/rfc/rfc9001#section-5.2) and
/// [TLS_1.3](https://datatracker.ietf.org/doc/html/rfc9001#name-initial-secrets).
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) struct Hkdf(MessageDigest);
static SHA256: LazyLock<Hkdf> = LazyLock::new(|| Hkdf(MessageDigest::sha256()));
static SHA384: LazyLock<Hkdf> = LazyLock::new(|| Hkdf(MessageDigest::sha384()));
impl Hkdf {
pub(crate) fn sha256() -> Hkdf {
*SHA256
}
pub(crate) fn sha384() -> Hkdf {
*SHA384
}
/// The size of the digest in bytes.
#[inline]
pub(crate) fn digest_size(self) -> usize {
self.0.size()
}
/// Performs an HKDF extract (<https://tools.ietf.org/html/rfc5869#section-2.2>),
/// given the salt and the initial key material (IKM). Returns the slice of the 'out'
/// array containing the generated pseudorandom key (PRK).
#[inline]
pub(crate) fn extract(self, salt: &[u8], ikm: &[u8], out: &mut [u8]) -> Result<usize> {
if out.len() < self.digest_size() {
return Err(Error::invalid_input(format!(
"HKDF extract output array invalid size: {}",
out.len()
)));
}
let mut out_len = out.len();
unsafe {
map_result(bffi::HKDF_extract(
out.as_mut_ptr(),
&mut out_len,
self.0.as_ptr(),
ikm.as_ptr(),
ikm.len(),
salt.as_ptr(),
salt.len(),
))?;
Ok(out_len)
}
}
/// Performs the HKDF-Expand-Label function as defined in the
/// [TLS-1.3 spec](https://datatracker.ietf.org/doc/html/rfc8446#section-7.1). The
/// [HKDF-Expand-Label function](https://www.rfc-editor.org/rfc/rfc5869#section-2.3) takes
/// 4 explicit arguments (Secret, Label, Context, and Length), as well as implicit PRF
/// which is the hash function negotiated by TLS.
///
/// Its use in QUIC is only for deriving initial secrets for obfuscation, for calculating
/// packet protection keys and IVs from the corresponding packet protection secret and
/// key update in the same quic session. None of these uses need a Context (a zero-length
/// context is provided), so this argument is omitted here.
#[inline]
pub(crate) fn expand_label(self, secret: &[u8], label: &[u8], out: &mut [u8]) -> Result<()> {
// Convert the label to a structure required by HKDF_expand. Doing
// this inline rather than using the complex openssl Crypto ByteBuilder (CBB).
let label = {
const TLS_VERSION_LABEL: &[u8] = b"tls13 ";
// Initialize the builder for the label structure. Breakdown of the required capacity:
// 2-byte total length field +
// 1-byte for the length of the label +
// Label length +
// 1-byte for the length of the Context +
// 0-bytes for an empty context (QUIC does not use context).
let label_len = TLS_VERSION_LABEL.len() + label.len();
let builder_capacity = label_len + 4;
let mut builder = BytesMut::with_capacity(builder_capacity);
// Add the length of the output key in big-endian byte order.
builder.put_u16(out.len() as u16);
// Add a child containing the label.
builder.put_u8(label_len as u8);
builder.put(TLS_VERSION_LABEL);
builder.put(label);
// Add a child containing a zero hash.
builder.put_u8(0);
builder
};
self.expand(secret, &label, out)
}
#[inline]
pub(crate) fn expand(&self, prk: &[u8], info: &[u8], out: &mut [u8]) -> Result<()> {
unsafe {
map_result(bffi::HKDF_expand(
out.as_mut_ptr(),
out.len(),
self.0.as_ptr(),
prk.as_ptr(),
prk.len(),
info.as_ptr(),
info.len(),
))?;
}
Ok(())
}
}

74
quinn-boring/src/hmac.rs Normal file
View File

@ -0,0 +1,74 @@
use crate::error::map_ptr_result;
use crate::hkdf::DIGEST_BLOCK_LEN;
use boring::hash::MessageDigest;
use boring_sys as bffi;
use quinn_proto::crypto;
use rand::RngCore;
use std::ffi::{c_uint, c_void};
use std::result::Result as StdResult;
const SIGNATURE_LEN_SHA_256: usize = 32;
/// Implementation of [crypto::HmacKey] using BoringSSL.
pub struct HmacKey {
alg: MessageDigest,
key: Vec<u8>,
}
impl HmacKey {
/// Creates a new randomized SHA-256 HMAC key.
pub fn sha256() -> Self {
// Create a random key.
let mut key = [0u8; DIGEST_BLOCK_LEN];
rand::rng().fill_bytes(&mut key);
Self {
alg: MessageDigest::sha256(),
key: Vec::from(key),
}
}
}
impl crypto::HmacKey for HmacKey {
fn sign(&self, data: &[u8], out: &mut [u8]) {
let mut out_len = out.len() as c_uint;
unsafe {
map_ptr_result(bffi::HMAC(
self.alg.as_ptr(),
self.key.as_ptr() as *const c_void,
self.key.len(),
data.as_ptr(),
data.len(),
out.as_mut_ptr(),
&mut out_len,
))
.unwrap();
}
// Verify the signature length.
if out_len as usize != self.signature_len() {
panic!("HMAC.sign: generated signature with unexpected length: {out_len}");
}
}
#[inline]
fn signature_len(&self) -> usize {
SIGNATURE_LEN_SHA_256
}
fn verify(&self, data: &[u8], signature: &[u8]) -> StdResult<(), crypto::CryptoError> {
if signature.len() != self.signature_len() {
return Err(crypto::CryptoError {});
}
// Sign the data.
let mut out = [0u8; SIGNATURE_LEN_SHA_256];
self.sign(data, &mut out);
// Compare the output.
if out == signature {
return Ok(());
}
Err(crypto::CryptoError {})
}
}

529
quinn-boring/src/key.rs Normal file
View File

@ -0,0 +1,529 @@
use crate::error::{map_result, map_result_zero_is_success, Result};
use crate::macros::bounded_array;
use crate::secret::Secret;
use crate::suite::{CipherSuite, ID};
use crate::{Error, QuicVersion};
use boring_sys as bffi;
use bytes::BytesMut;
use quinn_proto::crypto;
use std::ffi::c_uint;
use std::fmt::{Debug, Formatter};
use std::mem;
use std::mem::MaybeUninit;
use std::result::Result as StdResult;
const SAMPLE_LEN: usize = 16; // 128-bits.
/// The maximum key size used by Quic algorithms.
const MAX_KEY_LEN: usize = 32;
/// The maximum nonce size used by Quic algorithms.
const MAX_NONCE_LEN: usize = 12;
/// The maximum tag size used by Quic algorithms.
const MAX_TAG_LEN: usize = 16;
bounded_array! {
/// A buffer that can fit the largest key supported by Quic.
pub(crate) struct Key(MAX_KEY_LEN),
/// A buffer that can fit the largest nonce supported by Quic.
pub(crate) struct Nonce(MAX_NONCE_LEN),
/// A buffer that can fit the largest tag supported by Quic.
pub(crate) struct Tag(MAX_TAG_LEN)
}
/// A pair of keys for bidirectional communication
#[derive(Copy, Clone, Debug)]
pub(crate) struct KeyPair<T> {
/// The key for this side, used for encrypting data.
pub(crate) local: T,
/// The key for the other side, used for decrypting data.
pub(crate) remote: T,
}
impl KeyPair<HeaderKey> {
#[inline]
pub(crate) fn as_crypto(&self) -> Result<crypto::KeyPair<Box<dyn crypto::HeaderKey>>> {
Ok(crypto::KeyPair {
local: self.local.as_crypto()?,
remote: self.remote.as_crypto()?,
})
}
}
impl KeyPair<PacketKey> {
#[inline]
pub(crate) fn as_crypto(&self) -> Result<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
Ok(crypto::KeyPair {
local: Box::new(self.local),
remote: Box::new(self.remote),
})
}
}
/// A complete set of keys for a certain encryption level.
#[derive(Clone, Debug)]
pub(crate) struct Keys {
/// Header protection keys
pub(crate) header: KeyPair<HeaderKey>,
/// Packet protection keys
pub(crate) packet: KeyPair<PacketKey>,
}
impl Keys {
pub(crate) fn as_crypto(&self) -> Result<crypto::Keys> {
Ok(crypto::Keys {
header: self.header.as_crypto()?,
packet: self.packet.as_crypto()?,
})
}
}
/// Internal header key representation. Supports conversion to [crypto::HeaderKey]
#[derive(Copy, Clone, Debug)]
pub(crate) struct HeaderKey {
suite: &'static CipherSuite,
key: Key,
}
impl HeaderKey {
pub(crate) fn new(
version: QuicVersion,
suite: &'static CipherSuite,
secret: &Secret,
) -> Result<Self> {
let mut key = suite.aead.zero_key();
suite
.hkdf
.expand_label(secret.slice(), version.header_key_label(), key.slice_mut())?;
Ok(Self { suite, key })
}
#[inline]
pub(crate) fn key(&self) -> &Key {
&self.key
}
/// Converts to a crypto HeaderKey.
#[inline]
pub(crate) fn as_crypto(&self) -> Result<Box<dyn crypto::HeaderKey>> {
match self.suite.id {
ID::Aes128GcmSha256 | ID::Aes256GcmSha384 => {
Ok(Box::new(AesHeaderKey::new(self.key())?))
}
ID::Chacha20Poly1305Sha256 => Ok(Box::new(ChaChaHeaderKey::new(self.key())?)),
}
}
}
/// Base trait for a crypto header protection keys. Implementation copied from rustls.
trait CryptoHeaderKey: crypto::HeaderKey {
fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]>;
#[inline]
fn sample_len(&self) -> usize {
SAMPLE_LEN
}
#[inline]
fn decrypt_in_place(&self, pn_offset: usize, packet: &mut [u8]) {
let (header, sample) = packet.split_at_mut(pn_offset + 4);
let (first, rest) = header.split_at_mut(1);
let pn_end = Ord::min(pn_offset + 3, rest.len());
self.xor_in_place(
&sample[..self.sample_len()],
&mut first[0],
&mut rest[pn_offset - 1..pn_end],
true,
)
.unwrap();
}
#[inline]
fn encrypt_in_place(&self, pn_offset: usize, packet: &mut [u8]) {
let (header, sample) = packet.split_at_mut(pn_offset + 4);
let (first, rest) = header.split_at_mut(1);
let pn_end = Ord::min(pn_offset + 3, rest.len());
self.xor_in_place(
&sample[..self.sample_size()],
&mut first[0],
&mut rest[pn_offset - 1..pn_end],
false,
)
.unwrap();
}
#[inline]
fn xor_in_place(
&self,
sample: &[u8],
first: &mut u8,
packet_number: &mut [u8],
masked: bool,
) -> Result<()> {
// This implements [Header Protection Application] almost verbatim.
let mask = self.new_mask(sample).unwrap();
// The `unwrap()` will not panic because `new_mask` returns a
// non-empty result.
let (first_mask, pn_mask) = mask.split_first().unwrap();
// It is OK for the `mask` to be longer than `packet_number`,
// but a valid `packet_number` will never be longer than `mask`.
if packet_number.len() > pn_mask.len() {
return Err(Error::other(format!(
"packet number too long: {}",
packet_number.len()
)));
}
// Infallible from this point on. Before this point, `first` and
// `packet_number` are unchanged.
const LONG_HEADER_FORM: u8 = 0x80;
let bits = match *first & LONG_HEADER_FORM == LONG_HEADER_FORM {
true => 0x0f, // Long header: 4 bits masked
false => 0x1f, // Short header: 5 bits masked
};
let first_plain = match masked {
// When unmasking, use the packet length bits after unmasking
true => *first ^ (first_mask & bits),
// When masking, use the packet length bits before masking
false => *first,
};
let pn_len = (first_plain & 0x03) as usize + 1;
*first ^= first_mask & bits;
for (dst, m) in packet_number.iter_mut().zip(pn_mask).take(pn_len) {
*dst ^= m;
}
Ok(())
}
}
/// A [CryptoHeaderKey] for AES ciphers.
struct AesHeaderKey(bffi::AES_KEY);
impl AesHeaderKey {
fn new(key: &Key) -> Result<AesHeaderKey> {
let hpk = unsafe {
let mut hpk = MaybeUninit::uninit();
// NOTE: this function breaks the usual return value convention.
map_result_zero_is_success(bffi::AES_set_encrypt_key(
key.as_ptr(),
(key.len() * 8) as c_uint,
hpk.as_mut_ptr(),
))?;
hpk.assume_init()
};
Ok(Self(hpk))
}
}
impl CryptoHeaderKey for AesHeaderKey {
#[inline]
fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> {
if sample.len() != SAMPLE_LEN {
return Err(Error::invalid_input(format!(
"invalid sample length: {}",
sample.len()
)));
}
let mut encrypted: [u8; SAMPLE_LEN] = [0; SAMPLE_LEN];
unsafe {
bffi::AES_encrypt(sample.as_ptr(), encrypted.as_mut_ptr(), &self.0);
}
let mut out: [u8; 5] = [0; 5];
out.copy_from_slice(&encrypted[..5]);
Ok(out)
}
}
impl crypto::HeaderKey for AesHeaderKey {
#[inline]
fn decrypt(&self, pn_offset: usize, packet: &mut [u8]) {
self.decrypt_in_place(pn_offset, packet)
}
#[inline]
fn encrypt(&self, pn_offset: usize, packet: &mut [u8]) {
self.encrypt_in_place(pn_offset, packet)
}
#[inline]
fn sample_size(&self) -> usize {
self.sample_len()
}
}
/// A [CryptoHeaderKey] for ChaCha ciphers.
struct ChaChaHeaderKey(Key);
impl ChaChaHeaderKey {
const ZEROS: [u8; 5] = [0; 5];
fn new(key: &Key) -> Result<ChaChaHeaderKey> {
Ok(Self(*key))
}
}
impl CryptoHeaderKey for ChaChaHeaderKey {
#[inline]
fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> {
if sample.len() != SAMPLE_LEN {
return Err(Error::invalid_input(format!(
"sample len invalid: {}",
sample.len()
)));
}
// Extract the counter and the nonce from the sample.
let (counter, nonce) = sample.split_at(mem::size_of::<u32>());
let counter = u32::from_ne_bytes(counter.try_into().unwrap());
let mut out: [u8; 5] = [0; 5];
unsafe {
bffi::CRYPTO_chacha_20(
out.as_mut_ptr(),
Self::ZEROS.as_ptr(),
Self::ZEROS.len(),
self.0.as_ptr(),
nonce.as_ptr(),
counter,
);
}
Ok(out)
}
}
impl crypto::HeaderKey for ChaChaHeaderKey {
#[inline]
fn decrypt(&self, pn_offset: usize, packet: &mut [u8]) {
self.decrypt_in_place(pn_offset, packet)
}
#[inline]
fn encrypt(&self, pn_offset: usize, packet: &mut [u8]) {
self.encrypt_in_place(pn_offset, packet)
}
#[inline]
fn sample_size(&self) -> usize {
self.sample_len()
}
}
/// Internal key representation.
#[derive(Copy, Clone, Debug)]
pub(crate) struct PacketKey {
aead_key: AeadKey,
iv: Nonce,
}
impl PacketKey {
#[inline]
pub(crate) fn new(
version: QuicVersion,
suite: &'static CipherSuite,
secret: &Secret,
) -> Result<Self> {
let mut key = suite.aead.zero_key();
suite
.hkdf
.expand_label(secret.slice(), version.key_label(), key.slice_mut())?;
let mut iv = suite.aead.zero_nonce();
suite
.hkdf
.expand_label(secret.slice(), version.iv_label(), iv.slice_mut())?;
let aead_key = AeadKey::new(suite, key)?;
Ok(Self { aead_key, iv })
}
#[cfg(test)]
pub(crate) fn key(&self) -> &Key {
&self.aead_key.key
}
#[cfg(test)]
pub(crate) fn iv(&self) -> &Nonce {
&self.iv
}
#[inline]
fn nonce_for_packet(&self, packet_number: u64) -> Nonce {
let mut nonce = self.aead_key.suite.aead.zero_nonce();
let slice = nonce.slice_mut();
slice[4..].copy_from_slice(&packet_number.to_be_bytes());
for (out, inp) in slice.iter_mut().zip(self.iv.slice().iter()) {
*out ^= inp;
}
nonce
}
}
impl crypto::PacketKey for PacketKey {
/// Encrypt a QUIC packet in-place.
fn encrypt(&self, packet_number: u64, buf: &mut [u8], header_len: usize) {
let (header, payload_tag) = buf.split_at_mut(header_len);
let nonce = self.nonce_for_packet(packet_number);
self.aead_key
.seal_in_place(&nonce, payload_tag, header)
.unwrap();
}
/// Decrypt a QUIC packet in-place.
fn decrypt(
&self,
packet_number: u64,
header: &[u8],
payload: &mut BytesMut,
) -> StdResult<(), crypto::CryptoError> {
let nonce = self.nonce_for_packet(packet_number);
let plain_len = self
.aead_key
.open_in_place(&nonce, payload.as_mut(), header)?;
payload.truncate(plain_len);
Ok(())
}
#[inline]
fn tag_len(&self) -> usize {
self.aead_key.suite.aead.tag_len
}
#[inline]
fn confidentiality_limit(&self) -> u64 {
self.aead_key.suite.confidentiality_limit
}
#[inline]
fn integrity_limit(&self) -> u64 {
self.aead_key.suite.integrity_limit
}
}
/// A [crypto::PacketKey] that is based on a BoringSSL [bffi::EVP_AEAD_CTX].
#[derive(Copy, Clone)]
pub(crate) struct AeadKey {
suite: &'static CipherSuite,
key: Key,
ctx: bffi::EVP_AEAD_CTX,
}
impl Debug for AeadKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AeadKey")
.field("suite", self.suite)
.field("key", &self.key)
.finish()
}
}
unsafe impl Send for AeadKey {}
// EVP_AEAD_CTX_seal & EVP_AEAD_CTX_open allowed to be called concurrently on the same instance of EVP_AEAD_CTX
// https://github.com/google/boringssl/blob/master/include/openssl/aead.h#L278
unsafe impl Sync for AeadKey {}
impl AeadKey {
#[inline]
pub(crate) fn new(suite: &'static CipherSuite, key: Key) -> Result<Self> {
let ctx = suite.aead.new_aead_ctx(&key)?;
Ok(Self { suite, key, ctx })
}
#[inline]
pub(crate) fn seal_in_place(
&self,
nonce: &Nonce,
data: &mut [u8],
additional_data: &[u8],
) -> Result<()> {
let mut out_len = data.len() - self.suite.aead.tag_len;
unsafe {
map_result(bffi::EVP_AEAD_CTX_seal(
&self.ctx,
data.as_mut_ptr(),
&mut out_len,
data.len(),
nonce.as_ptr(),
nonce.len(),
data.as_ptr(),
out_len,
additional_data.as_ptr(),
additional_data.len(),
))?;
}
Ok(())
}
#[inline]
pub(crate) fn open_in_place(
&self,
nonce: &Nonce,
data: &mut [u8],
additional_data: &[u8],
) -> StdResult<usize, crypto::CryptoError> {
let mut out_len = match data.len().checked_sub(self.suite.aead.tag_len) {
Some(n) => n,
None => return Err(crypto::CryptoError {}),
};
unsafe {
map_result(bffi::EVP_AEAD_CTX_open(
&self.ctx,
data.as_mut_ptr(),
&mut out_len,
out_len,
nonce.as_ptr(),
nonce.len(),
data.as_ptr(),
data.len(),
additional_data.as_ptr(),
additional_data.len(),
))?;
}
Ok(out_len)
}
}
impl crypto::AeadKey for AeadKey {
#[inline]
fn seal(
&self,
data: &mut Vec<u8>,
additional_data: &[u8],
) -> StdResult<(), crypto::CryptoError> {
data.extend_from_slice(self.suite.aead.zero_tag().slice());
self.seal_in_place(&self.suite.aead.zero_nonce(), data, additional_data)?;
Ok(())
}
#[inline]
fn open<'a>(
&self,
data: &'a mut [u8],
additional_data: &[u8],
) -> StdResult<&'a mut [u8], crypto::CryptoError> {
let plain_len = self.open_in_place(&self.suite.aead.zero_nonce(), data, additional_data)?;
Ok(&mut data[..plain_len])
}
}

301
quinn-boring/src/lib.rs Normal file
View File

@ -0,0 +1,301 @@
#![allow(unused)]
mod aead;
mod alert;
mod alpn;
mod bffi_ext;
mod client;
mod error;
mod handshake_token;
mod hkdf;
mod hmac;
mod key;
mod macros;
mod retry;
mod secret;
mod server;
mod session_cache;
mod session_state;
mod suite;
mod version;
// Export the public interface.
pub use bffi_ext::*;
pub use client::Config as ClientConfig;
pub use error::{Error, Result};
pub use handshake_token::HandshakeTokenKey;
pub use hmac::HmacKey;
pub use server::Config as ServerConfig;
pub use session_cache::*;
pub use version::QuicVersion;
/// Information available from [quinn_proto::crypto::Session::handshake_data] once the handshake has completed.
#[derive(Clone, Debug)]
pub struct HandshakeData {
/// The negotiated application protocol, if ALPN is in use
///
/// Guaranteed to be set if a nonempty list of protocols was specified for this connection.
pub protocol: Option<Vec<u8>>,
/// The server name specified by the client, if any
///
/// Always `None` for outgoing connections
pub server_name: Option<String>,
}
pub mod helpers {
use super::*;
use quinn_proto::crypto;
use std::sync::Arc;
/// Create a server config with the given [`crypto::ServerConfig`]
///
/// Uses a randomized handshake token key.
pub fn server_config(crypto: Arc<dyn crypto::ServerConfig>) -> Result<quinn::ServerConfig> {
Ok(quinn::ServerConfig::new(
crypto,
Arc::new(HandshakeTokenKey::new()?),
))
}
/// Returns a default endpoint configuration for BoringSSL.
pub fn default_endpoint_config() -> quinn::EndpointConfig {
let mut cfg = quinn::EndpointConfig::new(Arc::new(HmacKey::sha256()));
cfg.supported_versions(QuicVersion::default_supported_versions());
cfg
}
/// Helper to construct an endpoint for use with outgoing connections only
///
/// Note that `addr` is the *local* address to bind to, which should usually be a wildcard
/// address like `0.0.0.0:0` or `[::]:0`, which allow communication with any reachable IPv4 or
/// IPv6 address respectively from an OS-assigned port.
///
/// Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard
/// IPv6 address on Windows will not by default be able to communicate with IPv4
/// addresses. Portable applications should bind an address that matches the family they wish to
/// communicate within.
#[cfg(feature = "runtime-tokio")]
pub fn client_endpoint(addr: std::net::SocketAddr) -> std::io::Result<quinn::Endpoint> {
let socket = std::net::UdpSocket::bind(addr)?;
quinn::Endpoint::new(
default_endpoint_config(),
None,
socket,
Arc::new(quinn::TokioRuntime),
)
}
/// Helper to construct an endpoint for use with both incoming and outgoing connections
///
/// Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard
/// IPv6 address on Windows will not by default be able to communicate with IPv4
/// addresses. Portable applications should bind an address that matches the family they wish to
/// communicate within.
#[cfg(feature = "runtime-tokio")]
pub fn server_endpoint(
config: quinn::ServerConfig,
addr: std::net::SocketAddr,
) -> std::io::Result<quinn::Endpoint> {
let socket = std::net::UdpSocket::bind(addr)?;
quinn::Endpoint::new(
default_endpoint_config(),
Some(config),
socket,
Arc::new(quinn::TokioRuntime),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Result;
use crate::secret::{Secret, Secrets};
use crate::suite::CipherSuite;
use bytes::BytesMut;
use hex_literal::hex;
use quinn_proto::crypto::PacketKey;
use quinn_proto::{ConnectionId, Side};
/// Copied from quiche.
#[test]
fn test_initial_keys_v1() -> Result<()> {
let dcid: &[u8] = &hex!("8394c8f03e515708");
let version = QuicVersion::V1;
let suite = CipherSuite::aes128_gcm_sha256();
let s = Secrets::initial(version, &ConnectionId::new(dcid), Side::Client)?;
let expected_enc_key: &[u8] = &hex!("1f369613dd76d5467730efcbe3b1a22d");
assert_eq!(
s.local.packet_key(version, suite)?.key().slice(),
expected_enc_key
);
let expected_enc_iv: &[u8] = &hex!("fa044b2f42a3fd3b46fb255c");
assert_eq!(
s.local.packet_key(version, suite)?.iv().slice(),
expected_enc_iv
);
let expected_enc_hdr_key: &[u8] = &hex!("9f50449e04a0e810283a1e9933adedd2");
assert_eq!(
s.local.header_key(version, suite)?.key().slice(),
expected_enc_hdr_key
);
let expected_dec_key: &[u8] = &hex!("cf3a5331653c364c88f0f379b6067e37");
assert_eq!(
s.remote.packet_key(version, suite)?.key().slice(),
expected_dec_key
);
let expected_dec_iv: &[u8] = &hex!("0ac1493ca1905853b0bba03e");
assert_eq!(
s.remote.packet_key(version, suite)?.iv().slice(),
expected_dec_iv
);
let expected_dec_hdr_key: &[u8] = &hex!("c206b8d9b9f0f37644430b490eeaa314");
assert_eq!(
s.remote.header_key(version, suite)?.key().slice(),
expected_dec_hdr_key
);
Ok(())
}
/// Copied from rustls.
#[test]
fn short_packet_header_protection() {
// https://www.rfc-editor.org/rfc/rfc9001.html#name-chacha20-poly1305-short-hea
const PN: u64 = 654360564;
const SECRET: &[u8] =
&hex!("9ac312a7f877468ebe69422748ad00a15443f18203a07d6060f688f30f21632b");
let version = QuicVersion::V1;
let suite = CipherSuite::chacha20_poly1305_sha256();
let secret = Secret::from(SECRET);
let hpk = secret
.header_key(version, suite)
.unwrap()
.as_crypto()
.unwrap();
let packet = secret.packet_key(version, suite).unwrap();
const PLAIN: &[u8] = &[0x42, 0x00, 0xbf, 0xf4, b'h', b'e', b'l', b'l', b'o'];
let mut buf = PLAIN.to_vec();
// Make space for the output tag.
buf.extend_from_slice(&[0u8; 16]);
packet.encrypt(PN, &mut buf, 4);
let pn_offset = 1;
hpk.encrypt(pn_offset, &mut buf);
const PROTECTED: &[u8] = &hex!("593b46220c4d504a9f1857793356400fc4a784ee309dff98b2");
assert_eq!(&buf, PROTECTED);
hpk.decrypt(pn_offset, &mut buf);
let (header, payload_tag) = buf.split_at(4);
let mut payload_tag = BytesMut::from(payload_tag);
packet.decrypt(PN, header, &mut payload_tag).unwrap();
let plain = payload_tag.as_ref();
assert_eq!(plain, &PLAIN[4..]);
}
/// Copied from rustls.
#[test]
fn key_update_test_vector() {
let version = QuicVersion::V1;
let suite = CipherSuite::aes128_gcm_sha256();
let mut secrets = Secrets {
version,
suite,
local: Secret::from(&hex!(
"b8767708f8772358a6ea9fc43e4add2c961b3f5287a6d1467ee0aeab33724dbf"
)),
remote: Secret::from(&hex!(
"42dc972140e0f2e39845b767613439dc6758ca43259b878506824eb1e438d855"
)),
};
secrets.update().unwrap();
let expected = Secrets {
version,
suite,
local: Secret::from(&hex!(
"42cac8c91cd5eb40682e432edf2d2be9f41a52ca6b22d8e6cdb1e8aca9061fce"
)),
remote: Secret::from(&hex!(
"eb7f5e2a123f407db499e361cae590d4d992e14b7ace03c244e0422115b6d38a"
)),
};
assert_eq!(expected, secrets);
}
#[test]
fn client_encrypt_header() {
let dcid = ConnectionId::new(&hex!("06b858ec6f80452b"));
let secrets = Secrets::initial(QuicVersion::V1, &dcid, Side::Client).unwrap();
let client = secrets.keys().unwrap().as_crypto().unwrap();
// Client (encrypt)
let mut packet: [u8; 51] = hex!(
"c0000000010806b858ec6f80452b0000402100c8fb7ffd97230e38b70d86e7ff148afdf88fc21c4426c7d1cec79914c8785757"
);
let packet_number = 0;
let packet_number_pos = 18;
let header_len = 19;
// Encrypt the payload.
client
.packet
.local
.encrypt(packet_number, &mut packet, header_len);
let expected_after_packet_encrypt: [u8; 51] = hex!(
"c0000000010806b858ec6f80452b0000402100f60e77fa2f629f9921fae64125c5632cf769d801a4693af6b949af37c2c45399"
);
assert_eq!(packet, expected_after_packet_encrypt);
// Encrypt the header.
client.header.local.encrypt(packet_number_pos, &mut packet);
let expected_after_header_encrypt: [u8; 51] = hex!(
"cd000000010806b858ec6f80452b000040210bf60e77fa2f629f9921fae64125c5632cf769d801a4693af6b949af37c2c45399"
);
assert_eq!(packet, expected_after_header_encrypt);
}
#[test]
fn server_decrypt_header() {
let dcid = ConnectionId::new(&hex!("06b858ec6f80452b"));
let secrets = Secrets::initial(QuicVersion::V1, &dcid, Side::Server).unwrap();
let server = secrets.keys().unwrap().as_crypto().unwrap();
let mut packet = BytesMut::from(
&hex!(
"c8000000010806b858ec6f80452b00004021be3ef50807b84191a196f760a6dad1e9d1c430c48952cba0148250c21c0a6a70e1"
)[..],
);
let packet_number = 0;
let packet_number_pos = 18;
let header_len = 19;
// Decrypt the header.
server.header.remote.decrypt(packet_number_pos, &mut packet);
let expected_header: [u8; 19] = hex!("c0000000010806b858ec6f80452b0000402100");
assert_eq!(packet[..header_len], expected_header);
// Decrypt the payload.
let mut header = packet;
let mut packet = header.split_off(header_len);
server
.packet
.remote
.decrypt(packet_number, &header, &mut packet)
.unwrap();
assert_eq!(packet[..], [0; 16]);
}
}

120
quinn-boring/src/macros.rs Normal file
View File

@ -0,0 +1,120 @@
macro_rules! bounded_array {
{$(
$(#[$struct_docs:meta])*
$vis:vis struct $struct_name:ident($max_len:ident)
),*} => {
$(
$(#[$struct_docs])*
#[derive(Copy, Clone, Eq, PartialEq)]
$vis struct $struct_name {
buf: [u8; Self::MAX_LEN],
len: u8,
}
#[allow(dead_code)]
impl $struct_name {
/// Maximum value allowed.
$vis const MAX_LEN: usize = $max_len;
/// Creates a new instance, taking ownership of the buffer.
#[inline]
$vis fn new(buf: [u8; Self::MAX_LEN], len: usize) -> Self {
Self { buf, len: len as _ }
}
/// Creates a new instance with an empty buffer of the given size.
#[inline]
$vis fn with_len(len: usize) -> Self {
Self::new([0u8; Self::MAX_LEN], len)
}
/// Creates a new instance, copying the buffer.
#[inline]
$vis fn from(input: &[u8]) -> Self {
assert!(input.len() <= Self::MAX_LEN);
let mut buf = [0u8; Self::MAX_LEN];
let len = input.len();
buf[..len].copy_from_slice(input);
Self::new(buf, len)
}
/// Creates a new instance with random contents.
#[inline]
$vis fn random() -> Self {
let mut buf = [0u8; Self::MAX_LEN];
rand::RngCore::fill_bytes(&mut rand::rng(), &mut buf);
Self::new(buf, Self::MAX_LEN)
}
/// Creates a new instance from the parsed hex string.
#[inline]
$vis fn parse_hex_string(
input: &str,
) -> crate::error::Result<Self> {
if input.len() % 2 != 0 {
return Err(crate::error::Error::invalid_input(
"hex string with odd length".to_string(),
));
}
let out_len = input.len() / 2;
if out_len > Self::MAX_LEN {
return Err(crate::error::Error::invalid_input(
"hex string value exceeds buffer size".to_string(),
));
}
let mut out = [0u8; Self::MAX_LEN];
let mut out_ix = 0;
let mut in_ix = 0;
while in_ix < input.len() {
let next_two_chars = &input[in_ix..in_ix + 2];
out[out_ix] = u8::from_str_radix(next_two_chars, 16).unwrap();
in_ix += 2;
out_ix += 1;
}
Ok($struct_name {
buf: out,
len: out_len as _,
})
}
/// Returns the length of the buffer.
#[inline]
$vis fn len(&self) -> usize {
self.len as _
}
/// Returns a slice of the buffer for its length.
#[inline]
$vis fn slice(&self) -> &[u8] {
&self.buf[..self.len as _]
}
/// Returns a mutable slice of the buffer for its length.
#[inline]
$vis fn slice_mut(&mut self) -> &mut [u8] {
&mut self.buf[..self.len as _]
}
/// Returns a raw pointer to the buffer.
#[inline]
$vis fn as_ptr(&self) -> *const u8 {
self.buf.as_ptr()
}
}
impl std::fmt::Debug for $struct_name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:02x?}", self.slice())
}
}
)*
}
}
pub(crate) use bounded_array;

98
quinn-boring/src/retry.rs Normal file
View File

@ -0,0 +1,98 @@
use crate::key::{AeadKey, Key, Nonce};
use crate::suite::CipherSuite;
use crate::{aead, QuicVersion};
use quinn_proto::ConnectionId;
const TAG_LEN: usize = aead::AES_GCM_TAG_LEN;
#[inline]
pub(crate) fn retry_tag(
version: &QuicVersion,
orig_dst_cid: &ConnectionId,
packet: &[u8],
) -> [u8; TAG_LEN] {
let suite = CipherSuite::aes128_gcm_sha256();
let key = Key::from(version.retry_integrity_key());
let nonce = Nonce::from(version.retry_integrity_nonce());
let key = AeadKey::new(suite, key).unwrap();
let mut pseudo_packet = Vec::with_capacity(packet.len() + orig_dst_cid.len() + 1);
pseudo_packet.push(orig_dst_cid.len() as u8);
pseudo_packet.extend_from_slice(orig_dst_cid);
pseudo_packet.extend_from_slice(packet);
// Encrypt using the packet as additional data.
let mut encrypted = Vec::from(&[0; TAG_LEN][..]);
key.seal_in_place(&nonce, &mut encrypted, &pseudo_packet)
.unwrap();
let tag_start = encrypted.len() - TAG_LEN;
// Now extract the tag that was written.
let mut tag = [0; TAG_LEN];
tag.copy_from_slice(&encrypted[tag_start..]);
tag
}
#[inline]
pub(crate) fn is_valid_retry(
version: &QuicVersion,
orig_dst_cid: &ConnectionId,
header: &[u8],
payload: &[u8],
) -> bool {
let tag_start = match payload.len().checked_sub(TAG_LEN) {
Some(x) => x,
None => return false,
};
let mut pseudo_packet =
Vec::with_capacity(header.len() + payload.len() + orig_dst_cid.len() + 1);
pseudo_packet.push(orig_dst_cid.len() as u8);
pseudo_packet.extend_from_slice(orig_dst_cid);
pseudo_packet.extend_from_slice(header);
let tag_start = tag_start + pseudo_packet.len();
pseudo_packet.extend_from_slice(payload);
let suite = CipherSuite::aes128_gcm_sha256();
let key = Key::from(version.retry_integrity_key());
let nonce = Nonce::from(version.retry_integrity_nonce());
let key = AeadKey::new(suite, key).unwrap();
let (aad, tag) = pseudo_packet.split_at_mut(tag_start);
key.open_in_place(&nonce, tag, aad).is_ok()
}
#[cfg(test)]
mod test {
use super::*;
use hex_literal::hex;
use quinn_proto::ConnectionId;
#[test]
fn test_is_valid_retry() {
let orig_dst_cid = ConnectionId::new(&hex!("e080ab63f82458c1fd4d64f66faa9216f3f8b481"));
let header = hex!("f0000000010884d5a4bdfc1811e108648f4abb039d0c0a");
let packet = hex!("e9088adb79f9"
"7eabc8b5c8e78f4cc23da7a9dfa43a48a9b2dedc00c3a928ce501e2067300f1be896c2bde90af634ea8a"
"7fd1bb7ffd7c5ba7087cdb8c2a060eb360017e850bf5d27b063eedffa9"
"dfcdb8ebb4499c60cd86a84a9b2a2adf");
assert!(is_valid_retry(
&QuicVersion::V1,
&orig_dst_cid,
&header,
&packet
))
}
#[test]
fn test_retry_tag() {
let orig_dst_cid = ConnectionId::new(&hex!("e080ab63f82458c1fd4d64f66faa9216f3f8b481"));
let packet = hex!("f0000000010884d5a4bdfc1811e108648f4abb039d0c0ae9088adb79f9"
"7eabc8b5c8e78f4cc23da7a9dfa43a48a9b2dedc00c3a928ce501e2067300f1be896c2bde90af634ea8a"
"7fd1bb7ffd7c5ba7087cdb8c2a060eb360017e850bf5d27b063eedffa9");
let expected = hex!("dfcdb8ebb4499c60cd86a84a9b2a2adf");
let tag = retry_tag(&QuicVersion::V1, &orig_dst_cid, &packet);
assert_eq!(expected, tag)
}
}

207
quinn-boring/src/secret.rs Normal file
View File

@ -0,0 +1,207 @@
use crate::error::Result;
use crate::hkdf;
use crate::key::{HeaderKey, KeyPair, Keys, PacketKey};
use crate::macros::bounded_array;
use crate::suite::CipherSuite;
use crate::version::QuicVersion;
use quinn_proto::{ConnectionId, Side};
const MAX_SECRET_LEN: usize = hkdf::DIGEST_BLOCK_LEN;
bounded_array! {
/// A buffer that can fit the largest master secret.
pub(crate) struct Secret(MAX_SECRET_LEN)
}
impl Secret {
/// Performs an in-place key update.
#[inline]
pub(crate) fn update(&mut self, version: QuicVersion, suite: &CipherSuite) -> Result<()> {
let out = &mut [0u8; Secret::MAX_LEN][..self.len()];
suite
.hkdf
.expand_label(self.slice(), version.key_update_label(), out)?;
self.slice_mut().copy_from_slice(out);
Ok(())
}
#[inline]
pub(crate) fn header_key(
&self,
version: QuicVersion,
suite: &'static CipherSuite,
) -> Result<HeaderKey> {
HeaderKey::new(version, suite, self)
}
#[inline]
pub(crate) fn packet_key(
&self,
version: QuicVersion,
suite: &'static CipherSuite,
) -> Result<PacketKey> {
PacketKey::new(version, suite, self)
}
}
/// A secret pair for reading (decryption) and writing (encryption).
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct Secrets {
pub(crate) version: QuicVersion,
pub(crate) suite: &'static CipherSuite,
pub(crate) local: Secret,
pub(crate) remote: Secret,
}
impl Secrets {
/// Creates the Quic initial secrets.
/// See <https://datatracker.ietf.org/doc/html/rfc9001#name-initial-secrets>.
#[inline]
pub(crate) fn initial(
version: QuicVersion,
dst_cid: &ConnectionId,
side: Side,
) -> Result<Secrets> {
// Initial secrets always use AES-128-GCM and SHA256.
let suite = CipherSuite::aes128_gcm_sha256();
// Generate the initial secret.
let salt = version.initial_salt();
let mut initial_secret = [0u8; Secret::MAX_LEN];
let initial_secret_len = suite.hkdf.extract(salt, dst_cid, &mut initial_secret)?;
let initial_secret = &initial_secret[..initial_secret_len];
// Use the appropriate secret labels for "this" side of the connection.
const CLIENT_LABEL: &[u8] = b"client in";
const SERVER_LABEL: &[u8] = b"server in";
let (local_label, remote_label) = match side {
Side::Client => (CLIENT_LABEL, SERVER_LABEL),
Side::Server => (SERVER_LABEL, CLIENT_LABEL),
};
let len = suite.hkdf.digest_size();
let mut local = Secret::with_len(len);
suite
.hkdf
.expand_label(initial_secret, local_label, local.slice_mut())?;
let mut remote = Secret::with_len(len);
suite
.hkdf
.expand_label(initial_secret, remote_label, remote.slice_mut())?;
Ok(Secrets {
version,
suite,
local,
remote,
})
}
#[inline]
pub(crate) fn keys(&self) -> Result<Keys> {
Ok(Keys {
header: self.header_keys()?,
packet: self.packet_keys()?,
})
}
#[inline]
pub(crate) fn header_keys(&self) -> Result<KeyPair<HeaderKey>> {
Ok(KeyPair {
local: self.local.header_key(self.version, self.suite)?,
remote: self.remote.header_key(self.version, self.suite)?,
})
}
#[inline]
pub(crate) fn packet_keys(&self) -> Result<KeyPair<PacketKey>> {
Ok(KeyPair {
local: self.local.packet_key(self.version, self.suite)?,
remote: self.remote.packet_key(self.version, self.suite)?,
})
}
#[inline]
pub(crate) fn update(&mut self) -> Result<()> {
// Update the secrets.
self.local.update(self.version, self.suite)?;
self.remote.update(self.version, self.suite)?;
Ok(())
}
#[inline]
pub(crate) fn next_packet_keys(&mut self) -> Result<KeyPair<PacketKey>> {
// Get the current keys.
let keys = self.packet_keys()?;
// Update the secrets.
self.update()?;
Ok(keys)
}
}
pub(crate) struct SecretsBuilder {
pub(crate) version: QuicVersion,
pub(crate) suite: Option<&'static CipherSuite>,
pub(crate) local_secret: Option<Secret>,
pub(crate) remote_secret: Option<Secret>,
}
impl SecretsBuilder {
pub(crate) fn new(version: QuicVersion) -> Self {
Self {
version,
suite: None,
local_secret: None,
remote_secret: None,
}
}
pub(crate) fn set_suite(&mut self, suite: &'static CipherSuite) {
if let Some(prev) = self.suite {
// Make sure it doesn't change once set.
assert_eq!(prev, suite);
return;
}
self.suite = Some(suite)
}
pub(crate) fn set_remote_secret(&mut self, secret: Secret) {
if let Some(prev) = &self.remote_secret {
// Make sure it doesn't change once set.
assert_eq!(*prev, secret);
return;
}
self.remote_secret = Some(secret)
}
pub(crate) fn set_local_secret(&mut self, secret: Secret) {
if let Some(prev) = &self.local_secret {
// Make sure it doesn't change once set.
assert_eq!(*prev, secret);
return;
}
self.local_secret = Some(secret)
}
pub(crate) fn build(&self) -> Option<Secrets> {
if let Some(suite) = self.suite {
if let Some(local) = self.local_secret {
if let Some(remote) = self.remote_secret {
return Some(Secrets {
version: self.version,
suite,
local,
remote,
});
}
}
}
None
}
}

View File

@ -0,0 +1,313 @@
use crate::alpn::AlpnProtocols;
use crate::bffi_ext::QuicSsl;
use crate::error::{map_result, Result};
use crate::secret::Secrets;
use crate::session_state::{SessionState, QUIC_METHOD};
use crate::version::QuicVersion;
use crate::{retry, QuicSslContext};
use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslVersion};
use boring_sys as bffi;
use bytes::{Bytes, BytesMut};
use foreign_types_shared::ForeignType;
use quinn_proto::{
crypto, transport_parameters::TransportParameters, ConnectionId, Side, TransportError,
};
use std::any::Any;
use std::ffi::{c_int, c_uint, c_void};
use std::result::Result as StdResult;
use std::slice;
use std::sync::Arc;
use std::sync::LazyLock;
/// Configuration for a server-side QUIC. Wraps around a BoringSSL [SslContext].
pub struct Config {
ctx: SslContext,
alpn_protocols: AlpnProtocols,
}
impl Config {
pub fn new() -> Result<Self> {
let mut builder = SslContextBuilder::new(SslMethod::tls())?;
// QUIC requires TLS 1.3.
builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;
builder.set_default_verify_paths()?;
// We build the context early, since we are not allowed to further mutate the context
// in start_session.
let mut ctx = builder.build();
// Disable verification of the client by default.
ctx.verify_peer(false);
// By default, enable early data (used for 0-RTT).
ctx.enable_early_data(true);
// Configure default ALPN protocols accepted by the server.QUIC requires ALPN be
// configured (see https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1).
ctx.set_alpn_select_cb(Some(Session::alpn_select_callback));
// Set the callback for receipt of the Server Name Indication (SNI) extension.
ctx.set_server_name_cb(Some(Session::server_name_callback));
// Set callbacks for the SessionState.
ctx.set_quic_method(&QUIC_METHOD)?;
ctx.set_info_callback(Some(SessionState::info_callback));
ctx.set_options(bffi::SSL_OP_CIPHER_SERVER_PREFERENCE as u32);
Ok(Self {
ctx,
alpn_protocols: AlpnProtocols::default(),
})
}
/// Returns the underlying [SslContext] backing all created sessions.
pub fn ctx(&self) -> &SslContext {
&self.ctx
}
/// Returns the underlying [SslContext] backing all created sessions. Wherever possible use
/// the provided methods to modify settings rather than accessing this directly.
///
/// Care should be taken to avoid overriding required behavior. In particular, this
/// configuration will set callbacks for QUIC events, alpn selection, server name,
/// as well as info and key logging.
pub fn ctx_mut(&mut self) -> &mut SslContext {
&mut self.ctx
}
/// Sets whether or not the peer certificate should be verified. If `true`, any error
/// during verification will be fatal. If not called, verification of the client is
/// disabled by default.
pub fn verify_peer(&mut self, verify: bool) {
self.ctx.verify_peer(verify)
}
/// Sets the ALPN protocols that will be accepted by the server. QUIC requires that
/// ALPN be used (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
///
/// If this method is not called, the server will default to accepting "h3".
pub fn set_alpn(&mut self, alpn_protocols: &[Vec<u8>]) -> Result<()> {
self.alpn_protocols = alpn_protocols.into();
Ok(())
}
}
impl crypto::ServerConfig for Config {
fn initial_keys(
&self,
version: u32,
dst_cid: &ConnectionId,
) -> StdResult<crypto::Keys, crypto::UnsupportedVersion> {
let version = QuicVersion::parse(version)?;
let secrets = Secrets::initial(version, dst_cid, Side::Server).unwrap();
Ok(secrets.keys().unwrap().as_crypto().unwrap())
}
fn retry_tag(&self, version: u32, orig_dst_cid: &ConnectionId, packet: &[u8]) -> [u8; 16] {
let version = QuicVersion::parse(version).unwrap();
retry::retry_tag(&version, orig_dst_cid, packet)
}
fn start_session(
self: Arc<Self>,
version: u32,
params: &TransportParameters,
) -> Box<dyn crypto::Session> {
let version = QuicVersion::parse(version).unwrap();
Session::new(self, version, params).unwrap()
}
}
static SESSION_INDEX: LazyLock<c_int> = LazyLock::new(|| unsafe {
bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
});
/// The [crypto::Session] implementation for BoringSSL.
struct Session {
state: Box<SessionState>,
alpn: AlpnProtocols,
handshake_data_available: bool,
handshake_data_sent: bool,
}
impl Session {
fn new(
cfg: Arc<Config>,
version: QuicVersion,
params: &TransportParameters,
) -> Result<Box<Self>> {
let mut ssl = Ssl::new(&cfg.ctx).unwrap();
// Configure the TLS extension based on the QUIC version used.
ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());
// Configure the SSL to be a server.
ssl.set_accept_state();
// Set the transport parameters.
ssl.set_quic_transport_params(&encode_params(params))
.unwrap();
// Need to se
ssl.set_quic_early_data_context(b"quinn-boring").unwrap();
let mut session = Box::new(Self {
state: SessionState::new(ssl, Side::Server, version)?,
alpn: cfg.alpn_protocols.clone(),
handshake_data_available: false,
handshake_data_sent: false,
});
// Register the instance in SSL ex_data. This allows the static callbacks to
// reference the instance.
unsafe {
map_result(bffi::SSL_set_ex_data(
session.state.ssl.as_ptr(),
*SESSION_INDEX,
&mut *session as *mut Self as *mut _,
))?;
}
Ok(session)
}
/// Server-side only callback from BoringSSL to select the ALPN protocol.
#[inline]
fn on_alpn_select<'a>(&mut self, offered: &'a [u8]) -> Result<&'a [u8]> {
// Indicate that we now have handshake data available.
self.handshake_data_available = true;
self.alpn.select(offered)
}
/// Server-side only callback from BoringSSL indicating that the Server Name Indication (SNI)
/// extension in the client hello was successfully parsed.
#[inline]
fn on_server_name(&mut self, _: *mut c_int) -> c_int {
// Indicate that we now have handshake data available.
self.handshake_data_available = true;
// SSL_TLSEXT_ERR_OK causes the server_name extension to be acked in
// ServerHello.
bffi::SSL_TLSEXT_ERR_OK
}
}
// Raw callbacks from BoringSSL
impl Session {
#[inline]
fn get_instance(ssl: *const bffi::SSL) -> &'static mut Session {
unsafe {
let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
if data.is_null() {
panic!("BUG: Session instance missing")
}
&mut *(data as *mut Session)
}
}
extern "C" fn alpn_select_callback(
ssl: *mut bffi::SSL,
out: *mut *const u8,
out_len: *mut u8,
in_: *const u8,
in_len: c_uint,
_: *mut c_void,
) -> c_int {
let inst = Self::get_instance(ssl);
unsafe {
let protos = slice::from_raw_parts(in_, in_len as _);
match inst.on_alpn_select(protos) {
Ok(proto) => {
*out = proto.as_ptr() as _;
*out_len = proto.len() as _;
bffi::SSL_TLSEXT_ERR_OK
}
Err(_) => bffi::SSL_TLSEXT_ERR_ALERT_FATAL,
}
}
}
extern "C" fn server_name_callback(
ssl: *mut bffi::SSL,
out_alert: *mut c_int,
_: *mut c_void,
) -> c_int {
let inst = Self::get_instance(ssl);
inst.on_server_name(out_alert)
}
}
impl crypto::Session for Session {
fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
self.state.initial_keys(dcid, side)
}
fn handshake_data(&self) -> Option<Box<dyn Any>> {
self.state.handshake_data()
}
fn peer_identity(&self) -> Option<Box<dyn Any>> {
self.state.peer_identity()
}
fn early_crypto(&self) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
self.state.early_crypto()
}
fn early_data_accepted(&self) -> Option<bool> {
None
}
fn is_handshaking(&self) -> bool {
self.state.is_handshaking()
}
fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<bool, TransportError> {
self.state.read_handshake(plaintext)?;
// Only indicate that handshake data is available once.
if !self.handshake_data_sent && self.handshake_data_available {
self.handshake_data_sent = true;
return Ok(true);
}
Ok(false)
}
fn transport_parameters(&self) -> StdResult<Option<TransportParameters>, TransportError> {
self.state.transport_parameters()
}
fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
self.state.write_handshake(buf)
}
fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
self.state.next_1rtt_keys()
}
fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool {
self.state.is_valid_retry(orig_dst_cid, header, payload)
}
fn export_keying_material(
&self,
output: &mut [u8],
label: &[u8],
context: &[u8],
) -> StdResult<(), crypto::ExportKeyingMaterialError> {
self.state.export_keying_material(output, label, context)
}
}
fn encode_params(params: &TransportParameters) -> Bytes {
let mut out = BytesMut::with_capacity(128);
params.write(&mut out);
out.freeze()
}

View File

@ -0,0 +1,122 @@
use crate::error::Result;
use crate::{Error, QuicSslSession};
use boring::ssl::{SslContextRef, SslSession};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use lru::LruCache;
use quinn_proto::{transport_parameters::TransportParameters, Side};
use std::num::NonZeroUsize;
use std::sync::Mutex;
/// A client-side Session cache for the BoringSSL crypto provider.
pub trait SessionCache: Send + Sync {
/// Adds the given value to the session cache.
fn put(&self, key: Bytes, value: Bytes);
/// Returns the cached session, if it exists.
fn get(&self, key: Bytes) -> Option<Bytes>;
/// Removes the cached session, if it exists.
fn remove(&self, key: Bytes);
/// Removes all entries from the cache.
fn clear(&self);
}
/// A utility for combining an [SslSession] and server [TransportParameters] as a
/// [SessionCache] entry.
pub struct Entry {
pub session: SslSession,
pub params: TransportParameters,
}
impl Entry {
/// Encodes this [Entry] into a [SessionCache] value.
pub fn encode(&self) -> Result<Bytes> {
let mut out = BytesMut::with_capacity(2048);
// Split the buffer in two: the length prefix buffer and the encoded session buffer.
// This will be O(1) as both will refer to the same underlying buffer.
let mut encoded = out.split_off(8);
// Store the session in the second buffer.
self.session.encode(&mut encoded)?;
// Go back and write the length to the first buffer.
out.put_u64(encoded.len() as u64);
// Unsplit to merge the two buffers back together. This will be O(1) since
// the buffers are already contiguous in memory.
out.unsplit(encoded);
// Now add the transport parameters.
out.reserve(128);
let mut encoded = out.split_off(out.len() + 8);
self.params.write(&mut encoded);
out.put_u64(encoded.len() as u64);
out.unsplit(encoded);
Ok(out.freeze())
}
/// Decodes a [SessionCache] value into an [Entry].
pub fn decode(ctx: &SslContextRef, mut encoded: Bytes) -> Result<Self> {
// Decode the session.
let len = encoded.get_u64() as usize;
let mut encoded_session = encoded.split_to(len);
let session = SslSession::decode(ctx, &mut encoded_session)?;
// Decode the transport parameters.
let len = encoded.get_u64() as usize;
let mut encoded_params = encoded.split_to(len);
let params = TransportParameters::read(Side::Client, &mut encoded_params).map_err(|e| {
Error::invalid_input(format!("failed parsing cached transport parameters: {e:?}"))
})?;
Ok(Self { session, params })
}
}
/// A [SessionCache] implementation that will never cache anything. Requires no storage.
pub struct NoSessionCache;
impl SessionCache for NoSessionCache {
fn put(&self, _: Bytes, _: Bytes) {}
fn get(&self, _: Bytes) -> Option<Bytes> {
None
}
fn remove(&self, _: Bytes) {}
fn clear(&self) {}
}
pub struct SimpleCache {
cache: Mutex<LruCache<Bytes, Bytes>>,
}
impl SimpleCache {
pub fn new(num_entries: usize) -> Self {
SimpleCache {
cache: Mutex::new(LruCache::new(NonZeroUsize::new(num_entries).unwrap())),
}
}
}
impl SessionCache for SimpleCache {
fn put(&self, key: Bytes, value: Bytes) {
let _ = self.cache.lock().unwrap().put(key, value);
}
fn get(&self, key: Bytes) -> Option<Bytes> {
self.cache.lock().unwrap().get(&key).cloned()
}
fn remove(&self, key: Bytes) {
let _ = self.cache.lock().unwrap().pop(&key);
}
fn clear(&self) {
self.cache.lock().unwrap().clear()
}
}

View File

@ -0,0 +1,552 @@
use crate::alert::Alert;
use crate::error::{map_cb_result, map_result, Result};
use crate::secret::{Secret, Secrets, SecretsBuilder};
use crate::suite::CipherSuite;
use crate::{retry, Error, HandshakeData, Level, QuicSsl, QuicVersion, SslError};
use boring::error::ErrorStack;
use boring::ssl::{NameType, Ssl};
use boring_sys as bffi;
use bytes::{Buf, BytesMut};
use foreign_types_shared::ForeignType;
use quinn_proto::{
crypto, transport_parameters::TransportParameters, ConnectionId, Side, TransportError,
};
use std::any::Any;
use std::ffi::c_int;
use std::io::Cursor;
use std::result::Result as StdResult;
use std::slice;
use std::sync::LazyLock;
use tracing::{error, trace, warn};
pub(crate) static QUIC_METHOD: bffi::SSL_QUIC_METHOD = bffi::SSL_QUIC_METHOD {
set_read_secret: Some(SessionState::set_read_secret_callback),
set_write_secret: Some(SessionState::set_write_secret_callback),
add_handshake_data: Some(SessionState::add_handshake_data_callback),
flush_flight: Some(SessionState::flush_flight_callback),
send_alert: Some(SessionState::send_alert_callback),
};
static SESSION_INDEX: LazyLock<c_int> = LazyLock::new(|| unsafe {
bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
});
pub(crate) struct SessionState {
pub(crate) ssl: Ssl,
pub(crate) version: QuicVersion,
/// Indicates that early data was rejected in the last call to [Self::read_handshake].
pub(crate) early_data_rejected: bool,
side: Side,
alert: Option<TransportError>,
next_secrets: Option<Secrets>,
keys_updated: bool,
read_level: Level,
write_level: Level,
levels: [LevelState; Level::NUM_LEVELS],
handshaking: bool,
}
impl SessionState {
pub(crate) fn new(ssl: Ssl, side: Side, version: QuicVersion) -> Result<Box<Self>> {
let levels = [
LevelState::new(version, Level::Initial, &ssl),
LevelState::new(version, Level::EarlyData, &ssl),
LevelState::new(version, Level::Handshake, &ssl),
LevelState::new(version, Level::Application, &ssl),
];
let mut state = Box::new(Self {
ssl,
version,
side,
alert: None,
next_secrets: None,
keys_updated: false,
read_level: Level::Initial,
write_level: Level::Initial,
levels,
early_data_rejected: false,
handshaking: true,
});
// Registers this instance as ex data on the underlying Ssl in order to support
// BoringSSL callbacks to this instance.
unsafe {
map_result(bffi::SSL_set_ex_data(
state.ssl.as_ptr(),
*SESSION_INDEX,
&mut *state as *mut Self as *mut _,
))?;
}
Ok(state)
}
#[inline]
fn level_state(&self, level: Level) -> &LevelState {
&self.levels[level as usize]
}
#[inline]
fn level_state_mut(&mut self, level: Level) -> &mut LevelState {
&mut self.levels[level as usize]
}
#[inline]
pub(crate) fn is_handshaking(&self) -> bool {
self.handshaking
}
#[inline]
pub(crate) fn handshake_data(&self) -> Option<Box<dyn Any>> {
let sni_name = if self.side.is_server() {
self.ssl
.servername(NameType::HOST_NAME)
.map(|server_name| server_name.to_string())
} else {
// Server name does not apply to the client.
None
};
let alpn_protocol = self.ssl.selected_alpn_protocol().map(Vec::from);
if sni_name.is_none() && alpn_protocol.is_none() {
None
} else {
Some(Box::new(HandshakeData {
protocol: alpn_protocol,
server_name: sni_name,
}))
}
}
#[inline]
pub(crate) fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
self.next_secrets
.as_mut()
.map(|secrets| secrets.next_packet_keys().unwrap().as_crypto().unwrap())
}
#[inline]
pub(crate) fn transport_parameters(
&self,
) -> StdResult<Option<TransportParameters>, TransportError> {
match self.ssl.get_peer_quic_transport_params() {
Some(params) => {
let params = TransportParameters::read(self.side, &mut Cursor::new(params))
.map_err(|e| TransportError {
code: Alert::handshake_failure().into(),
frame: None,
reason: format!("failed parsing transport params: {e:?}"),
})?;
Ok(Some(params))
}
None => Ok(None),
}
}
#[inline]
pub(crate) fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<(), TransportError> {
let ssl_err = self.ssl.provide_quic_data(self.read_level, plaintext);
self.check_alert()?;
self.check_ssl_error(ssl_err)?;
self.advance_handshake()
}
#[inline]
pub(crate) fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
// Write all available data at the current write level.
let write_state = self.level_state_mut(self.write_level);
if write_state.write_buffer.has_remaining() {
buf.extend_from_slice(&write_state.write_buffer);
write_state.write_buffer.clear();
}
// Advance to the next write level.
let ssl_engine_write_level = self.ssl.quic_write_level();
let next_write_level = self.write_level.next();
if next_write_level != self.write_level && next_write_level <= ssl_engine_write_level {
self.write_level = next_write_level;
// Indicate that we're updating the keys.
self.keys_updated = true;
}
let out = if self.keys_updated {
self.keys_updated = false;
if self.next_secrets.is_some() {
// Once we've returned the application secrets, stop sending key updates.
None
} else {
// Determine if we're transitioning to the application-level keys.
let is_app = self.write_level == Level::Application;
// Build the secrets.
let secrets = self
.level_state(self.write_level)
.builder
.build()
.unwrap_or_else(|| {
panic!("failed building secrets for level {:?}", self.write_level)
});
if is_app {
// We've transitioned to the application level, we need to set the
// next (i.e. application) secrets for use from next_1rtt_keys.
// Copy the secrets and advance them to the next application secrets.
let mut next_app_secrets = secrets;
next_app_secrets.update().unwrap();
self.next_secrets = Some(next_app_secrets);
}
Some(secrets.keys().unwrap())
}
} else {
None
};
out.map(|keys| keys.as_crypto().unwrap())
}
#[inline]
pub(crate) fn is_valid_retry(
&self,
orig_dst_cid: &ConnectionId,
header: &[u8],
payload: &[u8],
) -> bool {
retry::is_valid_retry(&self.version, orig_dst_cid, header, payload)
}
#[inline]
pub(crate) fn peer_identity(&self) -> Option<Box<dyn Any>> {
todo!()
}
#[inline]
pub(crate) fn early_crypto(
&self,
) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
let builder = &self.level_state(Level::EarlyData).builder;
let version = builder.version;
let suite = builder.suite?;
let early_secret = match self.side {
Side::Client => builder.local_secret?,
Side::Server => builder.remote_secret?,
};
let header_key = early_secret
.header_key(version, suite)
.unwrap()
.as_crypto()
.unwrap();
let packet_key = Box::new(early_secret.packet_key(version, suite).unwrap());
Some((header_key, packet_key))
}
#[inline]
pub(crate) fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
let secrets = Secrets::initial(self.version, dcid, side).unwrap();
secrets.keys().unwrap().as_crypto().unwrap()
}
#[inline]
pub(crate) fn export_keying_material(
&self,
output: &mut [u8],
label: &[u8],
context: &[u8],
) -> StdResult<(), crypto::ExportKeyingMaterialError> {
self.ssl
.export_keyring_material(output, label, context)
.map_err(|_| crypto::ExportKeyingMaterialError {})
}
#[inline]
pub(crate) fn advance_handshake(&mut self) -> StdResult<(), TransportError> {
self.early_data_rejected = false;
if self.handshaking {
let rc = self.ssl.do_handshake();
// Update the state of the handshake.
self.handshaking = self.ssl.is_handshaking();
self.check_alert()?;
self.check_ssl_error(rc)?;
}
if !self.handshaking {
let ssl_err = self.ssl.process_post_handshake();
self.check_alert()?;
return self.check_ssl_error(ssl_err);
}
Ok(())
}
#[inline]
pub(crate) fn check_alert(&self) -> StdResult<(), TransportError> {
if let Some(alert) = &self.alert {
return Err(alert.clone());
}
Ok(())
}
#[inline]
pub(crate) fn check_ssl_error(&mut self, ssl_err: SslError) -> StdResult<(), TransportError> {
match ssl_err.value() {
bffi::SSL_ERROR_NONE => Ok(()),
bffi::SSL_ERROR_WANT_READ
| bffi::SSL_ERROR_WANT_WRITE
| bffi::SSL_ERROR_PENDING_SESSION
| bffi::SSL_ERROR_PENDING_CERTIFICATE
| bffi::SSL_ERROR_PENDING_TICKET
| bffi::SSL_ERROR_WANT_X509_LOOKUP
| bffi::SSL_ERROR_WANT_PRIVATE_KEY_OPERATION
| bffi::SSL_ERROR_WANT_CERTIFICATE_VERIFY => {
// Not an error - retry when we get more data from the peer.
trace!("SSL:{}", ssl_err.get_description());
Ok(())
}
bffi::SSL_ERROR_EARLY_DATA_REJECTED => {
// Reset the state to allow retry with 1-RTT.
self.ssl.reset_early_rejected_data();
// Indicate that the early data has been rejected for the current handshake.
self.early_data_rejected = true;
Ok(())
}
_ => {
// Everything else is fatal.
let reason = if ssl_err.value() == bffi::SSL_ERROR_SSL {
// Error occurred within the SSL library. Get details from the ErrorStack.
format!("{}: {:?}", ssl_err, ErrorStack::get())
} else {
format!("{ssl_err}")
};
let mut err: TransportError = Alert::handshake_failure().into();
err.reason = reason;
Err(err)
}
}
}
}
// BoringSSL event handlers.
impl SessionState {
/// Callback from BoringSSL that configures the read secret and cipher suite for the given
/// encryption level. If an error is returned, the handshake is terminated with an error.
/// This function will be called at most once per encryption level.
#[inline]
fn on_set_read_secret(
&mut self,
level: Level,
suite: &'static CipherSuite,
secret: Secret,
) -> Result<()> {
// Store the secret.
let builder = &mut self.level_state_mut(level).builder;
builder.set_suite(suite);
builder.set_remote_secret(secret);
// Advance the currently active read level.
self.read_level = level;
// Indicate that the next call to write_handshake should generate new keys.
self.keys_updated = true;
Ok(())
}
/// Callback from BoringSSL that configures the write secret and cipher suite for the given
/// encryption level. If an error is returned, the handshake is terminated with an error.
/// This function will be called at most once per encryption level.
#[inline]
fn on_set_write_secret(
&mut self,
level: Level,
suite: &'static CipherSuite,
secret: Secret,
) -> Result<()> {
// Store the secret.
let builder = &mut self.level_state_mut(level).builder;
builder.set_suite(suite);
builder.set_local_secret(secret);
Ok(())
}
/// Callback from BoringSSL that adds handshake data to the current flight at the given
/// encryption level. If an error is returned, the handshake is terminated with an error.
#[inline]
fn on_add_handshake_data(&mut self, level: Level, data: &[u8]) -> Result<()> {
if level < self.write_level {
return Err(Error::other(format!(
"add_handshake_data for previous write level {level:?}"
)));
}
// Make sure we don't exceed the buffer capacity for the level.
let state = self.level_state_mut(level);
if state.write_buffer.len() + data.len() > state.write_buffer.capacity() {
return Err(Error::other(format!(
"add_handshake_data exceeded buffer capacity for level {level:?}"
)));
}
// Add the message to the level.
state.write_buffer.extend_from_slice(data);
Ok(())
}
/// Callback from BoringSSL called when the current flight is complete and should be
/// written to the transport. Note a flight may contain data at several
/// encryption levels.
#[inline]
fn on_flush_flight(&mut self) -> Result<()> {
Ok(())
}
/// Callback from BoringSSL that sends a fatal alert at the specified encryption level.
#[inline]
fn on_send_alert(&mut self, _: Level, alert: Alert) -> Result<()> {
self.alert = Some(alert.into());
Ok(())
}
/// Callback from BoringSSL to handle (i.e. log) info events.
fn on_info(&self, type_: c_int, value: c_int) {
if type_ & bffi::SSL_CB_LOOP > 0 {
trace!("SSL:ACCEPT_LOOP:{}", self.ssl.state_string());
} else if type_ & bffi::SSL_CB_ALERT > 0 {
let prefix = if type_ & bffi::SSL_CB_READ > 0 {
"SSL:ALERT:READ:"
} else {
"SSL:ALERT:WRITE:"
};
if ((type_ & 0xF0) >> 8) == bffi::SSL3_AL_WARNING {
warn!("{}{}", prefix, self.ssl.state_string());
} else {
error!("{}{}", prefix, self.ssl.state_string());
}
} else if type_ & bffi::SSL_CB_EXIT > 0 {
if value == 1 {
trace!("SSL:ACCEPT_EXIT_OK:{}", self.ssl.state_string());
} else {
// Not necessarily an actual error. It could just require additional
// data from the other side.
trace!("SSL:ACCEPT_EXIT_FAIL:{}", self.ssl.state_string());
}
} else if type_ & bffi::SSL_CB_HANDSHAKE_START > 0 {
trace!("SSL:HANDSHAKE_START:{}", self.ssl.state_string());
} else if type_ & bffi::SSL_CB_HANDSHAKE_DONE > 0 {
trace!("SSL:HANDSHAKE_DONE:{}", self.ssl.state_string());
} else {
warn!(
"SSL:unknown event type {}:{}",
type_,
self.ssl.state_string()
);
}
}
}
// Raw callbacks from BoringSSL
impl SessionState {
/// Called by the static callbacks to retrieve the instance pointer.
#[inline]
fn get_instance(ssl: *const bffi::SSL) -> &'static mut SessionState {
unsafe {
let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
if data.is_null() {
panic!("BUG: SessionState instance missing")
}
&mut *(data as *mut SessionState)
}
}
extern "C" fn set_read_secret_callback(
ssl: *mut bffi::SSL,
level: bffi::ssl_encryption_level_t,
cipher: *const bffi::SSL_CIPHER,
secret: *const u8,
secret_len: usize,
) -> c_int {
let inst = Self::get_instance(ssl);
let level: Level = level.into();
let secret = unsafe { slice::from_raw_parts(secret, secret_len) };
let suite = CipherSuite::from_cipher(cipher).unwrap();
let secret = Secret::from(secret);
map_cb_result(inst.on_set_read_secret(level, suite, secret))
}
extern "C" fn set_write_secret_callback(
ssl: *mut bffi::SSL,
level: bffi::ssl_encryption_level_t,
cipher: *const bffi::SSL_CIPHER,
secret: *const u8,
secret_len: usize,
) -> c_int {
let inst = Self::get_instance(ssl);
let level: Level = level.into();
let secret = unsafe { slice::from_raw_parts(secret, secret_len) };
let suite = CipherSuite::from_cipher(cipher).unwrap();
let secret = Secret::from(secret);
map_cb_result(inst.on_set_write_secret(level, suite, secret))
}
extern "C" fn add_handshake_data_callback(
ssl: *mut bffi::SSL,
level: bffi::ssl_encryption_level_t,
data: *const u8,
len: usize,
) -> c_int {
let inst = Self::get_instance(ssl);
let level: Level = level.into();
let data = unsafe { slice::from_raw_parts(data, len) };
map_cb_result(inst.on_add_handshake_data(level, data))
}
extern "C" fn flush_flight_callback(ssl: *mut bffi::SSL) -> c_int {
let inst = Self::get_instance(ssl);
map_cb_result(inst.on_flush_flight())
}
extern "C" fn send_alert_callback(
ssl: *mut bffi::SSL,
level: bffi::ssl_encryption_level_t,
alert: u8,
) -> c_int {
let inst = Self::get_instance(ssl);
let level: Level = level.into();
map_cb_result(inst.on_send_alert(level, Alert::from(alert)))
}
pub(crate) extern "C" fn info_callback(ssl: *const bffi::SSL, type_: c_int, value: c_int) {
let inst = Self::get_instance(ssl);
inst.on_info(type_, value);
}
}
pub(crate) struct LevelState {
pub(crate) builder: SecretsBuilder,
pub(crate) write_buffer: BytesMut,
}
impl LevelState {
#[inline]
fn new(version: QuicVersion, level: Level, ssl: &Ssl) -> Self {
let capacity = ssl.quic_max_handshake_flight_len(level);
Self {
builder: SecretsBuilder::new(version),
write_buffer: BytesMut::with_capacity(capacity),
}
}
}

105
quinn-boring/src/suite.rs Normal file
View File

@ -0,0 +1,105 @@
use crate::aead::Aead;
use crate::error::{Error, Result};
use crate::hkdf::Hkdf;
use boring_sys as bffi;
use std::fmt::{Debug, Formatter};
use std::sync::LazyLock;
// For AEAD_AES_128_GCM and AEAD_AES_256_GCM ... endpoints that do not send
// packets larger than 2^11 bytes cannot protect more than 2^28 packets.
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-confidentiality-limit
const AES_CONFIDENTIALITY_LIMIT: u64 = 2u64.pow(28);
// For AEAD_CHACHA20_POLY1305, the confidentiality limit is greater than the
// number of possible packets (2^62) and so can be disregarded.
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
const CHACHA20_POLY1305_CONFIDENTIALITY_LIMIT: u64 = u64::MAX;
// For AEAD_AES_128_GCM ... endpoints that do not attempt to remove
// protection from packets larger than 2^11 bytes can attempt to remove
// protection from at most 2^57 packets.
// For AEAD_AES_256_GCM [the limit] is substantially larger than the limit for
// AEAD_AES_128_GCM. However, this document recommends that the same limit be
// applied to both functions as either limit is acceptably large.
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-integrity-limit
const AES_INTEGRITY_LIMIT: u64 = 2u64.pow(57);
// For AEAD_CHACHA20_POLY1305, the integrity limit is 2^36 invalid packets.
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
const CHACHA20_POLY1305_INTEGRITY_LIMIT: u64 = 2u64.pow(36);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ID {
Aes128GcmSha256,
Aes256GcmSha384,
Chacha20Poly1305Sha256,
}
#[derive(Eq, PartialEq)]
pub(crate) struct CipherSuite {
pub(crate) id: ID,
pub(crate) hkdf: Hkdf,
pub(crate) aead: &'static Aead,
pub(crate) confidentiality_limit: u64,
pub(crate) integrity_limit: u64,
}
impl Debug for CipherSuite {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.id, f)
}
}
static AES128_GCM_SHA256: LazyLock<CipherSuite> = LazyLock::new(|| CipherSuite {
id: ID::Aes128GcmSha256,
hkdf: Hkdf::sha256(),
aead: Aead::aes128_gcm(),
confidentiality_limit: AES_CONFIDENTIALITY_LIMIT,
integrity_limit: AES_INTEGRITY_LIMIT,
});
static AES256_GCM_SHA384: LazyLock<CipherSuite> = LazyLock::new(|| CipherSuite {
id: ID::Aes256GcmSha384,
hkdf: Hkdf::sha384(),
aead: Aead::aes256_gcm(),
confidentiality_limit: AES_CONFIDENTIALITY_LIMIT,
integrity_limit: AES_INTEGRITY_LIMIT,
});
static CHACHA20_POLY1305_SHA256: LazyLock<CipherSuite> = LazyLock::new(|| CipherSuite {
id: ID::Chacha20Poly1305Sha256,
hkdf: Hkdf::sha256(),
aead: Aead::chacha20_poly1305(),
confidentiality_limit: CHACHA20_POLY1305_CONFIDENTIALITY_LIMIT,
integrity_limit: CHACHA20_POLY1305_INTEGRITY_LIMIT,
});
unsafe impl Send for CipherSuite {}
unsafe impl Sync for CipherSuite {}
impl CipherSuite {
#[inline]
pub(crate) fn aes128_gcm_sha256() -> &'static Self {
&AES128_GCM_SHA256
}
#[inline]
pub(crate) fn aes256_gcm_sha384() -> &'static Self {
&AES256_GCM_SHA384
}
#[inline]
pub(crate) fn chacha20_poly1305_sha256() -> &'static Self {
&CHACHA20_POLY1305_SHA256
}
#[inline]
pub(crate) fn from_cipher(cipher: *const bffi::SSL_CIPHER) -> Result<&'static Self> {
match unsafe { bffi::SSL_CIPHER_get_id(cipher) } as i32 {
bffi::TLS1_CK_AES_128_GCM_SHA256 => Ok(Self::aes128_gcm_sha256()),
bffi::TLS1_CK_AES_256_GCM_SHA384 => Ok(Self::aes256_gcm_sha384()),
bffi::TLS1_CK_CHACHA20_POLY1305_SHA256 => Ok(Self::chacha20_poly1305_sha256()),
id => Err(Error::invalid_input(format!("invalid cipher id: {id}"))),
}
}
}

151
quinn-boring/src/version.rs Normal file
View File

@ -0,0 +1,151 @@
use quinn_proto::crypto;
use std::result::Result as StdResult;
/// QUIC protocol version
///
/// Governs version-specific behavior in the TLS layer
// TODO: add support for draft version 2.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuicVersion {
V1Draft29,
V1Draft30,
V1Draft31,
V1Draft32,
V1Draft33,
V1Draft34,
/// First stable RFC version.
V1,
}
impl Default for QuicVersion {
fn default() -> Self {
Self::V1
}
}
impl QuicVersion {
const DRAFT_INDICATOR: u32 = 0xff00_0000;
const VERSION_1_DRAFT_29: u32 = Self::DRAFT_INDICATOR | 29;
const VERSION_1_DRAFT_30: u32 = Self::DRAFT_INDICATOR | 30;
const VERSION_1_DRAFT_31: u32 = Self::DRAFT_INDICATOR | 31;
const VERSION_1_DRAFT_32: u32 = Self::DRAFT_INDICATOR | 32;
const VERSION_1_DRAFT_33: u32 = Self::DRAFT_INDICATOR | 33;
const VERSION_1_DRAFT_34: u32 = Self::DRAFT_INDICATOR | 34;
const VERSION_1: u32 = 1;
/// Returns the default list of supported quic versions.
pub fn default_supported_versions() -> Vec<u32> {
let mut out = Vec::new();
for v in [
Self::V1,
Self::V1Draft34,
Self::V1Draft33,
Self::V1Draft32,
Self::V1Draft31,
Self::V1Draft30,
Self::V1Draft29,
] {
out.push(v.label());
}
out
}
pub(crate) fn parse(version: u32) -> StdResult<Self, crypto::UnsupportedVersion> {
match version {
Self::VERSION_1_DRAFT_29 => Ok(Self::V1Draft29),
Self::VERSION_1_DRAFT_30 => Ok(Self::V1Draft30),
Self::VERSION_1_DRAFT_31 => Ok(Self::V1Draft31),
Self::VERSION_1_DRAFT_32 => Ok(Self::V1Draft32),
Self::VERSION_1_DRAFT_33 => Ok(Self::V1Draft33),
Self::VERSION_1_DRAFT_34 => Ok(Self::V1Draft34),
Self::VERSION_1 => Ok(Self::V1),
_ => Err(crypto::UnsupportedVersion),
}
}
pub(crate) fn label(&self) -> u32 {
match self {
Self::V1Draft29 => Self::VERSION_1_DRAFT_29,
Self::V1Draft30 => Self::VERSION_1_DRAFT_30,
Self::V1Draft31 => Self::VERSION_1_DRAFT_31,
Self::V1Draft32 => Self::VERSION_1_DRAFT_32,
Self::V1Draft33 => Self::VERSION_1_DRAFT_33,
Self::V1Draft34 => Self::VERSION_1_DRAFT_34,
Self::V1 => Self::VERSION_1,
}
}
pub(crate) fn initial_salt(&self) -> &'static [u8] {
match self {
Self::V1Draft29 | Self::V1Draft30 | Self::V1Draft31 | Self::V1Draft32 => &[
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
],
Self::V1Draft33 | Self::V1Draft34 | Self::V1 => &[
// https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8,
0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
],
}
}
pub(crate) fn retry_integrity_key(&self) -> &'static [u8] {
match self {
Self::V1Draft29 | Self::V1Draft30 | Self::V1Draft31 | Self::V1Draft32 => &[
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.8
0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9,
0x6b, 0xe1,
],
Self::V1Draft33 | Self::V1Draft34 | Self::V1 => &[
// https://datatracker.ietf.org/doc/html/rfc9001#name-retry-packet-integrity
0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68,
0xc8, 0x4e,
],
}
}
pub(crate) fn retry_integrity_nonce(&self) -> &'static [u8] {
match self {
Self::V1Draft29 | Self::V1Draft30 | Self::V1Draft31 | Self::V1Draft32 => &[
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.8
0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c,
],
Self::V1Draft33 | Self::V1Draft34 | Self::V1 => &[
// https://datatracker.ietf.org/doc/html/rfc9001#name-retry-packet-integrity
0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
],
}
}
/// Indicates whether this version uses the legacy TLS extension codepoint.
pub(crate) fn uses_legacy_extension(&self) -> bool {
match self {
Self::V1Draft29
| Self::V1Draft30
| Self::V1Draft31
| Self::V1Draft32
| Self::V1Draft33
| Self::V1Draft34 => true,
Self::V1 => false,
}
}
pub(crate) fn key_label(&self) -> &'static [u8] {
b"quic key"
}
pub(crate) fn iv_label(&self) -> &'static [u8] {
b"quic iv"
}
pub(crate) fn header_key_label(&self) -> &'static [u8] {
b"quic hp"
}
pub(crate) fn key_update_label(&self) -> &'static [u8] {
b"quic ku"
}
}

View File

@ -27,7 +27,3 @@ sleep 20
echo "=== Publishing tokio-boring... ==="
(cd tokio-boring && cargo publish)
sleep 20
echo "=== Publishing hyper-boring... ==="
(cd hyper-boring && cargo publish)
sleep 20

View File

@ -1,30 +1,23 @@
[package]
name = "tokio-boring"
name = "tokio-boring2"
version = { workspace = true }
authors = ["Alex Crichton <alex@alexcrichton.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = { workspace = true }
repository = { workspace = true }
homepage = "https://github.com/cloudflare/boring"
documentation = "https://docs.rs/tokio-boring"
homepage = "https://github.com/0x676e67/boring2"
documentation = "https://docs.rs/tokio-boring2"
description = """
An implementation of SSL streams for Tokio backed by BoringSSL
"""
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
# Use a FIPS-validated version of boringssl.
fips = ["boring/fips", "boring-sys/fips"]
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
pq-experimental = ["boring/pq-experimental"]
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
rpk = ["boring/rpk"]
[dependencies]
boring = { workspace = true }
boring-sys = { workspace = true }

View File

@ -1,8 +1,8 @@
# tokio-boring
# tokio-boring2
An implementation of SSL streams for Tokio built on top of the BoringSSL.
[Documentation](https://docs.rs/tokio-boring)
[Documentation](https://docs.rs/tokio-boring2)
## Usage
@ -10,7 +10,7 @@ First, add this to your `Cargo.toml`:
```toml
[dependencies]
tokio-boring = "1.0.0"
tokio-boring2 = "5.0.0"
```
Then, use either `accept` or `connect` as appropriate.
@ -25,11 +25,11 @@ async fn main() -> anyhow::Result<()> {
let (tcp_stream, _addr) = listener.accept().await?;
let server = ssl::SslMethod::tls_server();
let mut ssl_builder = boring::ssl::SslAcceptor::mozilla_modern(server)?;
let mut ssl_builder = boring2::ssl::SslAcceptor::mozilla_modern(server)?;
ssl_builder.set_default_verify_paths()?;
ssl_builder.set_verify(ssl::SslVerifyMode::PEER);
let acceptor = ssl_builder.build();
let _ssl_stream = tokio_boring::accept(&acceptor, tcp_stream).await?;
let _ssl_stream = tokio_boring2::accept(&acceptor, tcp_stream).await?;
Ok(())
}
```
@ -40,10 +40,10 @@ negotiating the connection. Each TLS stream implements the `Read` and
ecosystem. Client connections initiated from this crate verify hostnames
automatically and by default.
`tokio-boring` exports this ability through [`accept`] and [`connect`]. `accept` should
`tokio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
be used by servers, and `connect` by clients. These augment the functionality provided by the
[`boring`] crate, on which this crate is built. Configuration of TLS parameters is still
primarily done through the [`boring`] crate.
[`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
primarily done through the [`boring2`] crate.
# License

View File

@ -1,5 +1,6 @@
use boring::ssl;
use tokio::net::TcpListener;
use tokio_boring2 as tokio_boring;
#[tokio::main]
async fn main() -> anyhow::Result<()> {

View File

@ -6,10 +6,10 @@
//! ecosystem. Client connections initiated from this crate verify hostnames
//! automatically and by default.
//!
//! `tokio-boring` exports this ability through [`accept`] and [`connect`]. `accept` should
//! `tokio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
//! be used by servers, and `connect` by clients. These augment the functionality provided by the
//! [`boring`] crate, on which this crate is built. Configuration of TLS parameters is still
//! primarily done through the [`boring`] crate.
//! [`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
//! primarily done through the [`boring2`] crate.
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@ -1,3 +1,5 @@
use tokio_boring2 as tokio_boring;
use boring::ssl::{SslOptions, SslRef, SslSession, SslSessionCacheMode, SslVersion};
use futures::future;
use std::sync::atomic::{AtomicBool, Ordering};

View File

@ -1,3 +1,5 @@
use tokio_boring2 as tokio_boring;
use boring::hash::MessageDigest;
use boring::pkey::PKey;
use boring::rsa::Padding;

View File

@ -1,3 +1,5 @@
use tokio_boring2 as tokio_boring;
use boring::ssl::ClientHello;
use futures::future;
use tokio::task::yield_now;

View File

@ -1,3 +1,5 @@
use tokio_boring2 as tokio_boring;
use boring::ssl::{SslConnector, SslMethod};
use futures::future;
use std::net::ToSocketAddrs;

View File

@ -10,6 +10,7 @@ use std::pin::Pin;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio_boring::{HandshakeError, SslStream};
use tokio_boring2 as tokio_boring;
pub(crate) fn create_server(
setup: impl FnOnce(&mut SslAcceptorBuilder),

View File

@ -1,109 +0,0 @@
#[cfg(feature = "rpk")]
mod test_rpk {
use boring::pkey::PKey;
use boring::ssl::{SslAcceptor, SslConnector};
use futures::future;
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio_boring::{HandshakeError, SslStream};
fn create_server() -> (
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
SocketAddr,
) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
listener.set_nonblocking(true).unwrap();
let listener = TcpListener::from_std(listener).unwrap();
let addr = listener.local_addr().unwrap();
let server = async move {
let mut acceptor = SslAcceptor::rpk().unwrap();
let pkey = std::fs::read("tests/key.pem").unwrap();
let pkey = PKey::private_key_from_pem(&pkey).unwrap();
let cert = std::fs::read("tests/pubkey.der").unwrap();
acceptor.set_rpk_certificate(&cert).unwrap();
acceptor.set_null_chain_private_key(&pkey).unwrap();
let acceptor = acceptor.build();
let stream = listener.accept().await.unwrap().0;
tokio_boring::accept(&acceptor, stream).await
};
(server, addr)
}
#[tokio::test]
async fn server_rpk() {
let (stream, addr) = create_server();
let server = async {
let mut stream = stream.await.unwrap();
let mut buf = [0; 4];
stream.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"asdf");
stream.write_all(b"jkl;").await.unwrap();
future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx))
.await
.unwrap();
};
let client = async {
let mut connector = SslConnector::rpk_builder().unwrap();
let cert = std::fs::read("tests/pubkey.der").unwrap();
connector.set_rpk_certificate(&cert).unwrap();
let config = connector.build().configure().unwrap();
let stream = TcpStream::connect(&addr).await.unwrap();
let mut stream = tokio_boring::connect(config, "localhost", stream)
.await
.unwrap();
stream.write_all(b"asdf").await.unwrap();
let mut buf = vec![];
stream.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, b"jkl;");
};
future::join(server, client).await;
}
#[tokio::test]
async fn client_rpk_unknown_cert() {
let (stream, addr) = create_server();
let server = async {
assert!(stream.await.is_err());
};
let client = async {
let mut connector = SslConnector::rpk_builder().unwrap();
let cert = std::fs::read("tests/pubkey2.der").unwrap();
connector.set_rpk_certificate(&cert).unwrap();
let config = connector.build().configure().unwrap();
let stream = TcpStream::connect(&addr).await.unwrap();
let err = tokio_boring::connect(config, "localhost", stream)
.await
.unwrap_err();
// NOTE: smoke test for https://github.com/cloudflare/boring/issues/140
let _ = err.to_string();
};
future::join(server, client).await;
}
}