Compare commits

...

127 Commits

Author SHA1 Message Date
Jordan Rose
2c157897fe Merge in upstream v5.0.2
Some checks failed
CI / Test (true, map[CC:gcc CPLUS_INCLUDE_PATH:C:\msys64\usr\include CXX:g++ C_INCLUDE_PATH:C:\msys64\usr\include LIBRARY_PATH:C:\msys64\usr\lib RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-lat… (push) Has been cancelled
CI / Test (true, map[CMAKE_GENERATOR:MinGW Makefiles COLLECT_GCC:<nil> RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable, i686-pc-windows-gnu, i686-mingw) (push) Has been cancelled
CI / Test (true, map[CXXFLAGS:-msse2], ubuntu-latest, stable, i686-linux-android, i686-android) (push) Has been cancelled
CI / Test (true, map[IPHONEOS_DEPLOYMENT_TARGET:17.5], macos-latest, aarch64-apple-ios, aarch64-ios) (push) Has been cancelled
CI / Test (true, map[IPHONEOS_DEPLOYMENT_TARGET:17.5], macos-latest, aarch64-apple-ios-sim, aarch64-ios-sim) (push) Has been cancelled
CI / Test (true, map[IPHONEOS_DEPLOYMENT_TARGET:17.5], macos-latest, x86_64-apple-ios, x86_64-ios) (push) Has been cancelled
CI / Test (true, ubuntu-latest, stable, aarch64-linux-android, arm64-android) (push) Has been cancelled
CI / Test (true, ubuntu-latest, stable, armv7-linux-androideabi, arm-android) (push) Has been cancelled
CI / Test (true, ubuntu-latest, stable, x86_64-linux-android, x86_64-android) (push) Has been cancelled
CI / Test (ubuntu-latest, stable, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
CI / Test (macos-latest, stable, x86_64-apple-darwin, x86_64-macos) (push) Has been cancelled
CI / Test (map[CXXFLAGS:-msse2 RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable-x86_64-msvc, i686-pc-windows-msvc, i686-msvc) (push) Has been cancelled
CI / Test (map[RUSTC_BOOTSTRAP:1 RUSTFLAGS:-Dwarnings -C target-feature=+crt-static], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable-x86_64-msvc, x86_64-pc-windows-msvc, x86_64-msvc-static) (push) Has been cancelled
CI / Test (map[RUSTC_BOOTSTRAP:1], --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness, windows-latest, stable-x86_64-msvc, x86_64-pc-windows-msvc, x86_64-msvc) (push) Has been cancelled
CI / Test (map[]) (push) Has been cancelled
CI / Test (true, macos-latest, stable, aarch64-apple-darwin, arm64-macos) (push) Has been cancelled
CI / rustfmt (push) Has been cancelled
CI / clippy (push) Has been cancelled
CI / Test () (push) Has been cancelled
CI / Test (crossbuild-essential-arm64, true, map[CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER:aarch64-linux-gnu-g++ CC:aarch64-linux-gnu-gcc CXX:aarch64-linux-gnu-g++], ubuntu-latest, stable, aarch64-unknown-linux-gnu, aarch64-linux) (push) Has been cancelled
CI / Test (false) (push) Has been cancelled
CI / Test (gcc-arm-linux-gnueabi g++-arm-linux-gnueabi, true, map[CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER:arm-linux-gnueabi-g++ CC:arm-linux-gnueabi-gcc CXX:arm-linux-gnueabi-g++], ubuntu-latest, stable, arm-unknown-linux-gnueabi, arm-linux) (push) Has been cancelled
CI / Test (gcc-multilib g++-multilib, map[CXXFLAGS:-msse2], ubuntu-latest, stable, i686-unknown-linux-gnu, i686-linux) (push) Has been cancelled
CI / Test FIPS integration (push) Has been cancelled
CI / Cross build from macOS to Linux (x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / Test features (push) Has been cancelled
2026-02-18 17:24:41 -08:00
Kornel
e71b24328f Re-add fips-precompiled for v4 compat 2026-02-17 19:59:23 +00:00
Kornel
1f8a1041df Don't add build/ to non-FIPS pre-built path 2026-02-17 19:59:23 +00:00
Kornel
fa3a4ca915 Expose load_verify_locations like rust-openssl 2026-02-17 13:35:27 +00:00
Kornel
434585d90d Fix null handling, sync with rust-openssl 2026-02-17 13:35:27 +00:00
Jordan Rose
ee95e2060a boring-sys: Support static MSVC runtime 2026-02-13 01:51:50 +00:00
Kornel
f19374dfad Bump 2026-02-12 13:29:59 +00:00
Kornel
5bd645077b README links 2026-02-12 13:29:59 +00:00
Jordan Rose
c740bd7715 Revert "Support TARGET_CC and CC_{target}"
This reverts commit a50a39fde7,
which interferes with CMake's own support for these variables,
at least how Signal has been using them.
2026-02-11 13:37:41 -08:00
Kornel
144b67bb10 Support building without default features 2026-02-11 21:00:08 +00:00
Kornel
484afea507 Make SslCredential optional 2026-02-11 21:00:08 +00:00
Kornel
a40cba6cd6 Make ML-KEM optional 2026-02-11 21:00:08 +00:00
Kornel
9cc97c87c7 Don't always require all headers from all versions of BoringSSL 2026-02-11 21:00:08 +00:00
Kornel
d0973d7617 Backwards-compatible add_cert() 2026-02-11 21:00:08 +00:00
Kornel
e65f394509 Ensure we don't leave unit memory if generate_key fails 2026-02-11 20:59:52 +00:00
Kornel
fc02d4a47c Add missing error handling in ML-KEM generate 2026-02-11 20:59:52 +00:00
Kornel
532003e437 Don't always require all headers from all versions of BoringSSL 2026-02-11 16:22:11 +00:00
Kornel
c3f96c9cb0 Make ML-KEM optional 2026-02-11 16:22:11 +00:00
Kornel
d47684d087 v5.0.0 2026-02-10 16:28:54 +00:00
Kornel
ae4a737426 Update README 2026-02-10 16:28:54 +00:00
Kornel
8ba06e1974 rm symlink 2026-02-10 16:28:54 +00:00
Christopher Patton
559fc27ba1 symm: Ensure Cipher::from_nid() handles GCM NIDs
This method returns `None` for the GCM NIDs.

It appears to be implemented incorrectly: It first calls
`OBJ_nid2sn(nid)` to get the NID's short name, then calls
`EVP_get_cipherbyname(name)`. The documentation isn't clear as to
whether `name` should be the short or long name, but it appears to
expect the long name. At least, changing to `OBJ_nid2sn()` to
`OBJ_nid2ln()` makes the method work properly,

To fix this, this commit calls `EVP_get_cipherbynid()`, which is is more
direct.

Note that the method still returns `None` on the 3DES NID, but we're not
likely to encounter this one in practice.
2026-02-10 16:26:57 +00:00
Christopher Patton
7888b0fb91 symm: Add regression test for cipher NIDs 2026-02-10 16:26:57 +00:00
Kornel
d60c579bfd FIXMEs 2026-02-09 20:19:14 +00:00
Kornel
06ca1fd746 Handle overflows in FFI integer conversions 2026-02-04 16:37:34 +00:00
Kornel
5f4cf54cc5 Bump rust-version to 1.85 2026-02-04 00:54:35 +00:00
Kornel
7cb4c89550 Detect bad headers in boring-sys 2026-02-04 00:54:35 +00:00
Kornel
7298c9e0f0 Use std helper methods for pointer casts 2026-02-04 00:54:25 +00:00
Kornel
bdc5e1864c mem::forget -= 1 2026-02-04 00:54:25 +00:00
Kornel
39e394f37f Avoid useless malloc for SSL_set_tlsext_status_ocsp_resp 2026-02-04 00:54:25 +00:00
Kornel
8d35c787e7 Allow pq-experimental for v4 back-compat 2026-02-03 14:43:34 +00:00
Kornel
9b098e6bb7 Use separate MlKemPrivateKey/MlKemPublicKey instead of exposing slices 2026-02-03 14:43:07 +00:00
Kornel
1722cc7317 Rename MlKem to Algorithm 2026-02-03 14:41:44 +00:00
Kornel
241d05aea8 Skip MlKemParams 2026-02-03 14:41:44 +00:00
Kornel
36d18367f3 Tests don't need AsRef and other accessors 2026-02-03 14:41:44 +00:00
Kornel
c35cb1bb9f Use associated constants 2026-02-03 14:41:44 +00:00
Christopher Patton
531ac086f4 Expose a cipher's NID 2026-02-03 01:50:26 +00:00
Christopher Patton
88961db064 Add an init-update-finalize API for HMAC 2026-02-03 01:50:26 +00:00
Lina Baquero
884819622a Add safe Rust wrappers for ML-KEM-268 and ML-KEM-1020 2026-01-31 13:24:34 +00:00
Lina Baquero
f4dba99cd3
feat(boring-sys): add mlkem.h to bindgen headers (#455)
This enables rust bindings for BoringSSL's ML-KEM pq key encapsulation
including MLKEM758 and MLKEM1024
2026-01-27 06:10:37 -08:00
Kirill A. Korinsky
13b2db754d OpenBSD uses -lc++ as well 2026-01-27 00:59:21 +00:00
Kornel
48e27ae5a3 More helpful build errors 2026-01-26 15:29:07 +00:00
Kornel
f76cdc7502 Handle broken include dirs 2026-01-26 15:29:07 +00:00
Kornel
cc1881c71e Split SslCredential into a module 2026-01-26 12:26:24 +00:00
Kornel
6124273c4d Cache Windows builds harder 2026-01-24 11:41:09 +00:00
Kornel
6413deb356 Avoid cvt.map(drop) 2026-01-23 12:57:22 +00:00
Kornel
3c33edfd43 Cache test deps on Windows 2026-01-22 11:42:39 +00:00
Kornel
c13c69be3d Fix docs warnings 2026-01-21 19:09:22 +00:00
Kornel
f10b98672a Fix docs.rs build 2026-01-21 19:09:22 +00:00
Kornel
f4a7f8d345 Release 5.0.0-alpha.1 2026-01-21 13:25:48 +00:00
Kornel
3751abeeca Ensure dependency requirements are bumped
#436
2026-01-21 13:25:48 +00:00
Kornel
99dbbb3437 Fix missing import in tests 2026-01-21 03:08:36 +00:00
Kornel
077f134c75 Upgrading to v5 2026-01-21 03:07:01 +00:00
ihciah
ed768854a4 fix: BIO_set_retry_write when BIO_CTRL_FLUSH to allow writer returns WouldBlock on flush 2026-01-20 17:05:35 +00:00
Kornel
b65a064e76 Remove blanket Eq from FFI types 2026-01-20 15:53:04 +00:00
Kornel
4ca7589c2f MaybeUninit is stable now 2026-01-20 15:49:25 +00:00
Kornel
d322d3a380 Clippy 2026-01-20 15:49:25 +00:00
Kornel
96b7d5ff21 Avoid unicode chars 2026-01-20 15:49:25 +00:00
Kornel
13ef735d35 Cleaner include path check 2026-01-20 15:49:25 +00:00
Kornel
59483981d2 Test MinGW32 2026-01-20 15:48:42 +00:00
Kornel
4deec23722 Remove obsolete hack for OpenSSL 1.0.2
It already had a fix in 2019 rust-openssl#1133
2026-01-20 15:45:18 +00:00
Kornel
1ba39985e1 Remove unnecessary as_mut 2026-01-20 14:40:35 +00:00
Kornel
968999cf46 Don't readd leaky set_ex_data 2026-01-20 14:40:35 +00:00
Kornel
b32059ee88 Fix spki leak 2026-01-20 14:40:35 +00:00
Kornel
a2c57ecc0f Use existing macro for ForeignType 2026-01-20 14:40:35 +00:00
Anthony Ramine
c2f063cf47 Rework SslMethod
Instead of keeping around a flag on contexts to know
whether we are configured for RPK, we start from SslMethod
with a flag to know whether it is configured for X.509
certificates. We then propagate this flag to context builders
and contexts, defaulting to false, and introduce
`assume_x509` methods to inform the crate of X.509 support
for contexts created with other means than our own functions.

This improves the safety of the crate as any `SslContextBuilder`
configured with `SslMethod::tls_with_buffer` would crash if used
with functions involving X.509 certificates. This `SslMethod` is
made unsafe because we can't guarantee that we check for X.509
support from all FFI bindingsi (for example, BoringSSL crashes
if there is a mismatch in X.509 support in `SSL_set_SSL_CTX`).

Note that there is no point anyway in forbidding X.509 functions
on a context that supports RPK, as current BoringSSL is able
to negociate both raw public keys and X.509 certificates on the
same context.

Finally, I removed `SslMethod::tls_client` and other peer-specific
methods as they are just the same as there non-peer-specific
equivalent methods.
2026-01-20 14:40:35 +00:00
Anthony Ramine
93d9018774 Update boring to a newer version
RPK support has changed completely, it uses SSL_CREDENTIAL now.

Have fun reviewing this!
2026-01-20 14:40:35 +00:00
Kornel
11fec56d55 Flip is_rpk to has_x509_support 2026-01-20 14:40:35 +00:00
Anthony Ramine
acd8cbaf02 Fix MinGW builds
Those need to link against libstdc++.
2026-01-20 14:40:35 +00:00
Anthony Ramine
3ac364abc4 Fix Android builds
For starters, they should link against libc++, as they have always
intended to use STL "c++_shared".

824f2a7a20/Modules/Platform/Android-Common.cmake (L70-L75)

Also, fix the variable names we define, as far as I know cmake never
cared about ANDROID_NATIVE_API_LEVEL nor ANDROID_STL.
2026-01-20 14:40:35 +00:00
Anthony Ramine
c299b1476b Never use the debug CRT on Windows
See https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2968661195.
2026-01-20 14:40:35 +00:00
Anthony Ramine
e3483004e5 Pass -msse2 to i686 platforms on CI
In upstream commit 56d3ad9d23bc130aa9404bfdd1957fe81b3ba498,
BoringSSL stopped assuming SSE2 support for i688 platforms,
requiring users to explicitly pass -msse2.

```
  target/i686-unknown-linux-gnu/debug/build/boring-sys-3edff0f2746d7cbb/out/boringssl/crypto/fipsmodule/../internal.h:120:2: error: #error "x86 assembly requires SSE2. Build with -msse2 (recommended), or disable assembly optimizations with -DOPENSSL_NO_ASM."
```
2026-01-20 14:40:35 +00:00
Anthony Ramine
c5ed1d1319 Define __STDC_FORMAT_MACROS when cross-building from macOS to Linux 2026-01-20 14:40:35 +00:00
Kornel
3cf9b4f943 Use fips-build-compatible ERR_add_error_data 2026-01-20 14:40:35 +00:00
Kornel
7cb075cc6f Include err.h in FFI bindings 2026-01-20 14:40:35 +00:00
Kornel
a88541930f cargo publish is target-specific 2026-01-20 14:40:35 +00:00
Kornel
9bb20132ed Cross-platform Cargo registry cache 2026-01-20 14:40:35 +00:00
Kornel
41b4d6b77e Warn about BORING_BSSL_FIPS_PATH vs BORING_BSSL_PATH 2026-01-07 19:47:48 +00:00
Kornel
fc4ccbee1d Clippy CI blocker 2026-01-07 19:47:48 +00:00
Kornel
04114a8868 Fewer unwrap()s 2026-01-06 16:09:57 +00:00
Kornel
2d2052ee32 set_strict_cipher_list docs 2026-01-06 16:09:57 +00:00
Kornel
e98f4289e2 Cached index in tests 2026-01-06 15:46:36 +00:00
Kornel
2f53153128 Smaller cache, quicker rustup 2026-01-06 15:46:36 +00:00
southorange0929
9fcdba89f7 docs: add docs 2026-01-06 13:09:19 +00:00
southorange0929
1999540f7d feat: support openharmony platform 2026-01-06 13:09:19 +00:00
Kornel
8dfa471c86 Remove deprecated X509CheckFlags flag 2026-01-05 23:23:50 +00:00
Kornel
794d4d5e2e Safe clone for X509Store
Fixes #362
2026-01-05 22:26:12 +00:00
Kornel
2c7c9b7672 Fix leak in set_ex_data 2026-01-05 21:42:45 +00:00
Antoine Bernardeau
456836aea1 Add boring specific api set_strict_cipher_list to SslContextBuilder 2025-12-29 16:05:21 +00:00
Kornel
76f47a794c Can't build with clang-12 to libc++ mismatch 2025-12-10 17:11:00 +00:00
Anthony Ramine
7a0021e169 Remove DerefMut for SslCipher
Not a breaking change because it's a bug that it existed in the first place.
2025-11-28 08:18:08 +01:00
Anthony Ramine
230f167b80 Store a &'static SslCipherRef in SslCipher
This lets us implement Send and Sync for SslCipherRef
and get it for free on SslCipher.
2025-11-28 08:18:08 +01:00
Anthony Ramine
0c4062ed54 Introduce SslCipherRef::protocol_id 2025-11-24 09:52:08 +01:00
Arjan Singh Bal
a095d95238 Fix peer_cert_chain doc 2025-11-12 12:32:27 +00: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
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
Kornel
75ef523230 Safer CryptoBufferBuilder::build 2025-10-02 17:55:21 +01:00
Kornel
5957ce94cc ErrorStack ctor for custom errors 2025-10-02 17:55:21 +01:00
Kornel
e3998212ed Fix string data conversion in ErrorStack::put() 2025-10-02 17:55:21 +01:00
Apoorv Kothari
353ea62c17 Convert CipherCtx fns into a safe abstraction. Additional testing. 2025-10-01 11:00:57 +01:00
Kornel
8773f0e1fa Use Ref foreign type instead of forgetting 2025-10-01 11:00:57 +01:00
Apoorv Kothari
ab8513ef8f Expose a safe Rust interface for the session resumption callback 2025-10-01 11:00:57 +01:00
Kornel
ac1d71cb54 Use MaybeUninit for raw_ticket_key key/iv 2025-10-01 11:00:57 +01:00
Apoorv Kothari
5cb35db989 initialize key_name and iv. mark fn as _unsafe to allow for future changes to the api 2025-10-01 11:00:57 +01:00
Apoorv Kothari
b9af0ef176 clippy 2025-10-01 11:00:57 +01:00
Apoorv Kothari
ba85fbb7ad simplify tests 2025-10-01 11:00:57 +01:00
Apoorv Kothari
f526b57daa update documentation 2025-10-01 11:00:57 +01:00
Apoorv Kothari
ae783f8273 add test case for TicketKeyCallbackResult::Noop 2025-10-01 11:00:57 +01:00
Apoorv Kothari
ea1d120912 pr comments: safety, receive multiple nst, return status refactor 2025-10-01 11:00:57 +01:00
Apoorv Kothari
c49282f112 Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb)
Add a wrapper for the `SSL_CTX_set_tlsext_ticket_key_cb`, which allows
consumers to configure the EVP_CIPHER_CTX and HMAC_CTX used for
encrypting/decrypting session tickets.

See https://docs.openssl.org/1.0.2/man3/SSL_CTX_set_tlsext_ticket_key_cb/
for more details.
2025-10-01 11:00:57 +01:00
Alessandro Ghedini
b3521e5523 Add SslRef::curve_name() 2025-09-30 16:57:59 +01:00
Kornel
4ce1308e1c Make rpk feature flag additive 2025-09-30 16:45:49 +01:00
Christopher Patton
1c51c7ee3b Add back the curve() method on SslRef
Instead of returning an `SslCurve`, just return the `u16` returned by
BoringSSL.
2025-09-30 16:14:54 +01:00
Christopher Patton
7078f61077 Remove outdated comments on FIPS API compatibility 2025-09-30 16:14:54 +01:00
Christopher Patton
b46d77087e Remove SslCurve API
This is incompatible with the latest internal FIPS build. Namely, the
various group identifiers have been renamed since the previous version.
2025-09-30 16:14:54 +01:00
Bas Westerbaan
21735accf8 pq: fix MSVC C4146 warning 2025-09-30 16:22:47 +02:00
Christopher Patton
72dabe1d85 Remove the "kx-*" features
The "kx-*" features control default key exchange preferences. Its
implementation requires disabling APIs for manually setting curve
preferences via `set_curves()` or `set_curves_list()`.

In practice, most teams need to be able to override default preferences
at runtime anyway, which means these features were never really used.
This commit gets rid of them, thereby reducing some complexity in the
API.
2025-09-30 09:36:33 +01:00
Rushil Mehra
646ae33c61 X509Builder::append_extension2 -> X509Builder::append_extension 2025-09-26 17:38:53 +01:00
Rushil Mehra
8abba360d3 Ssl::new_from_ref -> Ssl::new() 2025-09-26 17:38:53 +01:00
Rushil Mehra
0fc992bd76 Align SslStream APIs with upstream
SslStream::new() is fallible, but `SslStream::from_raw_parts()` and
`SslStreamBuilder::new()` now unwrap. Upstream has also deprecated the
`SslStreamBuilder`, maybe we should do the same.
2025-09-26 17:38:53 +01:00
Alessandro Ghedini
4cb7e260a8 Clean-up legacy FIPS options
Per BoringSSL's FIPS policy, its `main` branch is the "update branch"
for FedRAMP compliance's purposes.

This means that we can stop using a specific BoringSSL branch when
enabling FIPS, as well as a number of hacks that allowed us to build
more recent BoringSSL versions with an older pre-compiled FIPS modules.

This also required slightly updating the main BoringSSL submodule, as
the previous version had an issue when building with the FIPS option
enabled. This is turn required some changes to the PQ patch as well as
some APIs that don't seem to be exposed publicly, as well as changing
some paths in the other patches.

In order to allow a smooth upgrade of internal projects, the `fips-compat`
feature is reduced in scope and renamed to `legacy-compat-deprecated` so
that we can incrementally upgrade internal BoringSSL forks. In practice
this shouldn't really be something anyone else would need, since in
order to work it requires a specific mix of BoringSSL version and
backported patches.
2025-09-26 17:12:23 +01:00
Kornel
78b8ceaf10 Add more reliable library_reason() 2025-09-26 14:17:31 +01:00
Kornel
974c3d2db0 Ensure that ERR_LIB type can be named 2025-09-26 14:17:31 +01:00
Alessandro Ghedini
b4bf601394 Remove support for Hyper v0 2025-09-26 13:46:44 +01:00
77 changed files with 5398 additions and 8220 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.toml text eol=lf

View File

@ -4,14 +4,15 @@ on:
pull_request:
branches:
- main
- v4.x
push:
branches:
- main
- v4.x
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
jobs:
rustfmt:
@ -27,44 +28,41 @@ jobs:
clippy:
name: clippy
runs-on: ubuntu-latest
env:
CARGO_HOME: ${{ github.workspace }}/.cache/cargo
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust
run: rustup update --no-self-update stable && rustup default stable && rustup component add clippy
run: rustup toolchain add stable --no-self-update --component clippy && rustup default stable
- name: Get rust version
id: rust-version
shell: bash
run: |
echo "version=$(rustc --version)" >> $GITHUB_OUTPUT
- name: Cache cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/registry/index
key: index-${{ runner.os }}-${{ github.run_number }}
restore-keys: |
index-${{ runner.os }}-
- name: Create lockfile
run: cargo generate-lockfile
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry/cache
key: registry-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
path: |
.cache/cargo/registry/index
.cache/cargo/registry/cache
key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }}
enableCrossOsArchive: true
- name: Fetch dependencies
run: cargo fetch
- name: Cache target directory
uses: actions/cache@v4
with:
path: target
key: clippy-target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
key: clippy-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
- name: Run clippy
run: cargo clippy --all --all-targets
run: cargo clippy --all --all-targets --features rpk,mlkem
- 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 boring -p boring-sys -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards
env:
CARGO_BUILD_RUSTDOCFLAGS: "--cfg=docsrs"
RUST_BOOTSTRAP: 1
RUSTC_BOOTSTRAP: 1
DOCS_RS: 1
- name: Cargo.toml boring versions consistency
shell: bash
@ -82,6 +80,7 @@ jobs:
matrix:
thing:
- stable
- i686-mingw
- arm-android
- arm64-android
- i686-android
@ -121,6 +120,8 @@ jobs:
rust: stable
os: ubuntu-latest
check_only: true
custom_env:
CXXFLAGS: -msse2
- thing: x86_64-android
target: x86_64-linux-android
rust: stable
@ -151,6 +152,8 @@ jobs:
rust: stable
os: ubuntu-latest
apt_packages: gcc-multilib g++-multilib
custom_env:
CXXFLAGS: -msse2
- thing: arm-linux
target: arm-unknown-linux-gnueabi
rust: stable
@ -158,8 +161,8 @@ jobs:
apt_packages: gcc-arm-linux-gnueabi g++-arm-linux-gnueabi
check_only: true
custom_env:
CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc
CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++
CC: arm-linux-gnueabi-gcc
CXX: arm-linux-gnueabi-g++
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++
- thing: aarch64-linux
target: aarch64-unknown-linux-gnu
@ -168,8 +171,8 @@ jobs:
apt_packages: crossbuild-essential-arm64
check_only: true
custom_env:
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
CC: aarch64-linux-gnu-gcc
CXX: aarch64-linux-gnu-g++
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-g++
- thing: arm64-macos
target: aarch64-apple-darwin
@ -191,29 +194,73 @@ jobs:
C_INCLUDE_PATH: "C:\\msys64\\usr\\include"
CPLUS_INCLUDE_PATH: "C:\\msys64\\usr\\include"
LIBRARY_PATH: "C:\\msys64\\usr\\lib"
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
# 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-boring --exclude hyper-boring -Z checksum-freshness
- thing: i686-mingw
target: i686-pc-windows-gnu
rust: stable
os: windows-latest
check_only: true
custom_env:
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
CMAKE_GENERATOR: "MinGW Makefiles"
COLLECT_GCC: null
# CI's Windows doesn't have required root certs
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
- thing: i686-msvc
target: i686-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
custom_env:
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
CXXFLAGS: -msse2
# 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-boring --exclude hyper-boring -Z checksum-freshness
- thing: x86_64-msvc
target: x86_64-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
custom_env:
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
# 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-boring --exclude hyper-boring -Z checksum-freshness
- thing: x86_64-msvc-static
target: x86_64-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
custom_env:
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
RUSTFLAGS: -Dwarnings -C target-feature=+crt-static
# CI's Windows doesn't have required root certs
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
env:
CARGO_HOME: ${{ github.workspace }}/.cache/cargo
CARGO_BUILD_BUILD_DIR: ${{ github.workspace }}/.cache/build-dir
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} && rustup target add ${{ matrix.target }}
shell: bash
- run: rustup target add ${{ matrix.target }}
- name: Get rust version
id: rust-version
shell: bash
run: |
echo "version=$(rustc --version)" >> $GITHUB_OUTPUT
- name: Prepopulate cargo index
uses: actions/cache/restore@v4
with:
path: |
.cache/cargo/registry/index
.cache/cargo/registry/cache
key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }}
enableCrossOsArchive: true
- name: Install golang
uses: actions/setup-go@v5
with:
go-version: '>=1.22.0'
- name: Install target-specific APT dependencies
if: "matrix.apt_packages != ''"
run: sudo apt update && sudo apt install -y ${{ matrix.apt_packages }}
@ -222,6 +269,32 @@ jobs:
if: startsWith(matrix.os, 'windows')
run: choco install nasm
shell: cmd
- name: Setup 32-bit MSYS2
if: matrix.thing == 'i686-mingw'
uses: msys2/setup-msys2@v2
id: msys2
with:
msystem: MINGW32
path-type: inherit
install: >-
mingw-w64-i686-gcc
mingw-w64-i686-cmake
- name: Setup 32-bit MSYS2 Env vars
if: matrix.thing == 'i686-mingw'
shell: bash
run: |
MSYS_ROOT='${{ steps.msys2.outputs.msys2-location }}'
test -d "$MSYS_ROOT\\mingw32\\bin"
echo >> $GITHUB_PATH "$MSYS_ROOT\\mingw32\\bin"
echo >> $GITHUB_PATH "$MSYS_ROOT\\usr\\bin"
echo >> $GITHUB_ENV CC="$MSYS_ROOT\\mingw32\\bin\\gcc"
echo >> $GITHUB_ENV CXX="$MSYS_ROOT\\mingw32\\bin\\g++"
echo >> $GITHUB_ENV AR="$MSYS_ROOT\\mingw32\\bin\\ar"
echo >> $GITHUB_ENV CFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include"
echo >> $GITHUB_ENV CXXFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include"
echo >> $GITHUB_ENV BINDGEN_EXTRA_CLANG_ARGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include"
echo >> $GITHUB_ENV LIBRARY_PATH="$MSYS_ROOT\\mingw32\\lib"
echo >> $GITHUB_ENV LDFLAGS="-L$MSYS_ROOT\\mingw32\\lib"
- name: Install LLVM and Clang
if: startsWith(matrix.os, 'windows')
uses: KyleMayes/install-llvm-action@v1
@ -234,12 +307,31 @@ jobs:
- name: Set Android Linker path
if: endsWith(matrix.thing, '-android')
run: echo "CARGO_TARGET_$(echo ${{ matrix.target }} | tr \\-a-z _A-Z)_LINKER=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/$(echo ${{ matrix.target }} | sed s/armv7/armv7a/)21-clang++" >> "$GITHUB_ENV"
- name: Build tests
# We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets.
run: cargo build --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }}
- name: Fetch deps
run: cargo fetch --target ${{ matrix.target }}
shell: bash
env: ${{ matrix.custom_env }}
- name: Run tests
# Windows builds are the slowest
- name: Cache deps in Windows tests
if: startsWith(matrix.os, 'windows')
uses: actions/cache/restore@v4
id: test-cache-restore
with:
path: .cache/build-dir
key: wintest-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
- name: Build tests
# We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets.
run: cargo build -vv --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }}
shell: bash
env: ${{ matrix.custom_env }}
# By default it'd be saved after later cargo calls, which already invalidated the cache
- name: Cache deps in Windows tests
if: startsWith(matrix.os, 'windows')
uses: actions/cache/save@v4
with:
path: .cache/build-dir
key: ${{ steps.test-cache-restore.outputs.cache-primary-key }}
- name: Run tests (skip=${{ matrix.check_only }})
if: "!matrix.check_only"
run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }}
shell: bash
@ -255,7 +347,9 @@ jobs:
#
# 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 --target ${{ matrix.target }} -p boring-sys
shell: bash
env: ${{ matrix.custom_env }}
test-fips:
name: Test FIPS integration
@ -267,18 +361,10 @@ jobs:
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- name: Install Clang-12
uses: KyleMayes/install-llvm-action@v1
with:
version: "12.0.0"
directory: ${{ runner.temp }}/llvm
- name: Install golang
uses: actions/setup-go@v5
with:
go-version: '>=1.22.0'
- name: Add clang++-12 link
working-directory: ${{ runner.temp }}/llvm/bin
run: ln -s clang clang++-12
- name: Run tests
run: cargo test --features fips
- name: Test boring-sys cargo publish (FIPS)
@ -306,8 +392,12 @@ jobs:
with:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }}
run: rustup toolchain install stable --no-self-update --profile minimal --target ${{ matrix.target }} && rustup default stable
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 }}
- name: Set BORING_BSSL_SYSROOT
@ -315,12 +405,17 @@ jobs:
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
shell: bash
- name: Set CXXFLAGS
run: echo "CXXFLAGS=-D__STDC_FORMAT_MACROS" >> $GITHUB_ENV
- name: Build for ${{ matrix.target }}
run: cargo build --target ${{ matrix.target }} --all-targets
test-features:
name: Test features
runs-on: ubuntu-latest
env:
CARGO_INCREMENTAL: 1
steps:
- uses: actions/checkout@v4
with:
@ -328,21 +423,15 @@ jobs:
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- run: cargo check --no-default-features
name: Check `--no-default-features`
- run: cargo check --features mlkem,credential
name: Check `mlkem,credential`
- 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 kx-safe-default,pq-experimental
name: Run `kx-safe-default` 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
- run: cargo test -p hyper-boring --features hyper1
name: Run hyper 1.0 tests for hyper-boring
- run: cargo test --features rpk,underscore-wildcards,mlkem
name: Run `rpk,underscore-wildcards` tests

3
.gitmodules vendored
View File

@ -2,6 +2,3 @@
path = boring-sys/deps/boringssl
url = https://github.com/google/boringssl.git
ignore = dirty
[submodule "boring-sys/deps/boringssl-fips"]
path = boring-sys/deps/boringssl-fips
url = https://github.com/google/boringssl.git

View File

@ -8,7 +8,8 @@ members = [
resolver = "2"
[workspace.package]
version = "4.21.1"
version = "5.0.2"
rust-version = "1.85"
repository = "https://github.com/cloudflare/boring"
edition = "2021"
@ -19,9 +20,9 @@ tag-prefix = ""
publish = false
[workspace.dependencies]
boring-sys = { version = "4.21.1", path = "./boring-sys" }
boring = { version = "4.21.1", path = "./boring" }
tokio-boring = { version = "4.21.1", path = "./tokio-boring" }
boring-sys = { version = "5.0.2", path = "./boring-sys", default-features = false }
boring = { version = "5.0.2", path = "./boring", default-features = false }
tokio-boring = { version = "5.0.2", path = "./tokio-boring", default-features = false }
bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] }
bitflags = "2.9"
@ -40,10 +41,8 @@ anyhow = "1"
antidote = "1.0.0"
http = "1"
http-body-util = "0.1.2"
http_old = { package = "http", version = "0.2" }
hyper = "1"
hyper-util = "0.1.6"
hyper_old = { package = "hyper", version = "0.14", default-features = false }
linked_hash_set = "0.1"
openssl-macros = "0.1.1"
tower = "0.4"

View File

@ -2,15 +2,34 @@
[![crates.io](https://img.shields.io/crates/v/boring.svg)](https://crates.io/crates/boring)
BoringSSL bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio)
[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android.
This crate provides safe 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.
It supports [FIPS-compatible builds of BoringSSL](https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md),
as well as [Post-Quantum crypto](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/)
and [Raw Public Key](https://docs.rs/boring/latest/boring/ssl/struct.SslRef.html#method.peer_pubkey) extensions.
## 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>
# Upgrading from `boring` v4
* First update to boring 4.21 and ensure it builds without any deprecation warnings.
* `pq-experimental` Cargo feature is no longer needed. Post-quantum crypto is enabled by default.
* `fips-precompiled` Cargo feature has been merged into `fips`. Set `BORING_BSSL_FIPS_PATH` env var to use a precompiled library.
* `fips-compat` Cargo feature has been renamed to `legacy-compat-deprecated` (4cb7e260a85b7)
* `SslCurve` and `SslCurveNid` have been removed. Curve names are more stable and portable identifiers. Use `curve_name()` and `set_curves_list()`.
* `Ssl::new_from_ref` -> `Ssl::new()`.
* `X509Builder::append_extension2` -> `X509Builder::append_extension`.
* `X509Store` is now cheaply cloneable, but immutable. `SslContextBuilder.cert_store_mut()` can't be used after `.set_cert_store()`. If you need `.cert_store_mut()`, either don't overwrite the default store, or use `.set_cert_store_builder()`.
* `X509StoreBuilder::add_cert` takes a reference.
* `hyper` 0.x support has been removed. Use `hyper` 1.x.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally

View File

@ -1,5 +1,27 @@
4.21.0
5.0.0
- 2025-12-19 Update vendored boring to a newer version (2023.11 to 2025.11)
- 2025-12-20 Rework RPK/SslMethod (c2f063cf4711f15b8b417b6926496fbf1c2a03ac)
- 2025-09-29 Remove `SslCurve` API
- 2025-09-30 Remove the "kx-*" features
- 2025-09-25 Remove legacy FIPS options (they're controlled via `BORING_BSSL_` env vars instead)
- 2026-01-05 Remove deprecated X509CheckFlags flag
- 2025-09-30 Remove "pq-experimental" Cargo feature, apply PQ patch by default + P256Kyber768Draft00
- 2026-01-05 Safe clone for X509Store
- 2025-03-08 Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb)
- 2025-09-30 Add SslRef::curve_name()
- 2025-09-30 Expose a safe Rust interface for the session resumption callback
- 2026-01-05 Fix leaky set_ex_data() API
- 2025-12-12 Add boring specific api set_strict_cipher_list to SslContextBuilder
- 2025-11-20 Introduce SslCipherRef::protocol_id
- 2023-05-11 fix: BIO_set_retry_write when BIO_CTRL_FLUSH to allow writer returns WouldBlock on flush
- 2025-11-14 Remove blanket Eq from FFI types
- 2025-12-20 Never use the debug CRT on Windows
- 2025-02-19 X509Builder::append_extension2 -> X509Builder::append_extension
- 2025-02-19 `Ssl::new_from_ref` -> `Ssl::new()`
- 2025-02-19 Align SslStream APIs with upstream
- 2025-09-26 Remove support for Hyper v0
4.21.0
- 2026-01-05 Warn about set_curves() removal
- 2026-01-05 Deprecate set_ex_data()
- 2026-01-05 Fix build with --no-default-features

View File

@ -12,49 +12,38 @@ links = "boringssl"
build = "build/main.rs"
readme = "README.md"
categories = ["cryptography", "external-ffi-bindings"]
keywords = ["tls", "boringssl", "openssl", "fips", "ml-kem"]
edition = { workspace = true }
rust-version = "1.77"
rust-version = { workspace = true }
include = [
"/*.md",
"/*.toml",
"/LICENSE-MIT",
"/cmake/*.cmake",
# boringssl (non-FIPS)
"/deps/boringssl/src/util/32-bit-toolchain.cmake",
"/deps/boringssl/**/*.[chS]",
"/deps/boringssl/**/*.inc",
"/deps/boringssl/**/*.asm",
"/deps/boringssl/sources.json",
"/deps/boringssl/src/crypto/obj/obj_mac.num",
"/deps/boringssl/src/crypto/obj/objects.txt",
"/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/**/*.bzl",
"/deps/boringssl/src/**/*.cc",
"/deps/boringssl/**/*.cc",
"/deps/boringssl/**/CMakeLists.txt",
"/deps/boringssl/**/sources.cmake",
"/deps/boringssl/**/util/go_tests.txt",
"/deps/boringssl/LICENSE",
# boringssl (FIPS)
"/deps/boringssl-fips/src/util/32-bit-toolchain.cmake",
"/deps/boringssl-fips/**/*.[chS]",
"/deps/boringssl-fips/**/*.asm",
"/deps/boringssl-fips/**/*.pl",
"/deps/boringssl-fips/**/*.go",
"/deps/boringssl-fips/**/go.mod",
"/deps/boringssl-fips/**/go.sum",
"/deps/boringssl-fips/sources.json",
"/deps/boringssl-fips/crypto/obj/obj_mac.num",
"/deps/boringssl-fips/crypto/obj/objects.txt",
"/deps/boringssl-fips/crypto/err/*.errordata",
"/deps/boringssl-fips/**/*.bzl",
"/deps/boringssl-fips/**/*.cc",
"/deps/boringssl-fips/**/CMakeLists.txt",
"/deps/boringssl-fips/**/sources.cmake",
"/deps/boringssl-fips/LICENSE",
"/build/*",
"/src",
"/patches",
]
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental", "underscore-wildcards"]
features = ["rpk", "underscore-wildcards", "mlkem"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
@ -66,27 +55,18 @@ rustdoc-args = ["--cfg", "docsrs"]
# for instructions and more details on the boringssl FIPS flag.
fips = []
# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with
# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this
# feature, or else the build will fail.
fips-precompiled = []
# Link with precompiled FIPS-validated `bcm.o` module.
fips-link-precompiled = []
# 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 = []
# Require mlkem.h
mlkem = []
# 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 = []
[build-dependencies]

View File

@ -1 +0,0 @@
../README.md

17
boring-sys/README.md Normal file
View File

@ -0,0 +1,17 @@
# Low-level bindings to BoringSSL
[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android.
This crate builds the BoringSSL library (or optionally links a pre-built version) and generates FFI bindings for it.
It supports [FIPS-compatible builds of BoringSSL](https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md),
as well as [Post-Quantum crypto](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/)
and [Raw Public Key](https://docs.rs/boring/latest/boring/ssl/struct.SslRef.html#method.peer_pubkey) extensions.
To use BoringSSL from Rust, prefer the [higher-level safe API](https://docs.rs/boring).
## 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 under the terms of both the Apache License,
Version 2.0 and the MIT license without any additional terms or conditions.

View File

@ -10,15 +10,15 @@ pub(crate) struct Config {
pub(crate) target: String,
pub(crate) target_arch: String,
pub(crate) target_os: String,
pub(crate) unix: bool,
pub(crate) target_env: String,
pub(crate) target_features: Vec<String>,
pub(crate) features: Features,
pub(crate) env: Env,
}
pub(crate) struct Features {
pub(crate) fips: bool,
pub(crate) fips_precompiled: bool,
pub(crate) fips_link_precompiled: bool,
pub(crate) pq_experimental: bool,
pub(crate) rpk: bool,
pub(crate) underscore_wildcards: bool,
}
@ -27,7 +27,6 @@ pub(crate) struct Env {
pub(crate) path: Option<PathBuf>,
pub(crate) include_path: Option<PathBuf>,
pub(crate) source_path: Option<PathBuf>,
pub(crate) precompiled_bcm_o: Option<PathBuf>,
pub(crate) assume_patched: bool,
pub(crate) sysroot: Option<PathBuf>,
pub(crate) compiler_external_toolchain: Option<PathBuf>,
@ -36,9 +35,6 @@ pub(crate) struct Env {
pub(crate) android_ndk_home: Option<PathBuf>,
pub(crate) cmake_toolchain_file: Option<PathBuf>,
pub(crate) cpp_runtime_lib: Option<OsString>,
/// C compiler (ignored if using FIPS)
pub(crate) cc: Option<OsString>,
pub(crate) cxx: Option<OsString>,
pub(crate) docs_rs: bool,
}
@ -50,6 +46,14 @@ impl Config {
let target = env::var("TARGET").unwrap();
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
let unix = env::var("CARGO_CFG_UNIX").is_ok();
let target_features = env::var("CARGO_CFG_TARGET_FEATURE")
.unwrap_or_default()
.split(',')
.map(|s| s.to_owned())
.collect();
let features = Features::from_env();
let env = Env::from_env(&host, &target, features.is_fips_like());
@ -67,6 +71,9 @@ impl Config {
target,
target_arch,
target_os,
unix,
target_env,
target_features,
features,
env,
};
@ -81,10 +88,6 @@ impl Config {
panic!("`fips` and `rpk` features are mutually exclusive");
}
if self.features.fips_precompiled && self.features.rpk {
panic!("`fips-precompiled` and `rpk` features are mutually exclusive");
}
let is_precompiled_native_lib = self.env.path.is_some();
let is_external_native_lib_source =
!is_precompiled_native_lib && self.env.source_path.is_none();
@ -96,9 +99,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;
@ -107,88 +108,77 @@ impl Config {
"cargo:warning=precompiled BoringSSL was provided, so patches will be ignored"
);
}
// todo(rmehra): should this even be a restriction? why not let people link a custom bcm.o?
// precompiled boringssl will include libcrypto.a
if is_precompiled_native_lib && self.features.fips_link_precompiled {
panic!("precompiled BoringSSL was provided, so FIPS configuration can't be applied");
}
if !is_precompiled_native_lib && self.features.fips_precompiled {
panic!("`fips-precompiled` feature requires `BORING_BSSL_FIPS_PATH` to be set");
}
}
}
impl Features {
fn from_env() -> Self {
let fips = env::var_os("CARGO_FEATURE_FIPS").is_some();
let fips_precompiled = env::var_os("CARGO_FEATURE_FIPS_PRECOMPILED").is_some();
let fips_link_precompiled = env::var_os("CARGO_FEATURE_FIPS_LINK_PRECOMPILED").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();
Self {
fips,
fips_precompiled,
fips_link_precompiled,
pq_experimental,
rpk,
underscore_wildcards,
}
}
pub(crate) fn is_fips_like(&self) -> bool {
self.fips || self.fips_precompiled || self.fips_link_precompiled
self.fips
}
}
impl Env {
fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self {
const NORMAL_PREFIX: &str = "BORING_BSSL";
const FIPS_PREFIX: &str = "BORING_BSSL_FIPS";
let var_prefix = if host == target { "HOST" } else { "TARGET" };
let target_with_underscores = target.replace('-', "_");
let target_only_var = |name: &str| {
// Logic stolen from cmake-rs.
let target_var = |name: &str| {
let kind = if host == target { "HOST" } else { "TARGET" };
// TODO(rmehra): look for just `name` first, as most people just set that
var(&format!("{name}_{target}"))
.or_else(|| var(&format!("{name}_{target_with_underscores}")))
.or_else(|| var(&format!("{var_prefix}_{name}")))
.or_else(|| var(&format!("{kind}_{name}")))
.or_else(|| var(name))
};
let target_var = |name: &str| target_only_var(name).or_else(|| var(name));
let boringssl_var = |name: &str| {
const BORING_BSSL_PREFIX: &str = "BORING_BSSL_";
const BORING_BSSL_FIPS_PREFIX: &str = "BORING_BSSL_FIPS_";
// The passed name is the non-fips version of the environment variable,
// to help look for them in the repository.
assert!(name.starts_with(NORMAL_PREFIX));
assert!(name.starts_with(BORING_BSSL_PREFIX));
let non_fips = target_var(name);
if is_fips_like {
target_var(&name.replace(NORMAL_PREFIX, FIPS_PREFIX))
let fips_name = name.replace(BORING_BSSL_PREFIX, BORING_BSSL_FIPS_PREFIX);
let fips = target_var(&fips_name);
if fips.is_none() && non_fips.is_some() {
println!("cargo:warning=env var {name} ignored, because FIPS is enabled. Set {fips_name} instead.");
}
fips
} else {
target_var(name)
non_fips
}
};
Self {
path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from),
include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from),
source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from),
precompiled_bcm_o: boringssl_var("BORING_BSSL_PRECOMPILED_BCM_O").map(PathBuf::from),
assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED")
path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_PATH if fips is enabled
include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_INCLUDE_PATH if fips is enabled
source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_SOURCE_PATH if fips is enabled
assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") // gets BORING_BSSL_FIPS_ASSUME_PATCHED if fips is enabled
.is_some_and(|v| !v.is_empty()),
sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from),
compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN")
sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), // gets BORING_BSSL_FIPS_SYSROOT if fips is enabled
compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN") // gets BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN if fips is enabled
.map(PathBuf::from),
debug: target_var("DEBUG"),
opt_level: target_var("OPT_LEVEL"),
android_ndk_home: target_var("ANDROID_NDK_HOME").map(Into::into),
cmake_toolchain_file: target_var("CMAKE_TOOLCHAIN_FILE").map(Into::into),
cpp_runtime_lib: target_var("BORING_BSSL_RUST_CPPLIB"),
// matches the `cc` crate
cc: target_only_var("CC"),
cxx: target_only_var("CXX"),
docs_rs: var("DOCS_RS").is_some(),
}
}

View File

@ -1,5 +1,4 @@
use fslock::LockFile;
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io;
@ -50,6 +49,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphoneos"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
(
@ -57,6 +57,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
(
@ -64,6 +65,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
// macOS
@ -106,19 +108,21 @@ fn get_apple_sdk_name(config: &Config) -> &'static str {
}
/// Returns an absolute path to the BoringSSL source.
fn get_boringssl_source_path(config: &Config) -> &PathBuf {
if let Some(src_path) = &config.env.source_path {
return src_path;
}
fn get_boringssl_source_path(config: &Config) -> &Path {
static SOURCE_PATH: OnceLock<PathBuf> = OnceLock::new();
SOURCE_PATH.get_or_init(|| {
let submodule_dir = if config.features.fips {
"boringssl-fips"
} else {
"boringssl"
};
if let Some(src_path) = &config.env.source_path {
if !src_path.exists() {
println!(
"cargo:warning=boringssl source path doesn't exist: {}",
src_path.display()
);
}
return src_path.into();
}
let submodule_dir = "boringssl";
let src_path = config.out_dir.join(submodule_dir);
@ -132,11 +136,15 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf {
.args(["submodule", "update", "--init", "--recursive"])
.arg(&submodule_path),
)
.unwrap();
.expect("git submodule update");
}
let _ = fs::remove_dir_all(&src_path);
fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()).unwrap();
fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default())
.inspect_err(|_| {
let _ = fs::remove_dir_all(&config.out_dir);
})
.expect("copying failed. Try running `cargo clean`");
// NOTE: .git can be both file and dir, depening on whether it was copied from a submodule
// or created by the patches code.
@ -153,7 +161,7 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf {
/// MSVC generator on Windows place static libs in a target sub-folder,
/// so adjust library location based on platform and build target.
/// See issue: <https://github.com/alexcrichton/cmake-rs/issues/18>
fn get_boringssl_platform_output_path(config: &Config) -> String {
fn msvc_lib_subdir(config: &Config) -> Option<&'static str> {
if config.target.ends_with("-msvc") {
// Code under this branch should match the logic in cmake-rs
let debug_env_var = config
@ -187,9 +195,9 @@ fn get_boringssl_platform_output_path(config: &Config) -> String {
_ => panic!("Unknown OPT_LEVEL={opt_env_var:?} env var."),
};
subdir.to_string()
Some(subdir)
} else {
String::new()
None
}
}
@ -200,11 +208,23 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
let src_path = get_boringssl_source_path(config);
let mut boringssl_cmake = cmake::Config::new(src_path);
if config.host == config.target {
if config.env.cmake_toolchain_file.is_some() {
return boringssl_cmake;
}
if config.env.cmake_toolchain_file.is_some() {
if config.target_os == "windows" {
// Explicitly use the non-debug CRT.
// This is required now because newest BoringSSL requires CMake 3.22 which
// uses the new logic with CMAKE_MSVC_RUNTIME_LIBRARY introduced in CMake 3.15.
// https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2969758499
if config.target_features.iter().any(|f| f == "crt-static") {
boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
} else {
boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL");
}
}
if config.host == config.target {
return boringssl_cmake;
}
@ -216,15 +236,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
.define("CMAKE_ASM_COMPILER_TARGET", &config.target);
}
if !config.features.fips {
if let Some(cc) = &config.env.cc {
boringssl_cmake.define("CMAKE_C_COMPILER", cc);
}
if let Some(cxx) = &config.env.cxx {
boringssl_cmake.define("CMAKE_CXX_COMPILER", cxx);
}
}
if let Some(sysroot) = &config.env.sysroot {
boringssl_cmake.define("CMAKE_SYSROOT", sysroot);
}
@ -255,8 +266,8 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
boringssl_cmake.define("CMAKE_TOOLCHAIN_FILE", toolchain_file);
// 21 is the minimum level tested. You can give higher value.
boringssl_cmake.define("ANDROID_NATIVE_API_LEVEL", "21");
boringssl_cmake.define("ANDROID_STL", "c++_shared");
boringssl_cmake.define("CMAKE_SYSTEM_VERSION", "21");
boringssl_cmake.define("CMAKE_ANDROID_STL_TYPE", "c++_shared");
}
"macos" => {
@ -304,7 +315,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
config
.manifest_dir
.join(src_path)
.join("src/util/32-bit-toolchain.cmake")
.join("util/32-bit-toolchain.cmake")
.as_os_str(),
);
}
@ -340,55 +351,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
boringssl_cmake
}
/// Verify that the toolchains match <https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3678.pdf>
/// See "Installation Instructions" under section 12.1.
// TODO: maybe this should also verify the Go and Ninja versions? But those haven't been an issue in practice ...
fn verify_fips_clang_version() -> (&'static str, &'static str) {
fn version(tool: &str) -> Option<String> {
let output = match Command::new(tool).arg("--version").output() {
Ok(o) => o,
Err(e) => {
println!("cargo:warning=missing {tool}, trying other compilers: {e}");
// NOTE: hard-codes that the loop below checks the version
return None;
}
};
if !output.status.success() {
return Some(String::new());
}
let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output");
Some(output.lines().next().expect("empty output").to_string())
}
const REQUIRED_CLANG_VERSION: &str = "12.0.0";
for (cc, cxx) in [
("clang-12", "clang++-12"),
("clang", "clang++"),
("cc", "c++"),
] {
let (Some(cc_version), Some(cxx_version)) = (version(cc), version(cxx)) else {
continue;
};
if cc_version.contains(REQUIRED_CLANG_VERSION) {
assert!(
cxx_version.contains(REQUIRED_CLANG_VERSION),
"mismatched versions of cc and c++"
);
return (cc, cxx);
} else if cc == "cc" {
panic!(
"unsupported clang version \"{cc_version}\": FIPS requires clang {REQUIRED_CLANG_VERSION}"
);
} else if !cc_version.is_empty() {
println!(
"cargo:warning=FIPS requires clang version {REQUIRED_CLANG_VERSION}, skipping incompatible version \"{cc_version}\""
);
}
}
unreachable!()
}
fn pick_best_android_ndk_toolchain(toolchains_dir: &Path) -> std::io::Result<OsString> {
let toolchains = std::fs::read_dir(toolchains_dir)?.collect::<Result<Vec<_>, _>>()?;
// First look for one of the toolchains that Google has documented.
@ -420,31 +382,23 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
let mut params = Vec::new();
// Add platform-specific parameters.
#[allow(clippy::single_match)]
match &*config.target_os {
"ios" | "macos" => {
// When cross-compiling for Apple targets, tell bindgen to use SDK sysroot,
// and *don't* use system headers of the host macOS.
let sdk = get_apple_sdk_name(config);
let output = std::process::Command::new("xcrun")
.args(["--show-sdk-path", "--sdk", sdk])
.output()
.unwrap();
if !output.status.success() {
if let Some(exit_code) = output.status.code() {
println!("cargo:warning=xcrun failed: exit code {exit_code}");
} else {
println!("cargo:warning=xcrun failed: killed");
match run_command(Command::new("xcrun").args(["--show-sdk-path", "--sdk", sdk])) {
Ok(output) => {
let sysroot = std::str::from_utf8(&output.stdout).expect("xcrun output");
params.push("-isysroot".to_string());
// There is typically a newline at the end which confuses clang.
params.push(sysroot.trim_end().to_string());
}
Err(e) => {
println!("cargo:warning={e}");
// Uh... let's try anyway, I guess?
}
std::io::stderr().write_all(&output.stderr).unwrap();
// Uh... let's try anyway, I guess?
return params;
}
let mut sysroot = String::from_utf8(output.stdout).unwrap();
// There is typically a newline at the end which confuses clang.
sysroot.truncate(sysroot.trim_end().len());
params.push("-isysroot".to_string());
params.push(sysroot);
}
"android" => {
let mut android_sysroot = config
@ -455,20 +409,18 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
android_sysroot.extend(["toolchains", "llvm", "prebuilt"]);
let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) {
Ok(toolchain) => toolchain,
Err(e) => {
println!(
"cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}"
);
// Uh... let's try anyway, I guess?
return params;
match pick_best_android_ndk_toolchain(&android_sysroot) {
Ok(toolchain) => {
android_sysroot.push(toolchain);
android_sysroot.push("sysroot");
params.push("--sysroot".to_string());
params.push(android_sysroot.into_os_string().into_string().unwrap());
}
};
android_sysroot.push(toolchain);
android_sysroot.push("sysroot");
params.push("--sysroot".to_string());
params.push(android_sysroot.into_os_string().into_string().unwrap());
Err(e) => {
println!("cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}");
// Uh... let's try anyway, I guess?
}
}
}
_ => {}
}
@ -484,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"
);
}
@ -506,10 +456,8 @@ 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 post quantum crypto patch to boringssl");
apply_patch(config, "boring-pq.patch")?;
if config.features.rpk {
println!("cargo:warning=applying RPK patch to boringssl");
@ -554,10 +502,18 @@ fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> {
}
fn run_command(command: &mut Command) -> io::Result<Output> {
let out = command.output()?;
let out = command.output().map_err(|e| {
io::Error::new(
e.kind(),
format!(
"can't run {}: {e}\n{command:?} failed",
command.get_program().to_string_lossy(),
),
)
})?;
println!("{}", std::str::from_utf8(&out.stdout).unwrap());
eprintln!("{}", std::str::from_utf8(&out.stderr).unwrap());
std::io::stderr().write_all(&out.stderr)?;
std::io::stdout().write_all(&out.stdout)?;
if !out.status.success() {
let err = match out.status.code() {
@ -571,14 +527,17 @@ fn run_command(command: &mut Command) -> io::Result<Output> {
Ok(out)
}
fn built_boring_source_path(config: &Config) -> &PathBuf {
if let Some(path) = &config.env.path {
return path;
}
fn build_boringssl_or_get_prebuilt(config: &Config) -> &Path {
static BUILD_SOURCE_PATH: OnceLock<PathBuf> = OnceLock::new();
BUILD_SOURCE_PATH.get_or_init(|| {
if let Some(path) = &config.env.path {
if !path.exists() {
println!("cargo:warning=built path doesn't exist: {}", path.display());
}
return path.into();
}
let mut cfg = get_boringssl_cmake_config(config);
let num_jobs = std::env::var("NUM_JOBS").ok().or_else(|| {
@ -591,79 +550,33 @@ fn built_boring_source_path(config: &Config) -> &PathBuf {
}
if config.features.fips {
let (clang, clangxx) = verify_fips_clang_version();
cfg.define("CMAKE_C_COMPILER", clang)
.define("CMAKE_CXX_COMPILER", clangxx)
.define("CMAKE_ASM_COMPILER", clang)
cfg.define("CMAKE_C_COMPILER", "clang")
.define("CMAKE_CXX_COMPILER", "clang++")
.define("CMAKE_ASM_COMPILER", "clang")
.define("FIPS", "1");
}
if config.features.fips_link_precompiled {
cfg.define("FIPS", "1");
}
cfg.build_target("ssl").build();
cfg.build_target("crypto").build()
let path = cfg.build_target("crypto").build();
let build_dir = path.join("build");
if build_dir.exists() {
build_dir
} else {
path
}
})
}
fn link_in_precompiled_bcm_o(config: &Config) {
println!("cargo:warning=linking in precompiled `bcm.o` module");
let bssl_dir = built_boring_source_path(config);
let bcm_o_src_path = config.env.precompiled_bcm_o.as_ref()
.expect("`fips-link-precompiled` requires `BORING_BSSL_FIPS_PRECOMPILED_BCM_O` env variable to be specified");
let libcrypto_path = bssl_dir
.join("build/crypto/libcrypto.a")
.canonicalize()
.unwrap();
let bcm_o_dst_path = bssl_dir.join("build/bcm-fips.o");
fs::copy(bcm_o_src_path, &bcm_o_dst_path).unwrap();
// check that fips module is named as expected
let out = run_command(
Command::new("ar")
.arg("t")
.arg(&libcrypto_path)
.arg("bcm.o"),
)
.unwrap();
assert_eq!(
String::from_utf8(out.stdout).unwrap().trim(),
"bcm.o",
"failed to verify FIPS module name"
);
// insert fips bcm.o before bcm.o into libcrypto.a,
// so for all duplicate symbols the older fips bcm.o is used
// (this causes the need for extra linker flags to deal with duplicate symbols)
// (as long as the newer module does not define new symbols, one may also remove it,
// but once there are new symbols it would cause missing symbols at linking stage)
run_command(
Command::new("ar")
.args(["rb", "bcm.o"])
.args([&libcrypto_path, &bcm_o_dst_path]),
)
.unwrap();
}
fn get_cpp_runtime_lib(config: &Config) -> Option<String> {
if let Some(ref cpp_lib) = config.env.cpp_runtime_lib {
return cpp_lib.clone().into_string().ok();
}
// 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" | "freebsd" => Some("c++".into()),
_ => Some("stdc++".into()),
}
} else {
None
match &*config.target_os {
"macos" | "ios" | "freebsd" | "openbsd" | "android" => Some("c++".into()),
_ if config.unix || config.target_env == "gnu" => Some("stdc++".into()),
// TODO(rmehra): figure out how to do this for windows
_ => None,
}
}
@ -677,40 +590,23 @@ fn main() {
}
fn emit_link_directives(config: &Config) {
let bssl_dir = built_boring_source_path(config);
let build_path = get_boringssl_platform_output_path(config);
let bssl_dir = build_boringssl_or_get_prebuilt(config);
let msvc_lib_subdir = msvc_lib_subdir(config);
if config.is_bazel || (config.features.is_fips_like() && config.env.path.is_some()) {
println!(
"cargo:rustc-link-search=native={}/lib/{}",
bssl_dir.display(),
build_path
);
} else {
// todo(rmehra): clean this up, I think these are pretty redundant
println!(
"cargo:rustc-link-search=native={}/build/crypto/{}",
bssl_dir.display(),
build_path
);
println!(
"cargo:rustc-link-search=native={}/build/ssl/{}",
bssl_dir.display(),
build_path
);
println!(
"cargo:rustc-link-search=native={}/build/{}",
bssl_dir.display(),
build_path
);
println!(
"cargo:rustc-link-search=native={}/build",
bssl_dir.display(),
);
}
let subdirs =
if config.is_bazel || (config.features.is_fips_like() && config.env.path.is_some()) {
&["lib"][..]
} else {
&["lib", "crypto", "ssl", ""][..]
};
if config.features.fips_link_precompiled {
link_in_precompiled_bcm_o(config);
for subdir in subdirs {
let dir = bssl_dir.join(subdir);
let dir = msvc_lib_subdir
.map(|s| dir.join(s))
.filter(|d| d.exists())
.unwrap_or(dir);
println!("cargo:rustc-link-search=native={}", dir.display());
}
if let Some(cpp_lib) = get_cpp_runtime_lib(config) {
@ -725,20 +621,33 @@ fn emit_link_directives(config: &Config) {
}
}
fn check_include_path(path: PathBuf) -> Result<PathBuf, String> {
if path.join("openssl").join("x509v3.h").exists() {
Ok(path)
} else {
Err(format!(
"Include path {} {}",
path.display(),
if !path.exists() {
"does not exist"
} else {
"does not have expected openssl/x509v3.h"
}
))
}
}
fn generate_bindings(config: &Config) {
let include_path = config.env.include_path.clone().unwrap_or_else(|| {
if let Some(bssl_path) = &config.env.path {
return bssl_path.join("include");
return check_include_path(bssl_path.join("include"))
.expect("config has invalid include path");
}
let src_path = get_boringssl_source_path(config);
let candidate = src_path.join("include");
if candidate.exists() {
candidate
} else {
src_path.join("src").join("include")
}
check_include_path(src_path.join("include"))
.or_else(|_| check_include_path(src_path.join("src").join("include")))
.expect("can't find usable include path")
});
let target_rust_version =
@ -749,7 +658,8 @@ fn generate_bindings(config: &Config) {
.derive_copy(true)
.derive_debug(true)
.derive_default(true)
.derive_eq(true)
.derive_eq(false)
.derive_partialeq(false)
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
@ -769,9 +679,20 @@ fn generate_bindings(config: &Config) {
builder = builder
.clang_arg("--sysroot")
.clang_arg(sysroot.display().to_string());
// we need to add special platform header file with env for support cross building
let target_include_dir = sysroot.join(format!(
"usr/include/{}-{}-{}",
config.target_arch, config.target_os, config.target_env
));
if target_include_dir.is_dir() {
builder = builder
.clang_arg("-I")
.clang_arg(target_include_dir.display().to_string());
}
}
let headers = [
let must_have_headers = [
"aes.h",
"asn1_mac.h",
"asn1t.h",
@ -786,28 +707,40 @@ fn generate_bindings(config: &Config) {
"dtls1.h",
"err.h",
"hkdf.h",
#[cfg(not(feature = "fips"))]
"hpke.h",
"ossl_typ.h",
"pkcs12.h",
"poly1305.h",
"x509v3.h",
];
let headers = [
"hmac.h",
"hrss.h",
"md4.h",
"md5.h",
"mlkem.h",
"obj_mac.h",
"objects.h",
"opensslv.h",
"ossl_typ.h",
"pkcs12.h",
"poly1305.h",
"rand.h",
"rc4.h",
"ripemd.h",
"siphash.h",
"srtp.h",
"trust_token.h",
"x509v3.h",
];
for header in &headers {
builder = builder.header(include_path.join("openssl").join(header).to_str().unwrap());
for (i, header) in must_have_headers.into_iter().chain(headers).enumerate() {
let header_path = include_path.join("openssl").join(header);
if header_path.exists() {
builder = builder.header(header_path.to_str().unwrap());
} else {
let err = format!("'openssl/{header}' is missing from '{}'. The include path may be incorrect or contain an outdated version of OpenSSL/BoringSSL", include_path.display());
let required = i < must_have_headers.len();
println!(
"cargo::{}={err}",
if required { "error" } else { "warning" }
);
}
}
let bindings = builder.generate().expect("Unable to generate bindings");
@ -824,7 +757,7 @@ fn ensure_err_lib_enum_is_named(source_code: &mut Vec<u8>) {
let src = String::from_utf8_lossy(source_code);
let enum_type = src
.split_once("ERR_LIB_SSL:")
.and_then(|(_, def)| Some(def.split_once("=")?.0))
.and_then(|(_, def)| Some(def.split_once('=')?.0))
.unwrap_or("_bindgen_ty_1");
source_code.extend_from_slice(

@ -1 +1 @@
Subproject commit 44b3df6f03d85c901767250329c571db405122d5
Subproject commit 91a66a59b6c1435120ff83e245d7719411294386

@ -1 +0,0 @@
Subproject commit 853ca1ea1168dff08011e5d42d94609cc0ca2e27

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,19 @@
https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards
From 2128aa4382ba668e2c4f77bf18da719b2ad0087e Mon Sep 17 00:00:00 2001
From: Anthony Ramine <aramine@cloudflare.com>
Date: Fri, 5 Dec 2025 08:19:56 +0100
Subject: [PATCH] Introduce X509_CHECK_FLAG_UNDERSCORE_WILDCARDS
--- 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,
---
crypto/x509/v3_utl.cc | 4 +++-
crypto/x509/x509_test.cc | 25 +++++++++++++++++++++++++
include/openssl/x509.h | 3 +++
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/crypto/x509/v3_utl.cc b/crypto/x509/v3_utl.cc
index 015bbcad2..2b9b63430 100644
--- a/crypto/x509/v3_utl.cc
+++ b/crypto/x509/v3_utl.cc
@@ -740,7 +740,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) {
@ -13,9 +24,11 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders
return 0;
}
}
--- a/src/crypto/x509/x509_test.cc
+++ b/src/crypto/x509/x509_test.cc
@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) {
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index c6ce62dd1..f284f421f 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -5209,6 +5209,31 @@ TEST(X509Test, Names) {
/*invalid_emails=*/{},
/*flags=*/0,
},
@ -47,15 +60,20 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders
};
size_t i = 0;
--- 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.
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index 926f365f4..cc538cceb 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -3359,6 +3359,9 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param,
// enabled when subjectAltNames is missing.
#define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20
+// Allow underscores in DNS wildcard matches.
+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40
OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen,
unsigned int flags, char **peername);
+// X509_CHECK_FLAG_UNDERSCORE_WILDCARDS allows underscores in DNS wildcard matches.
+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40
+
// X509_VERIFY_PARAM_set_hostflags sets the name-checking flags on |param| to
// |flags|. |flags| should be a combination of |X509_CHECK_FLAG_*| constants.
OPENSSL_EXPORT void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param,
--
2.40.0

View File

@ -19,12 +19,22 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong};
clippy::useless_transmute,
clippy::derive_partial_eq_without_eq,
clippy::ptr_offset_with_cast,
unpredictable_function_pointer_comparisons, // TODO: remove Eq/PartialEq in v5
dead_code
)]
mod generated {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
// explicitly require presence of some symbols to check if the bindings worked
pub use generated::{ssl_compliance_policy_t, ERR_add_error_data, SSL_set1_groups}; // if these are missing, your include path is incorrect or has a wrong version of boringssl
pub use generated::{BIO_new, OPENSSL_free, SSL_ERROR_NONE}; // if these are missing, your include path is incorrect
#[cfg(feature = "fips")]
pub use generated::{FIPS_mode, SSL_CTX_set_compliance_policy}; // your include path is incorrect or has a version of boringssl without FIPS support
#[cfg(feature = "mlkem")]
pub use generated::{MLKEM768_encap, MLKEM768_private_key_from_seed}; // your include path is incorrect or has a version of boringssl without mlkem support
#[cfg(feature = "rpk")]
pub use generated::{SSL_CREDENTIAL_new_raw_public_key, SSL_CREDENTIAL_set1_spki}; // your include path is incorrect or has a version of boringssl without rpk support
pub use generated::*;
#[cfg(target_pointer_width = "64")]

View File

@ -7,82 +7,51 @@ description = "BoringSSL bindings"
repository = { workspace = true }
documentation = "https://docs.rs/boring"
readme = "README.md"
keywords = ["crypto", "tls", "ssl", "dtls"]
keywords = ["tls", "ssl", "dtls", "post-quantum", "fips"]
categories = ["cryptography", "api-bindings"]
edition = { workspace = true }
rust-version = "1.80"
rust-version = { workspace = true }
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental", "underscore-wildcards"]
features = ["rpk", "underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
# Controlling the build
default = []
# NOTE: This feature is deprecated. It is needed for the submoduled
# boringssl-fips, which is extremely old and requires modifications to the
# bindings, as some newer APIs don't exist and some function signatures have
# changed. It is highly recommended to use `fips-precompiled` instead.
#
# This feature sets `fips-compat` on behalf of the user to guarantee bindings
# compatibility with the submoduled boringssl-fips.
#
# Use a FIPS-validated version of BoringSSL.
fips = ["fips-compat", "boring-sys/fips"]
fips = ["boring-sys/fips"]
# Build with compatibility for the submoduled boringssl-fips, without enabling
# the `fips` feature itself (useful e.g. if `fips-link-precompiled` is used
# with an older BoringSSL version).
fips-compat = []
# **DO NOT USE** This will be removed without warning in future releases.
legacy-compat-deprecated = []
# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with
# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this
# feature, or else the build will fail.
fips-precompiled = ["boring-sys/fips-precompiled"]
# **DO NOT USE** This will be removed without warning in future releases.
# PQ is always enabled. This feature is a no-op, only for backwards compatibility.
pq-experimental = []
# Link with precompiled FIPS-validated `bcm.o` module.
fips-link-precompiled = ["boring-sys/fips-link-precompiled"]
# Interface for ML-KEM (FIPS 203) post-quantum key encapsulation. Does not affect ciphers used in TLS.
mlkem = []
# SslCredential API
credential = []
# 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"]
rpk = ["credential", "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"]
# Controlling key exchange preferences at compile time
# Choose key exchange preferences at compile time. This prevents the user from
# choosing their own preferences.
kx-safe-default = []
# Support PQ key exchange. The client will prefer classical key exchange, but
# will upgrade to PQ key exchange if requested by the server. This is the
# safest option if you don't know if the peer supports PQ key exchange. This
# feature implies "kx-safe-default".
kx-client-pq-supported = ["kx-safe-default"]
# Prefer PQ key exchange. The client will prefer PQ exchange, but fallback to
# classical key exchange if requested by the server. This is the best option if
# you know the peer supports PQ key exchange. This feature implies
# "kx-safe-default" and "kx-client-pq-supported".
kx-client-pq-preferred = ["kx-safe-default", "kx-client-pq-supported"]
# Disable key exchange involving non-NIST key exchange on the client side.
# Implies "kx-safe-default".
kx-client-nist-required = ["kx-safe-default"]
# **DO NOT USE** This will be removed without warning in future releases.
# Alias for 'fips', only for backwards compatibility.
fips-precompiled = ["fips"]
[dependencies]
bitflags = { workspace = true }

View File

@ -43,18 +43,19 @@ fn mk_ca_cert() -> Result<(X509, PKey<Private>), ErrorStack> {
let not_after = Asn1Time::days_from_now(365)?;
cert_builder.set_not_after(&not_after)?;
cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?;
cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?.as_ref())?;
cert_builder.append_extension(
KeyUsage::new()
.critical()
.key_cert_sign()
.crl_sign()
.build()?,
.build()?
.as_ref(),
)?;
let subject_key_identifier =
SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?;
cert_builder.append_extension(subject_key_identifier)?;
cert_builder.append_extension(&subject_key_identifier)?;
cert_builder.sign(&privkey, MessageDigest::sha256())?;
let cert = cert_builder.build();
@ -106,7 +107,7 @@ fn mk_ca_signed_cert(
let not_after = Asn1Time::days_from_now(365)?;
cert_builder.set_not_after(&not_after)?;
cert_builder.append_extension(BasicConstraints::new().build()?)?;
cert_builder.append_extension(BasicConstraints::new().build()?.as_ref())?;
cert_builder.append_extension(
KeyUsage::new()
@ -114,24 +115,25 @@ fn mk_ca_signed_cert(
.non_repudiation()
.digital_signature()
.key_encipherment()
.build()?,
.build()?
.as_ref(),
)?;
let subject_key_identifier =
SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
cert_builder.append_extension(subject_key_identifier)?;
cert_builder.append_extension(&subject_key_identifier)?;
let auth_key_identifier = AuthorityKeyIdentifier::new()
.keyid(false)
.issuer(false)
.build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
cert_builder.append_extension(auth_key_identifier)?;
cert_builder.append_extension(&auth_key_identifier)?;
let subject_alt_name = SubjectAlternativeName::new()
.dns("*.example.com")
.dns("hello.com")
.build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
cert_builder.append_extension(subject_alt_name)?;
cert_builder.append_extension(&subject_alt_name)?;
cert_builder.sign(ca_privkey, MessageDigest::sha256())?;
let cert = cert_builder.build();

View File

@ -38,7 +38,7 @@
//! ```
//!
use crate::ffi;
use libc::{c_int, c_uint, size_t};
use libc::c_int;
use openssl_macros::corresponds;
use std::mem::MaybeUninit;
use std::ptr;
@ -63,8 +63,8 @@ impl AesKey {
let mut aes_key = MaybeUninit::uninit();
let r = ffi::AES_set_encrypt_key(
key.as_ptr() as *const _,
key.len() as c_uint * 8,
key.as_ptr(),
(key.len() * 8).try_into().map_err(|_| KeyError(()))?,
aes_key.as_mut_ptr(),
);
if r == 0 {
@ -87,8 +87,8 @@ impl AesKey {
let mut aes_key = MaybeUninit::uninit();
let r = ffi::AES_set_decrypt_key(
key.as_ptr() as *const _,
key.len() as c_uint * 8,
key.as_ptr(),
(key.len() * 8).try_into().map_err(|_| KeyError(()))?,
aes_key.as_mut_ptr(),
);
@ -125,12 +125,11 @@ pub fn wrap_key(
assert!(out.len() >= in_.len() + 8); // Ciphertext is 64 bits longer (see 2.2.1)
let written = ffi::AES_wrap_key(
&key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer.
iv.as_ref()
.map_or(ptr::null(), |iv| iv.as_ptr() as *const _),
out.as_ptr() as *mut _,
in_.as_ptr() as *const _,
in_.len() as size_t,
std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer.
iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr()),
out.as_mut_ptr(),
in_.as_ptr(),
in_.len(),
);
if written <= 0 {
Err(KeyError(()))
@ -164,12 +163,11 @@ pub fn unwrap_key(
assert!(out.len() + 8 <= in_.len());
let written = ffi::AES_unwrap_key(
&key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer.
iv.as_ref()
.map_or(ptr::null(), |iv| iv.as_ptr() as *const _),
out.as_ptr() as *mut _,
in_.as_ptr() as *const _,
in_.len() as size_t,
std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer.
iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr().cast()),
out.as_ptr().cast_mut(),
in_.as_ptr().cast(),
in_.len(),
);
if written <= 0 {

View File

@ -26,7 +26,7 @@
//! ```
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_char, c_int, c_long, time_t};
use libc::{c_int, c_long, time_t};
use std::cmp::Ordering;
use std::ffi::CString;
use std::fmt;
@ -315,7 +315,10 @@ impl Asn1Time {
ffi::init();
unsafe {
let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time))?;
// for higher musl version, need to convert i32 to i64
// https://github.com/rust-lang/libc/issues/1848
#[allow(clippy::useless_conversion)]
let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time.into()))?;
Ok(Asn1Time::from_ptr(handle))
}
}
@ -405,7 +408,7 @@ impl Asn1StringRef {
return Err(ErrorStack::get());
}
Ok(OpensslString::from_ptr(ptr as *mut c_char))
Ok(OpensslString::from_ptr(ptr.cast()))
}
}
@ -505,7 +508,6 @@ impl Asn1IntegerRef {
self.as_ptr(),
c_long::from(value),
))
.map(|_| ())
}
}
}
@ -547,7 +549,7 @@ impl Asn1BitStringRef {
#[corresponds(ASN1_STRING_length)]
#[must_use]
pub fn len(&self) -> usize {
unsafe { ffi::ASN1_STRING_length(self.as_ptr() as *const _) as usize }
unsafe { ffi::ASN1_STRING_length(self.as_ptr().cast_const()) as usize }
}
/// Determines if the string is empty.
@ -589,7 +591,7 @@ impl Asn1Object {
unsafe {
ffi::init();
let txt = CString::new(txt).map_err(ErrorStack::internal_error)?;
let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr() as *const _, 0))?;
let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr(), 0))?;
Ok(Asn1Object::from_ptr(obj))
}
}
@ -614,9 +616,9 @@ impl Asn1ObjectRef {
// To promote this to `pub`, the call-site parameter meaning ought to be clearer
fn to_text(&self, no_name: bool) -> String {
unsafe {
let mut buf = [0; 80];
let mut buf = [0u8; 80];
let len = ffi::OBJ_obj2txt(
buf.as_mut_ptr() as *mut _,
buf.as_mut_ptr().cast(),
buf.len() as c_int,
self.as_ptr(),
no_name as c_int,

View File

@ -1,11 +1,12 @@
use crate::ffi;
use crate::ffi::BIO_new_mem_buf;
use std::marker::PhantomData;
use std::ptr;
use std::slice;
use crate::cvt_p;
use crate::error::ErrorStack;
use crate::ffi;
use crate::ffi::BIO_new_mem_buf;
use crate::try_int;
pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>);
@ -19,20 +20,9 @@ impl Drop for MemBioSlice<'_> {
impl<'a> MemBioSlice<'a> {
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
#[cfg(not(feature = "fips-compat"))]
type BufLen = isize;
#[cfg(feature = "fips-compat")]
type BufLen = libc::c_int;
ffi::init();
assert!(buf.len() <= BufLen::MAX as usize);
let bio = unsafe {
cvt_p(BIO_new_mem_buf(
buf.as_ptr() as *const _,
buf.len() as BufLen,
))?
};
let bio = unsafe { cvt_p(BIO_new_mem_buf(buf.as_ptr().cast(), try_int(buf.len())?))? };
Ok(MemBioSlice(bio, PhantomData))
}
@ -68,7 +58,7 @@ impl MemBio {
unsafe {
let mut ptr = ptr::null_mut();
let len = ffi::BIO_get_mem_data(self.0, &mut ptr);
if ptr.is_null() {
if ptr.is_null() || len < 0 {
return &[];
}
slice::from_raw_parts(ptr.cast_const().cast(), len as usize)

View File

@ -24,7 +24,7 @@
//! [`BIGNUM`]: https://wiki.openssl.org/index.php/Manual:Bn_internal(3)
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_int, size_t};
use libc::c_int;
use std::cmp::Ordering;
use std::ffi::CString;
use std::ops::{Add, Deref, Div, Mul, Neg, Rem, Shl, Shr, Sub};
@ -121,19 +121,19 @@ impl BigNumRef {
/// Adds a `u32` to `self`.
#[corresponds(BN_add_word)]
pub fn add_word(&mut self, w: u32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) }
unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
}
/// Subtracts a `u32` from `self`.
#[corresponds(BN_sub_word)]
pub fn sub_word(&mut self, w: u32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) }
unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
}
/// Multiplies a `u32` by `self`.
#[corresponds(BN_mul_word)]
pub fn mul_word(&mut self, w: u32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) }
unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
}
/// Divides `self` by a `u32`, returning the remainder.
@ -168,13 +168,13 @@ impl BigNumRef {
/// number less than `self` in `rnd`.
#[corresponds(BN_rand_range)]
pub fn rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())) }
}
/// The cryptographically weak counterpart to `rand_in_range`.
#[corresponds(BN_pseudo_rand_range)]
pub fn pseudo_rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())) }
}
/// Sets bit `n`. Equivalent to `self |= (1 << n)`.
@ -183,7 +183,7 @@ impl BigNumRef {
#[corresponds(BN_set_bit)]
#[allow(clippy::useless_conversion)]
pub fn set_bit(&mut self, n: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())).map(|_| ()) }
unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())) }
}
/// Clears bit `n`, setting it to 0. Equivalent to `self &= ~(1 << n)`.
@ -192,7 +192,7 @@ impl BigNumRef {
#[corresponds(BN_clear_bit)]
#[allow(clippy::useless_conversion)]
pub fn clear_bit(&mut self, n: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())).map(|_| ()) }
unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())) }
}
/// Returns `true` if the `n`th bit of `self` is set to 1, `false` otherwise.
@ -209,19 +209,19 @@ impl BigNumRef {
#[corresponds(BN_mask_bits)]
#[allow(clippy::useless_conversion)]
pub fn mask_bits(&mut self, n: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())).map(|_| ()) }
unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())) }
}
/// Places `a << 1` in `self`. Equivalent to `self * 2`.
#[corresponds(BN_lshift1)]
pub fn lshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())) }
}
/// Places `a >> 1` in `self`. Equivalent to `self / 2`.
#[corresponds(BN_rshift1)]
pub fn rshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())) }
}
/// Places `a + b` in `self`. [`core::ops::Add`] is also implemented for `BigNumRef`.
@ -229,7 +229,7 @@ impl BigNumRef {
/// [`core::ops::Add`]: struct.BigNumRef.html#method.add
#[corresponds(BN_add)]
pub fn checked_add(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())) }
}
/// Places `a - b` in `self`. [`core::ops::Sub`] is also implemented for `BigNumRef`.
@ -237,21 +237,21 @@ impl BigNumRef {
/// [`core::ops::Sub`]: struct.BigNumRef.html#method.sub
#[corresponds(BN_sub)]
pub fn checked_sub(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())) }
}
/// Places `a << n` in `self`. Equivalent to `a * 2 ^ n`.
#[corresponds(BN_lshift)]
#[allow(clippy::useless_conversion)]
pub fn lshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) }
unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())) }
}
/// Places `a >> n` in `self`. Equivalent to `a / 2 ^ n`.
#[corresponds(BN_rshift)]
#[allow(clippy::useless_conversion)]
pub fn rshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) }
unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())) }
}
/// Creates a new BigNum with the same value.
@ -339,7 +339,6 @@ impl BigNumRef {
msb.0,
c_int::from(odd),
))
.map(|_| ())
}
}
@ -354,7 +353,6 @@ impl BigNumRef {
msb.0,
c_int::from(odd),
))
.map(|_| ())
}
}
@ -392,13 +390,12 @@ impl BigNumRef {
unsafe {
cvt(ffi::BN_generate_prime_ex(
self.as_ptr(),
bits as c_int,
c_int::from(bits),
c_int::from(safe),
add.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()),
rem.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()),
ptr::null_mut(),
))
.map(|_| ())
}
}
@ -420,7 +417,6 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -443,7 +439,6 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -463,7 +458,6 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -484,14 +478,13 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a²` in `self`.
#[corresponds(BN_sqr)]
pub fn sqr(&mut self, a: &BigNumRef, ctx: &mut BigNumContextRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())) }
}
/// Places the result of `a mod m` in `self`. As opposed to `div_rem`
@ -510,7 +503,6 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -531,7 +523,6 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -552,7 +543,6 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -573,7 +563,6 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -592,7 +581,6 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -611,7 +599,6 @@ impl BigNumRef {
p.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -632,7 +619,6 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -670,7 +656,6 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -846,7 +831,7 @@ impl BigNum {
ffi::init();
let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?;
let mut bn = ptr::null_mut();
cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr() as *const _))?;
cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr()))?;
Ok(BigNum::from_ptr(bn))
}
}
@ -858,7 +843,7 @@ impl BigNum {
ffi::init();
let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?;
let mut bn = ptr::null_mut();
cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr() as *const _))?;
cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr()))?;
Ok(BigNum::from_ptr(bn))
}
}
@ -880,12 +865,7 @@ impl BigNum {
unsafe {
ffi::init();
assert!(n.len() <= c_int::MAX as usize);
cvt_p(ffi::BN_bin2bn(
n.as_ptr(),
n.len() as size_t,
ptr::null_mut(),
))
.map(|p| BigNum::from_ptr(p))
cvt_p(ffi::BN_bin2bn(n.as_ptr(), n.len(), ptr::null_mut())).map(|p| BigNum::from_ptr(p))
}
}
}

View File

@ -44,7 +44,7 @@ impl<'a> Deriver<'a> {
where
T: HasPublic,
{
unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())) }
}
/// Returns the size of the shared secret.
@ -64,14 +64,7 @@ impl<'a> Deriver<'a> {
#[corresponds(EVP_PKEY_derive)]
pub fn derive(&mut self, buf: &mut [u8]) -> Result<usize, ErrorStack> {
let mut len = buf.len();
unsafe {
cvt(ffi::EVP_PKEY_derive(
self.0,
buf.as_mut_ptr() as *mut _,
&mut len,
))
.map(|_| len)
}
unsafe { cvt(ffi::EVP_PKEY_derive(self.0, buf.as_mut_ptr(), &mut len)).map(|_| len) }
}
/// A convenience function which derives a shared secret and returns it in a new buffer.

View File

@ -5,7 +5,6 @@
//! using the private key that can be validated with the public key but not be generated
//! without the private key.
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::c_uint;
use openssl_macros::corresponds;
@ -15,7 +14,9 @@ use std::ptr;
use crate::bn::{BigNum, BigNumRef};
use crate::error::ErrorStack;
use crate::ffi;
use crate::pkey::{HasParams, HasPrivate, HasPublic, Private, Public};
use crate::try_int;
use crate::{cvt, cvt_p};
generic_foreign_type_and_impl_send_sync! {
@ -103,7 +104,7 @@ where
unsafe {
let mut pub_key = ptr::null();
DSA_get0_key(self.as_ptr(), &mut pub_key, ptr::null_mut());
BigNumRef::from_ptr(pub_key as *mut _)
BigNumRef::from_ptr(pub_key.cast_mut())
}
}
}
@ -132,7 +133,7 @@ where
unsafe {
let mut priv_key = ptr::null();
DSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut priv_key);
BigNumRef::from_ptr(priv_key as *mut _)
BigNumRef::from_ptr(priv_key.cast_mut())
}
}
}
@ -154,7 +155,7 @@ where
unsafe {
let mut p = ptr::null();
DSA_get0_pqg(self.as_ptr(), &mut p, ptr::null_mut(), ptr::null_mut());
BigNumRef::from_ptr(p as *mut _)
BigNumRef::from_ptr(p.cast_mut())
}
}
@ -164,7 +165,7 @@ where
unsafe {
let mut q = ptr::null();
DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), &mut q, ptr::null_mut());
BigNumRef::from_ptr(q as *mut _)
BigNumRef::from_ptr(q.cast_mut())
}
}
@ -174,7 +175,7 @@ where
unsafe {
let mut g = ptr::null();
DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut g);
BigNumRef::from_ptr(g as *mut _)
BigNumRef::from_ptr(g.cast_mut())
}
}
}
@ -195,7 +196,7 @@ impl Dsa<Private> {
let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?);
cvt(ffi::DSA_generate_parameters_ex(
dsa.0,
bits as c_uint,
c_uint::from(bits),
ptr::null(),
0,
ptr::null_mut(),
@ -300,7 +301,7 @@ mod test {
let mut ctx = BigNumContext::new().unwrap();
let mut calc = BigNum::new().unwrap();
calc.mod_exp(g, priv_key, p, &mut ctx).unwrap();
assert_eq!(&calc, pub_key)
assert_eq!(&calc, pub_key);
}
#[test]

View File

@ -15,7 +15,6 @@
//! [`EcGroup`]: struct.EcGroup.html
//! [`Nid`]: ../nid/struct.Nid.html
//! [Eliptic Curve Cryptography]: https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::c_int;
use openssl_macros::corresponds;
@ -24,8 +23,10 @@ use std::ptr;
use crate::bn::{BigNumContextRef, BigNumRef};
use crate::error::ErrorStack;
use crate::ffi;
use crate::nid::Nid;
use crate::pkey::{HasParams, HasPrivate, HasPublic, Params, Private, Public};
use crate::try_int;
use crate::{cvt, cvt_n, cvt_p, init};
/// Compressed or Uncompressed conversion
@ -143,7 +144,6 @@ impl EcGroupRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -160,7 +160,6 @@ impl EcGroupRef {
cofactor.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -185,7 +184,7 @@ impl EcGroupRef {
pub fn generator(&self) -> &EcPointRef {
unsafe {
let ptr = ffi::EC_GROUP_get0_generator(self.as_ptr());
EcPointRef::from_ptr(ptr as *mut _)
EcPointRef::from_ptr(ptr.cast_mut())
}
}
@ -202,7 +201,6 @@ impl EcGroupRef {
order.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -260,7 +258,6 @@ impl EcPointRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -271,8 +268,7 @@ impl EcPointRef {
group: &EcGroupRef,
q: &EcPointRef,
m: &BigNumRef,
// FIXME should be &mut
ctx: &BigNumContextRef,
ctx: &mut BigNumContextRef,
) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EC_POINT_mul(
@ -283,7 +279,6 @@ impl EcPointRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -292,8 +287,7 @@ impl EcPointRef {
&mut self,
group: &EcGroupRef,
n: &BigNumRef,
// FIXME should be &mut
ctx: &BigNumContextRef,
ctx: &mut BigNumContextRef,
) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EC_POINT_mul(
@ -304,7 +298,6 @@ impl EcPointRef {
ptr::null(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -326,7 +319,6 @@ impl EcPointRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -339,7 +331,6 @@ impl EcPointRef {
self.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -428,7 +419,6 @@ impl EcPointRef {
y.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
}
@ -506,7 +496,7 @@ where
pub fn private_key(&self) -> &BigNumRef {
unsafe {
let ptr = ffi::EC_KEY_get0_private_key(self.as_ptr());
BigNumRef::from_ptr(ptr as *mut _)
BigNumRef::from_ptr(ptr.cast_mut())
}
}
}
@ -521,7 +511,7 @@ where
pub fn public_key(&self) -> &EcPointRef {
unsafe {
let ptr = ffi::EC_KEY_get0_public_key(self.as_ptr());
EcPointRef::from_ptr(ptr as *mut _)
EcPointRef::from_ptr(ptr.cast_mut())
}
}
@ -552,14 +542,14 @@ where
pub fn group(&self) -> &EcGroupRef {
unsafe {
let ptr = ffi::EC_KEY_get0_group(self.as_ptr());
EcGroupRef::from_ptr(ptr as *mut _)
EcGroupRef::from_ptr(ptr.cast_mut())
}
}
/// Checks the key for validity.
#[corresponds(EC_KEY_check_key)]
pub fn check_key(&self) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())) }
}
}
@ -847,7 +837,7 @@ mod test {
let mut ctx = BigNumContext::new().unwrap();
let mut public_key = EcPoint::new(&group).unwrap();
public_key
.mul_generator(&group, key.private_key(), &ctx)
.mul_generator(&group, key.private_key(), &mut ctx)
.unwrap();
assert!(public_key.eq(&group, key.public_key(), &mut ctx).unwrap());
}
@ -859,7 +849,7 @@ mod test {
let one = BigNum::from_u32(1).unwrap();
let mut ctx = BigNumContext::new().unwrap();
let mut ecp = EcPoint::new(&group).unwrap();
ecp.mul_generator(&group, &one, &ctx).unwrap();
ecp.mul_generator(&group, &one, &mut ctx).unwrap();
assert!(ecp.eq(&group, gen, &mut ctx).unwrap());
}

View File

@ -2,7 +2,7 @@
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_int, size_t};
use libc::c_int;
use openssl_macros::corresponds;
use std::mem;
use std::ptr;
@ -36,10 +36,10 @@ impl EcdsaSig {
assert!(data.len() <= c_int::MAX as usize);
let sig = cvt_p(ffi::ECDSA_do_sign(
data.as_ptr(),
data.len() as size_t,
data.len(),
eckey.as_ptr(),
))?;
Ok(EcdsaSig::from_ptr(sig as *mut _))
Ok(EcdsaSig::from_ptr(sig))
}
}
@ -51,7 +51,7 @@ impl EcdsaSig {
let sig = cvt_p(ffi::ECDSA_SIG_new())?;
ECDSA_SIG_set0(sig, r.as_ptr(), s.as_ptr());
mem::forget((r, s));
Ok(EcdsaSig::from_ptr(sig as *mut _))
Ok(EcdsaSig::from_ptr(sig))
}
}
@ -83,7 +83,7 @@ impl EcdsaSigRef {
assert!(data.len() <= c_int::MAX as usize);
cvt_n(ffi::ECDSA_do_verify(
data.as_ptr(),
data.len() as size_t,
data.len(),
self.as_ptr(),
eckey.as_ptr(),
))
@ -98,7 +98,7 @@ impl EcdsaSigRef {
unsafe {
let mut r = ptr::null();
ECDSA_SIG_get0(self.as_ptr(), &mut r, ptr::null_mut());
BigNumRef::from_ptr(r as *mut _)
BigNumRef::from_ptr(r.cast_mut())
}
}
@ -109,7 +109,7 @@ impl EcdsaSigRef {
unsafe {
let mut s = ptr::null();
ECDSA_SIG_get0(self.as_ptr(), ptr::null_mut(), &mut s);
BigNumRef::from_ptr(s as *mut _)
BigNumRef::from_ptr(s.cast_mut())
}
}
}

View File

@ -38,6 +38,9 @@ pub struct ErrorStack(Vec<Error>);
impl ErrorStack {
/// Pops the contents of the OpenSSL error stack, and returns it.
///
/// This should be used only immediately after calling Boring FFI functions,
/// otherwise the stack may be empty or a leftover from unrelated calls.
#[corresponds(ERR_get_error_line_data)]
#[must_use = "Use ErrorStack::clear() to drop the error stack"]
pub fn get() -> ErrorStack {
@ -62,6 +65,12 @@ impl ErrorStack {
Self(vec![Error::new_internal(Data::String(err.to_string()))])
}
/// Used to report errors from the Rust crate
#[cold]
pub(crate) fn internal_error_str(message: &'static str) -> Self {
Self(vec![Error::new_internal(Data::Static(message))])
}
/// Empties the current thread's error queue.
#[corresponds(ERR_clear_error)]
pub(crate) fn clear() {
@ -94,7 +103,7 @@ impl fmt::Display for ErrorStack {
write!(
fmt,
"[{}]",
err.reason_internal()
err.reason()
.or_else(|| err.library())
.unwrap_or("unknown reason")
)?;
@ -131,6 +140,7 @@ enum Data {
None,
CString(CString),
String(String),
Static(&'static str),
}
unsafe impl Sync for Error {}
@ -242,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() {
@ -293,6 +306,7 @@ impl Error {
Data::None => None,
Data::CString(cstring) => cstring.to_str().ok(),
Data::String(s) => Some(s),
Data::Static(s) => Some(s),
}
}
@ -302,6 +316,7 @@ impl Error {
Data::None => return None,
Data::CString(cstr) => return Some(Cow::Borrowed(cstr)),
Data::String(s) => s.as_str(),
Data::Static(s) => s,
};
CString::new(s).ok().map(Cow::Owned)
}
@ -318,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 {
@ -357,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

@ -15,16 +15,8 @@ pub fn enabled() -> bool {
#[test]
fn is_enabled() {
#[cfg(any(
feature = "fips",
feature = "fips-precompiled",
feature = "fips-link-precompiled"
))]
#[cfg(feature = "fips")]
assert!(enabled());
#[cfg(not(any(
feature = "fips",
feature = "fips-precompiled",
feature = "fips-link-precompiled"
)))]
#[cfg(not(feature = "fips"))]
assert!(!enabled());
}

View File

@ -1,7 +1,5 @@
use crate::ffi;
use openssl_macros::corresponds;
use std::convert::TryInto;
use std::ffi::{c_uint, c_void};
use std::ffi::c_uint;
use std::fmt;
use std::io;
use std::io::prelude::*;
@ -9,8 +7,10 @@ use std::ops::{Deref, DerefMut};
use std::ptr;
use crate::error::ErrorStack;
use crate::ffi;
use crate::ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new};
use crate::nid::Nid;
use crate::try_int;
use crate::{cvt, cvt_p};
#[derive(Copy, Clone, PartialEq, Eq)]
@ -196,7 +196,7 @@ impl Hasher {
unsafe {
cvt(ffi::EVP_DigestUpdate(
self.ctx,
data.as_ptr() as *mut _,
data.as_ptr().cast_mut().cast(),
data.len(),
))?;
}
@ -210,7 +210,7 @@ impl Hasher {
self.init()?;
}
unsafe {
let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap();
let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?;
let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize];
cvt(ffi::EVP_DigestFinal_ex(
self.ctx,
@ -220,7 +220,7 @@ impl Hasher {
self.state = Finalized;
Ok(DigestBytes {
buf,
len: len as usize,
len: try_int(len)?,
})
}
}
@ -359,7 +359,7 @@ pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<[u8; 20], ErrorStack> {
hmac(MessageDigest::sha1(), key, data)
}
fn hmac<const N: usize>(
pub(crate) fn hmac<const N: usize>(
digest: MessageDigest,
key: &[u8],
data: &[u8],
@ -370,7 +370,7 @@ fn hmac<const N: usize>(
cvt_p(unsafe {
ffi::HMAC(
digest.as_ptr(),
key.as_ptr() as *const c_void,
key.as_ptr().cast(),
key.len(),
data.as_ptr(),
data.len(),

130
boring/src/hmac.rs Normal file
View File

@ -0,0 +1,130 @@
use crate::cvt;
use crate::error::ErrorStack;
use crate::foreign_types::ForeignTypeRef;
use crate::hash::MessageDigest;
use openssl_macros::corresponds;
foreign_type_and_impl_send_sync! {
type CType = ffi::HMAC_CTX;
fn drop = ffi::HMAC_CTX_free;
pub struct HmacCtx;
}
impl HmacCtxRef {
/// Configures HmacCtx to use `md` as the hash function and `key` as the key.
///
#[corresponds(HMAC_Init_ex)]
pub fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> {
ffi::init();
unsafe {
cvt(ffi::HMAC_Init_ex(
self.as_ptr(),
key.as_ptr().cast(),
key.len(),
md.as_ptr(),
// ENGINE api is deprecated
core::ptr::null_mut(),
))
}
}
}
/// Provides an init-update-finalize API for HMAC.
pub struct Hmac(*mut ffi::HMAC_CTX);
impl Hmac {
/// Creates a new HMAC object with the given key and hash algorithm.
pub fn init(key: &[u8], md: &MessageDigest) -> Result<Hmac, ErrorStack> {
ffi::init();
let ctx = unsafe {
let ctx = ffi::HMAC_CTX_new();
cvt(ffi::HMAC_Init_ex(
ctx,
key.as_ptr().cast(),
key.len(),
md.as_ptr(),
// ENGINE api is deprecated
core::ptr::null_mut(),
))?;
ctx
};
Ok(Hmac(ctx))
}
/// Updates the HMAC input.
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::HMAC_Update(self.0, data.as_ptr().cast(), data.len())) }
}
/// Finalizes the HMAC and returns the output.
pub fn finalize(self) -> Result<Vec<u8>, ErrorStack> {
let out_len = unsafe { ffi::HMAC_size(self.0) };
let mut out = vec![0; out_len];
unsafe {
cvt(ffi::HMAC_Final(
self.0,
out.as_mut_ptr().cast(),
// ENGINE api is deprecated
core::ptr::null_mut(),
))?;
}
Ok(out)
}
}
impl Drop for Hmac {
fn drop(&mut self) {
unsafe { ffi::HMAC_CTX_free(self.0) }
}
}
#[cfg(test)]
mod tests {
use crate::hash;
use super::*;
fn test<const N: usize>(md: MessageDigest) {
assert_eq!(N, md.size());
let key = vec![0; N];
let message_parts = [
b"hello".to_vec(),
b"world!".to_vec(),
b"".to_vec(),
vec![0; 23],
b"fella guy".to_vec(),
];
let message = message_parts.concat();
let mut hmac = Hmac::init(&key, &md).unwrap();
for part in &message_parts {
hmac.update(part).unwrap();
}
let res = hmac.finalize().unwrap();
assert_eq!(res, hash::hmac::<N>(md, &key, &message).unwrap());
}
#[test]
fn test_sha1() {
test::<20>(MessageDigest::sha1());
}
#[test]
fn test_sha256() {
test::<32>(MessageDigest::sha256());
}
#[test]
fn test_sha384() {
test::<48>(MessageDigest::sha384());
}
#[test]
fn test_sha512() {
test::<64>(MessageDigest::sha512());
}
}

View File

@ -106,13 +106,12 @@ extern crate libc;
#[cfg(test)]
extern crate hex;
use std::ffi::{c_long, c_void};
use std::ffi::{c_int, c_long, c_void};
use std::num::NonZeroUsize;
#[doc(inline)]
pub use crate::ffi::init;
use libc::{c_int, size_t};
use crate::error::ErrorStack;
#[macro_use]
@ -135,9 +134,11 @@ pub mod error;
pub mod ex_data;
pub mod fips;
pub mod hash;
#[cfg(not(feature = "fips"))]
pub mod hmac;
pub mod hpke;
pub mod memcmp;
#[cfg(feature = "mlkem")]
pub mod mlkem;
pub mod nid;
pub mod pkcs12;
pub mod pkcs5;
@ -162,11 +163,11 @@ fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
}
}
fn cvt_0(r: size_t) -> Result<size_t, ErrorStack> {
fn cvt_0(r: usize) -> Result<(), ErrorStack> {
if r == 0 {
Err(ErrorStack::get())
} else {
Ok(r)
Ok(())
}
}
@ -178,14 +179,21 @@ fn cvt_0i(r: c_int) -> Result<c_int, ErrorStack> {
}
}
fn cvt(r: c_int) -> Result<c_int, ErrorStack> {
fn cvt(r: c_int) -> Result<(), ErrorStack> {
if r <= 0 {
Err(ErrorStack::get())
} else {
Ok(r)
Ok(())
}
}
fn cvt_nz(r: c_int) -> Result<NonZeroUsize, ErrorStack> {
usize::try_from(r)
.ok()
.and_then(NonZeroUsize::new)
.ok_or_else(ErrorStack::get)
}
fn cvt_n(r: c_int) -> Result<c_int, ErrorStack> {
if r < 0 {
Err(ErrorStack::get())
@ -194,6 +202,15 @@ fn cvt_n(r: c_int) -> Result<c_int, ErrorStack> {
}
}
fn try_int<F, T>(from: F) -> Result<T, ErrorStack>
where
F: TryInto<T> + Send + Sync + Copy + 'static,
T: Send + Sync + Copy + 'static,
{
from.try_into()
.map_err(|_| ErrorStack::internal_error_str("int overflow"))
}
unsafe extern "C" fn free_data_box<T>(
_parent: *mut c_void,
ptr: *mut c_void,
@ -203,6 +220,6 @@ unsafe extern "C" fn free_data_box<T>(
_argp: *mut c_void,
) {
if !ptr.is_null() {
drop(Box::<T>::from_raw(ptr as *mut T));
drop(Box::<T>::from_raw(ptr.cast::<T>()));
}
}

View File

@ -7,7 +7,8 @@ macro_rules! private_key_from_pem {
unsafe {
ffi::init();
let bio = crate::bio::MemBioSlice::new(pem)?;
let passphrase = ::std::ffi::CString::new(passphrase).unwrap();
let passphrase = ::std::ffi::CString::new(passphrase)
.map_err(crate::error::ErrorStack::internal_error)?;
cvt_p($f(bio.as_ptr(),
ptr::null_mut(),
None,
@ -27,7 +28,7 @@ macro_rules! private_key_from_pem {
cvt_p($f(bio.as_ptr(),
ptr::null_mut(),
Some(crate::util::invoke_passwd_cb::<F>),
&mut cb as *mut _ as *mut _))
ptr::from_mut(&mut cb).cast()))
.map(|p| ::foreign_types::ForeignType::from_ptr(p))
}
}
@ -59,12 +60,11 @@ macro_rules! private_key_to_pem {
) -> Result<Vec<u8>, crate::error::ErrorStack> {
unsafe {
let bio = crate::bio::MemBio::new()?;
assert!(passphrase.len() <= ::libc::c_int::MAX as usize);
cvt($f(bio.as_ptr(),
self.as_ptr(),
cipher.as_ptr(),
passphrase.as_ptr() as *const _ as *mut _,
passphrase.len() as ::libc::c_int,
try_int(passphrase.len())?,
None,
ptr::null_mut()))?;
Ok(bio.get_buf().to_owned())
@ -91,9 +91,9 @@ macro_rules! to_der {
$(#[$m])*
pub fn $n(&self) -> Result<Vec<u8>, crate::error::ErrorStack> {
unsafe {
let len = crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self),
let len = crate::cvt_nz($f(::foreign_types::ForeignTypeRef::as_ptr(self),
ptr::null_mut()))?;
let mut buf = vec![0; len as usize];
let mut buf = vec![0; len.get()];
crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self),
&mut buf.as_mut_ptr()))?;
Ok(buf)

View File

@ -30,7 +30,6 @@
//! assert!(!eq(&a, &c));
//! ```
use crate::ffi;
use libc::size_t;
/// Returns `true` iff `a` and `b` contain the same bytes.
///
@ -64,13 +63,7 @@ use libc::size_t;
#[must_use]
pub fn eq(a: &[u8], b: &[u8]) -> bool {
assert!(a.len() == b.len());
let ret = unsafe {
ffi::CRYPTO_memcmp(
a.as_ptr() as *const _,
b.as_ptr() as *const _,
a.len() as size_t,
)
};
let ret = unsafe { ffi::CRYPTO_memcmp(a.as_ptr().cast(), b.as_ptr().cast(), a.len()) };
ret == 0
}

743
boring/src/mlkem.rs Normal file
View File

@ -0,0 +1,743 @@
//! ML-KEM (FIPS 203) post-quantum key encapsulation.
//!
//! ML-KEM is a low-level cryptographic primitive. For most applications,
//! using higher-level constructions like HPKE is preferred.
//! Note that it's also enabled in TLS by default, in the X25519MLKEM768 exchange.
//!
//! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`Algorithm`].
//!
//! ```
//! use boring::mlkem::{Algorithm, MlKemPrivateKey};
//!
//! let (public_key, private_key) = MlKemPrivateKey::generate(Algorithm::MlKem768).unwrap();
//! let (ciphertext, shared_secret) = public_key.encapsulate().unwrap();
//! let decrypted = private_key.decapsulate(&ciphertext).unwrap();
//! assert_eq!(shared_secret, decrypted);
//! ```
use std::fmt;
use std::mem::MaybeUninit;
use crate::cvt;
use crate::error::ErrorStack;
use crate::ffi;
// CBS_init is inline in BoringSSL, so bindgen can't generate bindings for it.
#[inline]
fn cbs_init(data: &[u8]) -> ffi::CBS {
ffi::CBS {
data: data.as_ptr(),
len: data.len(),
}
}
/// Private key seed size (64 bytes).
pub const PRIVATE_KEY_SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize;
/// Shared secret size (32 bytes).
pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize;
/// Raw bytes of the private key seed ([`PRIVATE_KEY_SEED_BYTES`] long)
pub type MlKemPrivateKeySeed = [u8; PRIVATE_KEY_SEED_BYTES];
/// Raw bytes of the shared secret ([`SHARED_SECRET_BYTES`] long)
pub type MlKemSharedSecret = [u8; SHARED_SECRET_BYTES];
/// ML-KEM runtime algorithm selection.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Algorithm {
/// Recommended. AES-192 equivalent security.
MlKem768,
/// AES-256 equivalent security.
MlKem1024,
}
impl Algorithm {
/// Returns 1184 for ML-KEM-768, 1568 for ML-KEM-1024.
#[must_use]
pub const fn public_key_bytes(&self) -> usize {
match self {
Self::MlKem768 => MlKem768PublicKey::PUBLIC_KEY_BYTES,
Self::MlKem1024 => MlKem1024PublicKey::PUBLIC_KEY_BYTES,
}
}
/// Returns 1088 for ML-KEM-768, 1568 for ML-KEM-1024.
#[must_use]
pub const fn ciphertext_bytes(&self) -> usize {
match self {
Self::MlKem768 => MlKem768PrivateKey::CIPHERTEXT_BYTES,
Self::MlKem1024 => MlKem1024PrivateKey::CIPHERTEXT_BYTES,
}
}
}
#[derive(Clone)]
pub struct MlKemPublicKey(Either<Box<MlKem768PublicKey>, Box<MlKem1024PublicKey>>);
#[derive(Clone)]
pub struct MlKemPrivateKey(Either<Box<MlKem768PrivateKey>, Box<MlKem1024PrivateKey>>);
#[derive(Clone)]
enum Either<T768, T1024> {
MlKem768(T768),
MlKem1024(T1024),
}
impl MlKemPrivateKey {
/// Generates a new key pair, returning `(public_key, private_key)`.
///
/// The private key is a 64-byte seed. Keep it secret.
pub fn generate(algorithm: Algorithm) -> Result<(MlKemPublicKey, MlKemPrivateKey), ErrorStack> {
match algorithm {
Algorithm::MlKem768 => {
let (pk, sk) = MlKem768PrivateKey::generate()?;
Ok((
MlKemPublicKey(Either::MlKem768(pk)),
MlKemPrivateKey(Either::MlKem768(sk)),
))
}
Algorithm::MlKem1024 => {
let (pk, sk) = MlKem1024PrivateKey::generate()?;
Ok((
MlKemPublicKey(Either::MlKem1024(pk)),
MlKemPrivateKey(Either::MlKem1024(sk)),
))
}
}
}
}
impl MlKemPublicKey {
pub fn from_slice(algorithm: Algorithm, public_key: &[u8]) -> Result<Self, ErrorStack> {
match algorithm {
Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new(
MlKem768PublicKey::from_slice(public_key)?,
)))),
Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new(
MlKem1024PublicKey::from_slice(public_key)?,
)))),
}
}
/// Serialized bytes of the public key
pub fn as_bytes(&self) -> &[u8] {
match &self.0 {
Either::MlKem768(pk) => &pk.bytes,
Either::MlKem1024(pk) => &pk.bytes,
}
}
/// Encapsulates a shared secret to the given public key, returning
/// `(ciphertext, shared_secret)`.
pub fn encapsulate(&self) -> Result<(Vec<u8>, MlKemSharedSecret), ErrorStack> {
match &self.0 {
Either::MlKem768(pk) => {
let (ct, ss) = pk.encapsulate();
Ok((ct.to_vec(), ss))
}
Either::MlKem1024(pk) => {
let (ct, ss) = pk.encapsulate();
Ok((ct.to_vec(), ss))
}
}
}
/// Query public key and ciphertext length
pub fn algorithm(&self) -> Algorithm {
match self.0 {
Either::MlKem768(_) => Algorithm::MlKem768,
Either::MlKem1024(_) => Algorithm::MlKem1024,
}
}
}
impl MlKemPrivateKey {
/// Expand private key from the seed bytes
pub fn from_seed(
algorithm: Algorithm,
private_seed: &MlKemPrivateKeySeed,
) -> Result<Self, ErrorStack> {
match algorithm {
Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new(
MlKem768PrivateKey::from_seed(private_seed)?,
)))),
Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new(
MlKem1024PrivateKey::from_seed(private_seed)?,
)))),
}
}
/// Secret seed bytes of this private key
pub fn seed_bytes(&self) -> &MlKemPrivateKeySeed {
match &self.0 {
Either::MlKem768(sk) => &sk.seed,
Either::MlKem1024(sk) => &sk.seed,
}
}
/// Decapsulates a shared secret from a ciphertext using the private key.
pub fn decapsulate(&self, ciphertext: &[u8]) -> Result<MlKemSharedSecret, ErrorStack> {
match &self.0 {
Either::MlKem768(sk) => {
let ct: &[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES] = ciphertext
.try_into()
.map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?;
Ok(sk.decapsulate(ct))
}
Either::MlKem1024(sk) => {
let ct: &[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES] = ciphertext
.try_into()
.map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?;
Ok(sk.decapsulate(ct))
}
}
}
/// Query public key and ciphertext length
pub fn algorithm(&self) -> Algorithm {
match self.0 {
Either::MlKem768(_) => Algorithm::MlKem768,
Either::MlKem1024(_) => Algorithm::MlKem1024,
}
}
}
/// ML-KEM-768 private key.
///
/// Caches the expanded key for fast decapsulation.
struct MlKem768PrivateKey {
seed: MlKemPrivateKeySeed,
expanded: ffi::MLKEM768_private_key,
}
impl Clone for MlKem768PrivateKey {
fn clone(&self) -> Self {
// unwrap is safe: cloning a valid key with a valid seed always succeeds
Self::from_seed(&self.seed).unwrap()
}
}
impl MlKem768PrivateKey {
pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM768_CIPHERTEXT_BYTES as usize;
/// Generate a new key pair.
fn generate() -> Result<(Box<MlKem768PublicKey>, Box<MlKem768PrivateKey>), ErrorStack> {
// SAFETY: all buffers are out parameters, correctly sized
unsafe {
ffi::init();
let mut bytes = [0; MlKem768PublicKey::PUBLIC_KEY_BYTES];
let mut seed = [0; PRIVATE_KEY_SEED_BYTES];
let mut expanded: MaybeUninit<ffi::MLKEM768_private_key> = MaybeUninit::uninit();
ffi::MLKEM768_generate_key(
bytes.as_mut_ptr().cast(),
seed.as_mut_ptr(),
expanded.as_mut_ptr(),
);
Ok((
Box::new(MlKem768PublicKey::from_slice(&bytes)?),
Box::new(MlKem768PrivateKey {
seed,
expanded: expanded.assume_init(),
}),
))
}
}
/// Restore private key from seed.
fn from_seed(seed: &MlKemPrivateKeySeed) -> Result<Self, ErrorStack> {
// SAFETY: seed is 64 bytes, out parameter correctly sized
unsafe {
ffi::init();
let mut expanded: MaybeUninit<ffi::MLKEM768_private_key> = MaybeUninit::uninit();
cvt(ffi::MLKEM768_private_key_from_seed(
expanded.as_mut_ptr(),
seed.as_ptr(),
seed.len(),
))?;
Ok(Self {
seed: *seed,
expanded: expanded.assume_init(),
})
}
}
/// Derive the public key.
#[cfg(test)]
fn public_key(&self) -> Result<MlKem768PublicKey, ErrorStack> {
// SAFETY: expanded key is valid, buffers correctly sized
unsafe {
ffi::init();
let mut parsed: MaybeUninit<ffi::MLKEM768_public_key> = MaybeUninit::uninit();
ffi::MLKEM768_public_from_private(parsed.as_mut_ptr(), &self.expanded);
let mut bytes = [0u8; MlKem768PublicKey::PUBLIC_KEY_BYTES];
let mut cbb: MaybeUninit<ffi::CBB> = MaybeUninit::uninit();
cvt(ffi::CBB_init_fixed(
cbb.as_mut_ptr(),
bytes.as_mut_ptr(),
bytes.len(),
))?;
cvt(ffi::MLKEM768_marshal_public_key(
cbb.as_mut_ptr(),
parsed.as_ptr(),
))?;
Ok(MlKem768PublicKey {
bytes,
parsed: parsed.assume_init(),
})
}
}
/// Decapsulate to get the shared secret.
fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret {
// SAFETY: expanded key is valid, ciphertext is correctly sized
unsafe {
ffi::init();
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
ffi::MLKEM768_decap(
shared_secret.as_mut_ptr(),
ciphertext.as_ptr(),
ciphertext.len(),
&self.expanded,
);
shared_secret
}
}
}
impl fmt::Debug for MlKem768PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MlKem768PrivateKey")
.field("key", &"[redacted]")
.finish()
}
}
impl Drop for MlKem768PrivateKey {
fn drop(&mut self) {
// SAFETY: pointers and lengths are valid
unsafe {
ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len());
ffi::OPENSSL_cleanse(
self.expanded.opaque.bytes.as_mut_ptr().cast(),
self.expanded.opaque.bytes.len(),
);
}
}
}
/// ML-KEM-768 public key.
#[derive(Clone)]
struct MlKem768PublicKey {
bytes: [u8; Self::PUBLIC_KEY_BYTES],
parsed: ffi::MLKEM768_public_key,
}
impl MlKem768PublicKey {
pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM768_PUBLIC_KEY_BYTES as usize;
/// Parse and validate a public key.
///
/// The slice must be [`Self::PUBLIC_KEY_BYTES`] long.
fn from_slice(slice: &[u8]) -> Result<Self, ErrorStack> {
if slice.len() != Self::PUBLIC_KEY_BYTES {
return Err(ErrorStack::internal_error_str("invalid public key length"));
}
// SAFETY: CBS correctly initialized, length already checked
unsafe {
ffi::init();
let mut cbs = cbs_init(slice);
let mut parsed: MaybeUninit<ffi::MLKEM768_public_key> = MaybeUninit::uninit();
cvt(ffi::MLKEM768_parse_public_key(
parsed.as_mut_ptr(),
&mut cbs,
))?;
if cbs.len != 0 {
return Err(ErrorStack::internal_error_str(
"trailing bytes after public key",
));
}
let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES];
bytes.copy_from_slice(slice);
Ok(Self {
bytes,
parsed: parsed.assume_init(),
})
}
}
/// Encapsulate: returns (ciphertext, shared_secret).
fn encapsulate(
&self,
) -> (
[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES],
MlKemSharedSecret,
) {
// SAFETY: buffers correctly sized, parsed key is valid
unsafe {
ffi::init();
let mut ciphertext = [0u8; MlKem768PrivateKey::CIPHERTEXT_BYTES];
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
ffi::MLKEM768_encap(
ciphertext.as_mut_ptr(),
shared_secret.as_mut_ptr(),
&self.parsed,
);
(ciphertext, shared_secret)
}
}
}
impl fmt::Debug for MlKem768PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MlKem768PublicKey")
.field("bytes", &format_args!("[{}]", self.bytes.len()))
.finish()
}
}
/// ML-KEM-1024 private key.
///
/// Prefer ML-KEM-768 unless you need AES-256 equivalent security.
/// Caches the expanded key for fast decapsulation.
struct MlKem1024PrivateKey {
seed: MlKemPrivateKeySeed,
expanded: ffi::MLKEM1024_private_key,
}
impl Clone for MlKem1024PrivateKey {
fn clone(&self) -> Self {
// unwrap is safe: cloning a valid key with a valid seed always succeeds
Self::from_seed(&self.seed).unwrap()
}
}
impl MlKem1024PrivateKey {
pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM1024_CIPHERTEXT_BYTES as usize;
/// Generate a new key pair.
fn generate() -> Result<(Box<MlKem1024PublicKey>, Box<MlKem1024PrivateKey>), ErrorStack> {
// SAFETY: all buffers are out parameters, correctly sized
unsafe {
ffi::init();
let mut bytes = [0; MlKem1024PublicKey::PUBLIC_KEY_BYTES];
let mut seed = [0; PRIVATE_KEY_SEED_BYTES];
let mut expanded: MaybeUninit<ffi::MLKEM1024_private_key> = MaybeUninit::uninit();
ffi::MLKEM1024_generate_key(
bytes.as_mut_ptr().cast(),
seed.as_mut_ptr(),
expanded.as_mut_ptr(),
);
Ok((
Box::new(MlKem1024PublicKey::from_slice(&bytes)?),
Box::new(MlKem1024PrivateKey {
seed,
expanded: expanded.assume_init(),
}),
))
}
}
/// Restore private key from seed.
fn from_seed(seed: &MlKemPrivateKeySeed) -> Result<Self, ErrorStack> {
// SAFETY: seed is 64 bytes, out parameter correctly sized
unsafe {
ffi::init();
let mut expanded: MaybeUninit<ffi::MLKEM1024_private_key> = MaybeUninit::uninit();
cvt(ffi::MLKEM1024_private_key_from_seed(
expanded.as_mut_ptr(),
seed.as_ptr(),
seed.len(),
))?;
Ok(Self {
seed: *seed,
expanded: expanded.assume_init(),
})
}
}
/// Derive the public key.
#[cfg(test)]
fn public_key(&self) -> Result<MlKem1024PublicKey, ErrorStack> {
// SAFETY: expanded key is valid, buffers correctly sized
unsafe {
ffi::init();
let mut parsed: MaybeUninit<ffi::MLKEM1024_public_key> = MaybeUninit::uninit();
ffi::MLKEM1024_public_from_private(parsed.as_mut_ptr(), &self.expanded);
let mut bytes = [0u8; MlKem1024PublicKey::PUBLIC_KEY_BYTES];
let mut cbb: MaybeUninit<ffi::CBB> = MaybeUninit::uninit();
cvt(ffi::CBB_init_fixed(
cbb.as_mut_ptr(),
bytes.as_mut_ptr(),
bytes.len(),
))?;
cvt(ffi::MLKEM1024_marshal_public_key(
cbb.as_mut_ptr(),
parsed.as_ptr(),
))?;
Ok(MlKem1024PublicKey {
bytes,
parsed: parsed.assume_init(),
})
}
}
/// Decapsulate to get the shared secret.
fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret {
// SAFETY: expanded key is valid, ciphertext is correctly sized
unsafe {
ffi::init();
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
ffi::MLKEM1024_decap(
shared_secret.as_mut_ptr(),
ciphertext.as_ptr(),
ciphertext.len(),
&self.expanded,
);
shared_secret
}
}
}
impl fmt::Debug for MlKem1024PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MlKem1024PrivateKey")
.field("key", &"[redacted]")
.finish()
}
}
impl Drop for MlKem1024PrivateKey {
fn drop(&mut self) {
// SAFETY: pointers and lengths are valid
unsafe {
ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len());
ffi::OPENSSL_cleanse(
self.expanded.opaque.bytes.as_mut_ptr().cast(),
self.expanded.opaque.bytes.len(),
);
}
}
}
/// ML-KEM-1024 public key.
///
/// Prefer ML-KEM-768 unless you need AES-256 equivalent security.
#[derive(Clone)]
struct MlKem1024PublicKey {
bytes: [u8; Self::PUBLIC_KEY_BYTES],
parsed: ffi::MLKEM1024_public_key,
}
impl MlKem1024PublicKey {
pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM1024_PUBLIC_KEY_BYTES as usize;
/// Parse and validate a serialized public key.
///
/// The slice must be [`Self::PUBLIC_KEY_BYTES`] long.
fn from_slice(slice: &[u8]) -> Result<Self, ErrorStack> {
if slice.len() != Self::PUBLIC_KEY_BYTES {
return Err(ErrorStack::internal_error_str("invalid public key length"));
}
// SAFETY: CBS correctly initialized, length already checked
unsafe {
ffi::init();
let mut cbs = cbs_init(slice);
let mut parsed: MaybeUninit<ffi::MLKEM1024_public_key> = MaybeUninit::uninit();
cvt(ffi::MLKEM1024_parse_public_key(
parsed.as_mut_ptr(),
&mut cbs,
))?;
if cbs.len != 0 {
return Err(ErrorStack::internal_error_str(
"trailing bytes after public key",
));
}
let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES];
bytes.copy_from_slice(slice);
Ok(Self {
bytes,
parsed: parsed.assume_init(),
})
}
}
/// Encapsulate: returns (ciphertext, shared_secret).
fn encapsulate(
&self,
) -> (
[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES],
[u8; SHARED_SECRET_BYTES],
) {
// SAFETY: buffers correctly sized, parsed key is valid
unsafe {
ffi::init();
let mut ciphertext = [0u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES];
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
ffi::MLKEM1024_encap(
ciphertext.as_mut_ptr(),
shared_secret.as_mut_ptr(),
&self.parsed,
);
(ciphertext, shared_secret)
}
}
}
impl fmt::Debug for MlKem1024PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MlKem1024PublicKey")
.field("bytes", &format_args!("[{}]", self.bytes.len()))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! mlkem_tests {
($name:ident, $priv:ty, $pub:ty, $ct_len:expr) => {
mod $name {
use super::*;
#[test]
fn roundtrip() {
let (pk, sk) = <$priv>::generate().unwrap();
let (ct, ss1) = pk.encapsulate();
let ss2 = sk.decapsulate(&ct);
assert_eq!(ss1, ss2);
}
#[test]
fn seed_roundtrip() {
let (pk, sk) = <$priv>::generate().unwrap();
let sk2 = <$priv>::from_seed(&sk.seed).unwrap();
let (ct, ss1) = pk.encapsulate();
let ss2 = sk2.decapsulate(&ct);
assert_eq!(ss1, ss2);
}
#[test]
fn derive_pubkey() {
let (pk, sk) = <$priv>::generate().unwrap();
assert_eq!(pk.bytes, sk.public_key().unwrap().bytes);
}
#[test]
fn from_slice_rejects_bad_len() {
assert!(<$pub>::from_slice(&[0u8; 100]).is_err());
assert!(<$pub>::from_slice(&[]).is_err());
}
#[test]
fn from_slice_roundtrip() {
let (pk, _) = <$priv>::generate().unwrap();
let pk2 = <$pub>::from_slice(&pk.bytes).unwrap();
assert_eq!(pk.bytes, pk2.bytes);
}
#[test]
fn implicit_rejection() {
let (_, sk) = <$priv>::generate().unwrap();
let bad_ct = [0x42u8; $ct_len];
// bad ciphertext still "works", just returns deterministic garbage
let ss1 = sk.decapsulate(&bad_ct);
let ss2 = sk.decapsulate(&bad_ct);
assert_eq!(ss1, ss2);
}
#[test]
fn debug_redacts_seed() {
let (_, sk) = <$priv>::generate().unwrap();
let dbg = format!("{:?}", sk);
assert!(dbg.contains("redacted"));
}
}
};
}
mlkem_tests!(mlkem768, MlKem768PrivateKey, MlKem768PublicKey, 1088);
mlkem_tests!(mlkem1024, MlKem1024PrivateKey, MlKem1024PublicKey, 1568);
// Tests for unified API (MlKem struct)
mod unified_api {
use super::*;
macro_rules! unified_tests {
($name:ident, $algorithm:expr, $pk_len:expr, $ct_len:expr) => {
mod $name {
use super::*;
#[test]
fn roundtrip() {
let (pk, sk) = MlKemPrivateKey::generate($algorithm).unwrap();
let (ct, ss1) = pk.encapsulate().unwrap();
let ss2 = sk.decapsulate(&ct).unwrap();
assert_eq!(ss1, ss2);
}
#[test]
fn key_sizes() {
assert_eq!($algorithm.public_key_bytes(), $pk_len);
assert_eq!($algorithm.ciphertext_bytes(), $ct_len);
let (pk, private_key) = MlKemPrivateKey::generate($algorithm).unwrap();
assert_eq!(pk.as_bytes().len(), $pk_len);
assert_eq!(private_key.seed_bytes().len(), PRIVATE_KEY_SEED_BYTES);
let (ct, ss) = pk.encapsulate().unwrap();
assert_eq!(ct.len(), $ct_len);
assert_eq!(ss.len(), SHARED_SECRET_BYTES);
}
#[test]
fn invalid_public_key_length() {
let result = MlKemPublicKey::from_slice($algorithm, &[0u8; 100]);
assert!(result.is_err());
}
#[test]
fn invalid_ciphertext_length() {
let (_, sk) = MlKemPrivateKey::generate($algorithm).unwrap();
let result = sk.decapsulate(&[0u8; 100]);
assert!(result.is_err());
}
}
};
}
unified_tests!(mlkem768, Algorithm::MlKem768, 1184, 1088);
unified_tests!(mlkem1024, Algorithm::MlKem1024, 1568, 1568);
#[test]
fn params_constants() {
assert_eq!(Algorithm::MlKem768.public_key_bytes(), 1184);
assert_eq!(Algorithm::MlKem768.ciphertext_bytes(), 1088);
assert_eq!(Algorithm::MlKem1024.public_key_bytes(), 1568);
assert_eq!(Algorithm::MlKem1024.ciphertext_bytes(), 1568);
}
}
}

View File

@ -1,6 +1,6 @@
//! A collection of numerical identifiers for OpenSSL objects.
use crate::ffi;
use libc::{c_char, c_int};
use libc::c_int;
use openssl_macros::corresponds;
use std::ffi::CStr;
@ -87,7 +87,7 @@ impl Nid {
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn long_name(&self) -> Result<&'static str, ErrorStack> {
unsafe {
let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0) as *mut c_char)?;
let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0).cast_mut())?;
CStr::from_ptr(nameptr)
.to_str()
.map_err(ErrorStack::internal_error)
@ -99,7 +99,7 @@ impl Nid {
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn short_name(&self) -> Result<&'static str, ErrorStack> {
unsafe {
let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0) as *mut c_char)?;
let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0).cast_mut())?;
CStr::from_ptr(nameptr)
.to_str()
.map_err(ErrorStack::internal_error)

View File

@ -10,7 +10,7 @@ use std::ptr;
use crate::error::ErrorStack;
use crate::nid::Nid;
use crate::pkey::{HasPrivate, PKey, PKeyRef, Private};
use crate::stack::Stack;
use crate::stack::{Stack, StackRef};
use crate::x509::{X509Ref, X509};
use crate::{cvt_0i, cvt_p};
@ -31,33 +31,43 @@ impl Pkcs12Ref {
ffi::i2d_PKCS12
}
/// Extracts the contents of the `Pkcs12`.
/// Extracts the contents of the `Pkcs12` with `pkey` and `cert` required.
pub fn parse(&self, pass: &str) -> Result<ParsedPkcs12, ErrorStack> {
let p2 = self.parse2(pass)?;
Ok(ParsedPkcs12 {
pkey: p2
.pkey
.ok_or_else(|| ErrorStack::internal_error_str("missing pkey"))?,
cert: p2
.cert
.ok_or_else(|| ErrorStack::internal_error_str("missing cert"))?,
chain: p2.ca,
})
}
/// Extracts the contents of the `Pkcs12` with `pkey` and `cert` optional.
#[corresponds(PKCS12_parse)]
pub fn parse2(&self, pass: &str) -> Result<ParsedPkcs12_2, ErrorStack> {
unsafe {
let pass = CString::new(pass.as_bytes()).map_err(ErrorStack::internal_error)?;
let mut pkey = ptr::null_mut();
let mut cert = ptr::null_mut();
let mut chain = ptr::null_mut();
let mut ca = ptr::null_mut();
cvt_0i(ffi::PKCS12_parse(
self.as_ptr(),
pass.as_ptr(),
&mut pkey,
&mut cert,
&mut chain,
&mut ca,
))?;
let pkey = PKey::from_ptr(pkey);
let cert = X509::from_ptr(cert);
let pkey = (!pkey.is_null()).then(|| PKey::from_ptr(pkey));
let cert = (!cert.is_null()).then(|| X509::from_ptr(cert));
let ca = (!ca.is_null()).then(|| Stack::from_ptr(ca));
let chain = if chain.is_null() {
None
} else {
Some(Stack::from_ptr(chain))
};
Ok(ParsedPkcs12 { pkey, cert, chain })
Ok(ParsedPkcs12_2 { pkey, cert, ca })
}
}
}
@ -100,6 +110,19 @@ pub struct ParsedPkcs12 {
pub chain: Option<Stack<X509>>,
}
/// [`ParsedPkcs12`] with optional fields
pub struct ParsedPkcs12_2 {
pub pkey: Option<PKey<Private>>,
pub cert: Option<X509>,
pub ca: Option<Stack<X509>>,
}
impl ParsedPkcs12_2 {
pub fn chain(&self) -> Option<&StackRef<X509>> {
self.ca.as_deref()
}
}
pub struct Pkcs12Builder {
nid_key: Nid,
nid_cert: Nid,
@ -180,8 +203,8 @@ impl Pkcs12Builder {
let keytype = 0;
cvt_p(ffi::PKCS12_create(
pass.as_ptr() as *const _ as *mut _,
friendly_name.as_ptr() as *const _ as *mut _,
pass.as_ptr(),
friendly_name.as_ptr(),
pkey,
cert,
ca,
@ -260,7 +283,7 @@ mod test {
.unwrap();
builder.set_subject_name(&name).unwrap();
builder.set_issuer_name(&name).unwrap();
builder.append_extension(key_usage).unwrap();
builder.append_extension(&key_usage).unwrap();
builder.set_pubkey(&pkey).unwrap();
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
let cert = builder.build();

View File

@ -1,11 +1,11 @@
use crate::ffi;
use libc::{c_int, c_uint};
use std::ffi::c_int;
use std::ptr;
use crate::cvt;
use crate::error::ErrorStack;
use crate::hash::MessageDigest;
use crate::symm::Cipher;
use crate::{cvt, cvt_nz, try_int};
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct KeyIvPair {
@ -49,7 +49,7 @@ pub fn bytes_to_key(
let cipher = cipher.as_ptr();
let digest = digest.as_ptr();
let len = cvt(ffi::EVP_BytesToKey(
let len = cvt_nz(ffi::EVP_BytesToKey(
cipher,
digest,
salt_ptr,
@ -60,7 +60,7 @@ pub fn bytes_to_key(
ptr::null_mut(),
))?;
let mut key = vec![0; len as usize];
let mut key = vec![0; len.get()];
let iv_ptr = iv
.as_mut()
.map(|v| v.as_mut_ptr())
@ -90,22 +90,18 @@ pub fn pbkdf2_hmac(
key: &mut [u8],
) -> Result<(), ErrorStack> {
unsafe {
assert!(pass.len() <= c_int::MAX as usize);
assert!(salt.len() <= c_int::MAX as usize);
assert!(key.len() <= c_int::MAX as usize);
ffi::init();
cvt(ffi::PKCS5_PBKDF2_HMAC(
pass.as_ptr() as *const _,
pass.as_ptr().cast(),
pass.len(),
salt.as_ptr(),
salt.len(),
iter as c_uint,
try_int(iter)?,
hash.as_ptr(),
key.len(),
key.as_mut_ptr(),
))
.map(|_| ())
}
}
@ -122,18 +118,17 @@ pub fn scrypt(
unsafe {
ffi::init();
cvt(ffi::EVP_PBE_scrypt(
pass.as_ptr() as *const _,
pass.as_ptr().cast(),
pass.len(),
salt.as_ptr() as *const _,
salt.as_ptr().cast(),
salt.len(),
n,
r,
p,
maxmem,
key.as_mut_ptr() as *mut _,
key.as_mut_ptr(),
key.len(),
))
.map(|_| ())
}
}

View File

@ -40,7 +40,6 @@
//! println!("{:?}", str::from_utf8(pub_key.as_slice()).unwrap());
//! ```
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_int, c_long};
use openssl_macros::corresponds;
@ -54,7 +53,9 @@ use crate::dh::Dh;
use crate::dsa::Dsa;
use crate::ec::EcKey;
use crate::error::ErrorStack;
use crate::ffi;
use crate::rsa::Rsa;
use crate::try_int;
use crate::util::{invoke_passwd_cb, CallbackState};
use crate::{cvt, cvt_0i, cvt_p};
@ -361,7 +362,7 @@ impl<T> PKey<T> {
cvt(ffi::EVP_PKEY_assign(
pkey.0,
ffi::EVP_PKEY_RSA,
rsa.as_ptr() as *mut _,
rsa.as_ptr().cast(),
))?;
mem::forget(rsa);
Ok(pkey)
@ -377,7 +378,7 @@ impl<T> PKey<T> {
cvt(ffi::EVP_PKEY_assign(
pkey.0,
ffi::EVP_PKEY_EC,
ec_key.as_ptr() as *mut _,
ec_key.as_ptr().cast(),
))?;
mem::forget(ec_key);
Ok(pkey)
@ -455,7 +456,7 @@ impl PKey<Private> {
bio.as_ptr(),
ptr::null_mut(),
Some(invoke_passwd_cb::<F>),
&mut cb as *mut _ as *mut _,
std::ptr::addr_of_mut!(cb).cast(),
))
.map(|p| PKey::from_ptr(p))
}
@ -479,7 +480,7 @@ impl PKey<Private> {
bio.as_ptr(),
ptr::null_mut(),
None,
passphrase.as_ptr() as *const _ as *mut _,
passphrase.as_ptr().cast_mut().cast(),
))
.map(|p| PKey::from_ptr(p))
}

View File

@ -36,7 +36,7 @@ pub fn rand_bytes(buf: &mut [u8]) -> Result<(), ErrorStack> {
unsafe {
ffi::init();
assert!(buf.len() <= c_int::MAX as usize);
cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len())).map(|_| ())
cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len()))
}
}

View File

@ -23,7 +23,6 @@
//! let mut buf = vec![0; rsa.size() as usize];
//! let encrypted_len = rsa.public_encrypt(data, &mut buf, Padding::PKCS1).unwrap();
//! ```
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::c_int;
use openssl_macros::corresponds;
@ -33,7 +32,9 @@ use std::ptr;
use crate::bn::{BigNum, BigNumRef};
use crate::error::ErrorStack;
use crate::ffi;
use crate::pkey::{HasPrivate, HasPublic, Private, Public};
use crate::try_int;
use crate::{cvt, cvt_n, cvt_p};
pub const EVP_PKEY_OP_SIGN: c_int = 1 << 3;
@ -194,7 +195,7 @@ where
unsafe {
let mut d = ptr::null();
RSA_get0_key(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut d);
BigNumRef::from_ptr(d as *mut _)
BigNumRef::from_ptr(d.cast_mut())
}
}
@ -208,7 +209,7 @@ where
if p.is_null() {
None
} else {
Some(BigNumRef::from_ptr(p as *mut _))
Some(BigNumRef::from_ptr(p.cast_mut()))
}
}
}
@ -223,7 +224,7 @@ where
if q.is_null() {
None
} else {
Some(BigNumRef::from_ptr(q as *mut _))
Some(BigNumRef::from_ptr(q.cast_mut()))
}
}
}
@ -238,7 +239,7 @@ where
if dp.is_null() {
None
} else {
Some(BigNumRef::from_ptr(dp as *mut _))
Some(BigNumRef::from_ptr(dp.cast_mut()))
}
}
}
@ -253,7 +254,7 @@ where
if dq.is_null() {
None
} else {
Some(BigNumRef::from_ptr(dq as *mut _))
Some(BigNumRef::from_ptr(dq.cast_mut()))
}
}
}
@ -268,7 +269,7 @@ where
if qi.is_null() {
None
} else {
Some(BigNumRef::from_ptr(qi as *mut _))
Some(BigNumRef::from_ptr(qi.cast_mut()))
}
}
}
@ -391,7 +392,7 @@ where
unsafe {
let mut n = ptr::null();
RSA_get0_key(self.as_ptr(), &mut n, ptr::null_mut(), ptr::null_mut());
BigNumRef::from_ptr(n as *mut _)
BigNumRef::from_ptr(n.cast_mut())
}
}
@ -402,7 +403,7 @@ where
unsafe {
let mut e = ptr::null();
RSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut e, ptr::null_mut());
BigNumRef::from_ptr(e as *mut _)
BigNumRef::from_ptr(e.cast_mut())
}
}
}
@ -416,7 +417,7 @@ impl Rsa<Public> {
pub fn from_public_components(n: BigNum, e: BigNum) -> Result<Rsa<Public>, ErrorStack> {
unsafe {
let rsa = cvt_p(ffi::RSA_new())?;
RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut());
cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut()))?;
mem::forget((n, e));
Ok(Rsa::from_ptr(rsa))
}
@ -474,7 +475,7 @@ impl RsaPrivateKeyBuilder {
pub fn new(n: BigNum, e: BigNum, d: BigNum) -> Result<RsaPrivateKeyBuilder, ErrorStack> {
unsafe {
let rsa = cvt_p(ffi::RSA_new())?;
RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr());
cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr()))?;
mem::forget((n, e, d));
Ok(RsaPrivateKeyBuilder {
rsa: Rsa::from_ptr(rsa),
@ -485,12 +486,10 @@ impl RsaPrivateKeyBuilder {
/// Sets the factors of the Rsa key.
///
/// `p` and `q` are the first and second factors of `n`.
///
// FIXME should be infallible
#[corresponds(RSA_set0_factors)]
pub fn set_factors(self, p: BigNum, q: BigNum) -> Result<RsaPrivateKeyBuilder, ErrorStack> {
unsafe {
RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr());
cvt(RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr()))?;
mem::forget((p, q));
}
Ok(self)
@ -500,8 +499,6 @@ impl RsaPrivateKeyBuilder {
///
/// `dmp1`, `dmq1`, and `iqmp` are the exponents and coefficient for
/// CRT calculations which is used to speed up RSA operations.
///
// FIXME should be infallible
#[corresponds(RSA_set0_crt_params)]
pub fn set_crt_params(
self,
@ -510,12 +507,12 @@ impl RsaPrivateKeyBuilder {
iqmp: BigNum,
) -> Result<RsaPrivateKeyBuilder, ErrorStack> {
unsafe {
RSA_set0_crt_params(
cvt(RSA_set0_crt_params(
self.rsa.as_ptr(),
dmp1.as_ptr(),
dmq1.as_ptr(),
iqmp.as_ptr(),
);
))?;
mem::forget((dmp1, dmq1, iqmp));
}
Ok(self)

View File

@ -54,7 +54,6 @@ use std::mem::MaybeUninit;
/// SHA1 is known to be insecure - it should not be used unless required for
/// compatibility with existing systems.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn sha1(data: &[u8]) -> [u8; 20] {
unsafe {
@ -66,7 +65,6 @@ pub fn sha1(data: &[u8]) -> [u8; 20] {
/// Computes the SHA224 hash of some data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn sha224(data: &[u8]) -> [u8; 28] {
unsafe {
@ -78,7 +76,6 @@ pub fn sha224(data: &[u8]) -> [u8; 28] {
/// Computes the SHA256 hash of some data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn sha256(data: &[u8]) -> [u8; 32] {
unsafe {
@ -90,7 +87,6 @@ pub fn sha256(data: &[u8]) -> [u8; 32] {
/// Computes the SHA384 hash of some data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn sha384(data: &[u8]) -> [u8; 48] {
unsafe {
@ -102,7 +98,6 @@ pub fn sha384(data: &[u8]) -> [u8; 48] {
/// Computes the SHA512 hash of some data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn sha512(data: &[u8]) -> [u8; 64] {
unsafe {
@ -114,7 +109,6 @@ pub fn sha512(data: &[u8]) -> [u8; 64] {
/// Computes the SHA512-256 hash of some data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn sha512_256(data: &[u8]) -> [u8; 32] {
unsafe {
@ -143,7 +137,6 @@ impl Default for Sha1 {
impl Sha1 {
/// Creates a new hasher.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn new() -> Sha1 {
unsafe {
@ -159,13 +152,12 @@ impl Sha1 {
#[inline]
pub fn update(&mut self, buf: &[u8]) {
unsafe {
ffi::SHA1_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
ffi::SHA1_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
}
}
/// Returns the hash of the data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn finish(mut self) -> [u8; 20] {
unsafe {
@ -190,7 +182,6 @@ impl Default for Sha224 {
impl Sha224 {
/// Creates a new hasher.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn new() -> Sha224 {
unsafe {
@ -206,13 +197,12 @@ impl Sha224 {
#[inline]
pub fn update(&mut self, buf: &[u8]) {
unsafe {
ffi::SHA224_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
ffi::SHA224_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
}
}
/// Returns the hash of the data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn finish(mut self) -> [u8; 28] {
unsafe {
@ -237,7 +227,6 @@ impl Default for Sha256 {
impl Sha256 {
/// Creates a new hasher.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn new() -> Sha256 {
unsafe {
@ -253,13 +242,12 @@ impl Sha256 {
#[inline]
pub fn update(&mut self, buf: &[u8]) {
unsafe {
ffi::SHA256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
ffi::SHA256_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
}
}
/// Returns the hash of the data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn finish(mut self) -> [u8; 32] {
unsafe {
@ -284,7 +272,6 @@ impl Default for Sha384 {
impl Sha384 {
/// Creates a new hasher.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn new() -> Sha384 {
unsafe {
@ -300,13 +287,12 @@ impl Sha384 {
#[inline]
pub fn update(&mut self, buf: &[u8]) {
unsafe {
ffi::SHA384_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
ffi::SHA384_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
}
}
/// Returns the hash of the data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn finish(mut self) -> [u8; 48] {
unsafe {
@ -331,7 +317,6 @@ impl Default for Sha512 {
impl Sha512 {
/// Creates a new hasher.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn new() -> Sha512 {
unsafe {
@ -347,13 +332,12 @@ impl Sha512 {
#[inline]
pub fn update(&mut self, buf: &[u8]) {
unsafe {
ffi::SHA512_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
ffi::SHA512_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
}
}
/// Returns the hash of the data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn finish(mut self) -> [u8; 64] {
unsafe {
@ -378,7 +362,6 @@ impl Default for Sha512_256 {
impl Sha512_256 {
/// Creates a new hasher.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn new() -> Sha512_256 {
unsafe {
@ -394,13 +377,12 @@ impl Sha512_256 {
#[inline]
pub fn update(&mut self, buf: &[u8]) {
unsafe {
ffi::SHA512_256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
ffi::SHA512_256_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
}
}
/// Returns the hash of the data.
#[inline]
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[must_use]
pub fn finish(mut self) -> [u8; 32] {
unsafe {

View File

@ -174,7 +174,6 @@ impl<'a> Signer<'a> {
self.pctx,
padding.as_raw(),
))
.map(|_| ())
}
}
@ -188,7 +187,6 @@ impl<'a> Signer<'a> {
self.pctx,
len.as_raw(),
))
.map(|_| ())
}
}
@ -200,9 +198,8 @@ impl<'a> Signer<'a> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md(
self.pctx,
md.as_ptr() as *mut _,
md.as_ptr().cast_mut(),
))
.map(|_| ())
}
}
@ -215,10 +212,9 @@ impl<'a> Signer<'a> {
unsafe {
cvt(ffi::EVP_DigestUpdate(
self.md_ctx,
buf.as_ptr() as *const _,
buf.as_ptr().cast(),
buf.len(),
))
.map(|_| ())
}
}
@ -255,7 +251,7 @@ impl<'a> Signer<'a> {
let mut len = buf.len();
cvt(ffi::EVP_DigestSignFinal(
self.md_ctx,
buf.as_mut_ptr() as *mut _,
buf.as_mut_ptr().cast(),
&mut len,
))?;
Ok(len)
@ -290,9 +286,9 @@ impl<'a> Signer<'a> {
let mut sig_len = sig_buf.len();
cvt(ffi::EVP_DigestSign(
self.md_ctx,
sig_buf.as_mut_ptr() as *mut _,
sig_buf.as_mut_ptr(),
&mut sig_len,
data_buf.as_ptr() as *const _,
data_buf.as_ptr(),
data_buf.len(),
))?;
Ok(sig_len)
@ -421,7 +417,6 @@ impl<'a> Verifier<'a> {
self.pctx,
padding.as_raw(),
))
.map(|_| ())
}
}
@ -435,7 +430,6 @@ impl<'a> Verifier<'a> {
self.pctx,
len.as_raw(),
))
.map(|_| ())
}
}
@ -447,9 +441,8 @@ impl<'a> Verifier<'a> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md(
self.pctx,
md.as_ptr() as *mut _,
md.as_ptr().cast_mut(),
))
.map(|_| ())
}
}
@ -462,10 +455,9 @@ impl<'a> Verifier<'a> {
unsafe {
cvt(ffi::EVP_DigestUpdate(
self.md_ctx,
buf.as_ptr() as *const _,
buf.as_ptr().cast(),
buf.len(),
))
.map(|_| ())
}
}
@ -474,7 +466,7 @@ impl<'a> Verifier<'a> {
pub fn verify(&self, signature: &[u8]) -> Result<bool, ErrorStack> {
unsafe {
let r =
EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr() as *mut _, signature.len());
EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr().cast_mut(), signature.len());
match r {
1 => Ok(true),
0 => {
@ -492,9 +484,9 @@ impl<'a> Verifier<'a> {
unsafe {
let r = ffi::EVP_DigestVerify(
self.md_ctx,
signature.as_ptr() as *const _,
signature.as_ptr().cast(),
signature.len(),
buf.as_ptr() as *const _,
buf.as_ptr().cast(),
buf.len(),
);
match r {

View File

@ -27,7 +27,7 @@ impl SrtpProtectionProfileRef {
#[must_use]
pub fn name(&self) -> &'static str {
unsafe { CStr::from_ptr((*self.as_ptr()).name as *const _) }
unsafe { CStr::from_ptr((*self.as_ptr()).name.cast()) }
.to_str()
.expect("should be UTF-8")
}

View File

@ -4,7 +4,11 @@ use super::{
Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError,
SslVerifyMode,
};
#[cfg(feature = "credential")]
use crate::error::ErrorStack;
use crate::ex_data::Index;
#[cfg(feature = "credential")]
use crate::ssl::SslCredentialBuilder;
use std::convert::identity;
use std::future::Future;
use std::pin::Pin;
@ -171,6 +175,22 @@ impl SslContextBuilder {
}
}
#[cfg(feature = "credential")]
impl SslCredentialBuilder {
/// Configures a custom private key method on the context.
///
/// A task waker must be set on `Ssl` values associated with the resulting
/// `SslContext` with [`SslRef::set_task_waker`].
///
/// See [`AsyncPrivateKeyMethod`] for more details.
pub fn set_async_private_key_method(
&mut self,
method: impl AsyncPrivateKeyMethod,
) -> Result<(), ErrorStack> {
self.set_private_key_method(AsyncPrivateKeyMethodBridge(Box::new(method)))
}
}
impl SslRef {
pub fn set_async_custom_verify_callback<F>(&mut self, mode: SslVerifyMode, callback: F)
where

View File

@ -44,7 +44,7 @@ pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, BioMethod), ErrorSta
unsafe {
let bio = cvt_p(BIO_new(method.0.get()))?;
BIO_set_data(bio, Box::into_raw(state) as *mut _);
BIO_set_data(bio, Box::into_raw(state).cast());
BIO_set_init(bio, 1);
Ok((bio, method))
@ -76,7 +76,7 @@ pub unsafe extern "C" fn take_stream<S>(bio: *mut BIO) -> S {
assert!(!data.is_null());
let state = Box::<StreamState<S>>::from_raw(data as *mut _);
let state = Box::<StreamState<S>>::from_raw(data.cast());
BIO_set_data(bio, ptr::null_mut());
@ -91,7 +91,7 @@ pub unsafe fn set_dtls_mtu_size<S>(bio: *mut BIO, mtu_size: usize) {
}
unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
let data = BIO_get_data(bio) as *mut StreamState<S>;
let data = BIO_get_data(bio).cast::<StreamState<S>>();
assert!(!data.is_null());
@ -101,8 +101,12 @@ unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int {
BIO_clear_retry_flags(bio);
let Ok(len) = usize::try_from(len) else {
return -1;
};
let state = state::<S>(bio);
let buf = slice::from_raw_parts(buf as *const _, len as usize);
let buf = slice::from_raw_parts(buf.cast(), len);
match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) {
Ok(Ok(len)) => len as c_int,
@ -123,8 +127,12 @@ unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_
unsafe extern "C" fn bread<S: Read>(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int {
BIO_clear_retry_flags(bio);
let Ok(len) = usize::try_from(len) else {
return -1;
};
let state = state::<S>(bio);
let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize);
let buf = slice::from_raw_parts_mut(buf.cast(), len);
match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) {
Ok(Ok(len)) => len as c_int,
@ -163,9 +171,13 @@ unsafe extern "C" fn ctrl<S: Write>(
let state = state::<S>(bio);
if cmd == BIO_CTRL_FLUSH {
BIO_clear_retry_flags(bio);
match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) {
Ok(Ok(())) => 1,
Ok(Err(err)) => {
if retriable_error(&err) {
BIO_set_retry_write(bio);
}
state.error = Some(err);
0
}
@ -197,7 +209,7 @@ unsafe extern "C" fn destroy<S>(bio: *mut BIO) -> c_int {
let data = BIO_get_data(bio);
if !data.is_null() {
drop(Box::<StreamState<S>>::from_raw(data as *mut _));
drop(Box::<StreamState<S>>::from_raw(data.cast()));
BIO_set_data(bio, ptr::null_mut());
}

View File

@ -8,12 +8,15 @@ use super::{
};
use crate::error::ErrorStack;
use crate::ffi;
use crate::hmac::HmacCtxRef;
use crate::ssl::TicketKeyCallbackResult;
use crate::symm::CipherCtxRef;
use crate::x509::{X509StoreContext, X509StoreContextRef};
use foreign_types::ForeignType;
use foreign_types::ForeignTypeRef;
use libc::c_char;
use libc::{c_int, c_uchar, c_uint, c_void};
use libc::{c_char, c_int, c_uchar, c_uint, c_void};
use std::ffi::CStr;
use std::mem::{self, MaybeUninit};
use std::ptr;
use std::slice;
use std::str;
@ -38,7 +41,7 @@ where
// SAFETY: The callback won't outlive the context it's associated with
// because there is no `X509StoreContextRef::ssl_mut(&mut self)` method.
let verify = unsafe { &*(verify as *const F) };
let verify = unsafe { &*std::ptr::from_ref::<F>(verify) };
c_int::from(verify(preverify_ok != 0, ctx))
}
@ -87,7 +90,7 @@ where
// SAFETY: The callback won't outlive the context it's associated with
// because there is no way to get a mutable reference to the `SslContext`,
// so the callback can't replace itself.
let verify = unsafe { &*(verify as *const F) };
let verify = unsafe { &*std::ptr::from_ref::<F>(verify) };
c_int::from(verify(ctx))
}
@ -157,7 +160,7 @@ where
// Give the callback mutable slices into which it can write the identity and psk.
let identity_sl =
unsafe { slice::from_raw_parts_mut(identity as *mut u8, max_identity_len as usize) };
unsafe { slice::from_raw_parts_mut(identity.cast::<u8>(), max_identity_len as usize) };
let psk_sl = unsafe { slice::from_raw_parts_mut(psk, max_psk_len as usize) };
let ssl_context = ssl.ssl_context().to_owned();
@ -269,6 +272,68 @@ where
}
}
unsafe fn to_uninit<'a, T: 'a>(ptr: *mut T) -> &'a mut MaybeUninit<T> {
assert!(!ptr.is_null());
unsafe { &mut *ptr.cast::<MaybeUninit<T>>() }
}
pub(super) unsafe extern "C" fn raw_ticket_key<F>(
ssl: *mut ffi::SSL,
key_name: *mut u8,
iv: *mut u8,
evp_ctx: *mut ffi::EVP_CIPHER_CTX,
hmac_ctx: *mut ffi::HMAC_CTX,
encrypt: c_int,
) -> c_int
where
F: Fn(
&SslRef,
&mut [u8; 16],
&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
&mut CipherCtxRef,
&mut HmacCtxRef,
bool,
) -> TicketKeyCallbackResult
+ 'static
+ Sync
+ Send,
{
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };
let ssl_context = ssl.ssl_context().to_owned();
let callback = ssl_context
.ex_data::<F>(SslContext::cached_ex_index::<F>())
.expect("expected session resumption callback");
// SAFETY: the callback guarantees that key_name is 16 bytes
let key_name =
unsafe { to_uninit(key_name.cast::<[u8; ffi::SSL_TICKET_KEY_NAME_LEN as usize]>()) };
// SAFETY: the callback provides 16 bytes iv
//
// https://github.com/google/boringssl/blob/main/ssl/ssl_session.cc#L331
let iv = unsafe { to_uninit(iv.cast::<[u8; ffi::EVP_MAX_IV_LENGTH as usize]>()) };
// When encrypting a new ticket, encrypt will be one.
let encrypt = encrypt == 1;
// Zero-initialize the key_name and iv, since the application is expected to populate these
// fields in the encrypt mode.
if encrypt {
*key_name = MaybeUninit::zeroed();
*iv = MaybeUninit::zeroed();
}
let key_name = unsafe { key_name.assume_init_mut() };
let iv = unsafe { iv.assume_init_mut() };
// The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL.
let evp_ctx = unsafe { CipherCtxRef::from_ptr_mut(evp_ctx) };
let hmac_ctx = unsafe { HmacCtxRef::from_ptr_mut(hmac_ctx) };
callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into()
}
pub(super) unsafe extern "C" fn raw_alpn_select<F>(
ssl: *mut ffi::SSL,
out: *mut *const c_uchar,
@ -293,7 +358,7 @@ where
match callback(ssl, protos) {
Ok(proto) => {
*out = proto.as_ptr() as *const c_uchar;
*out = proto.as_ptr();
*outlen = proto.len() as c_uchar;
ffi::SSL_TLSEXT_ERR_OK
@ -377,7 +442,7 @@ where
// SAFETY: We can make `callback` outlive `ssl` because it is a callback
// stored in the session context set in `Ssl::new` so it is always
// guaranteed to outlive the lifetime of this function's scope.
let callback = unsafe { &*(callback as *const F) };
let callback = unsafe { &*std::ptr::from_ref::<F>(callback) };
callback(ssl, session);
@ -430,7 +495,7 @@ where
// SAFETY: We can make `callback` outlive `ssl` because it is a callback
// stored in the session context set in `Ssl::new` so it is always
// guaranteed to outlive the lifetime of this function's scope.
let callback = unsafe { &*(callback as *const F) };
let callback = unsafe { &*std::ptr::from_ref::<F>(callback) };
match callback(ssl, data) {
Ok(Some(session)) => {
@ -450,7 +515,7 @@ where
F: Fn(&SslRef, &str) + 'static + Sync + Send,
{
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr(ssl as *mut _) };
let ssl = unsafe { SslRef::from_ptr(ssl.cast_mut()) };
let line = unsafe { CStr::from_ptr(line).to_string_lossy() };
let callback = ssl
@ -560,7 +625,7 @@ pub(super) unsafe extern "C" fn raw_info_callback<F>(
{
// Due to FFI signature requirements we have to pass a *const SSL into this function, but
// foreign-types requires a *mut SSL to get the Rust SslRef
let mut_ref = ssl as *mut ffi::SSL;
let mut_ref = ssl.cast_mut();
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr(mut_ref) };
@ -702,14 +767,10 @@ impl<'a> CryptoBufferBuilder<'a> {
let buffer_capacity = unsafe { ffi::CRYPTO_BUFFER_len(self.buffer) };
if self.cursor.position() != buffer_capacity as u64 {
// Make sure all bytes in buffer initialized as required by Boring SSL.
return Err(ErrorStack::get());
}
unsafe {
let mut result = ptr::null_mut();
ptr::swap(&mut self.buffer, &mut result);
std::mem::forget(self);
Ok(result)
return Err(ErrorStack::internal_error_str("invalid len"));
}
// Drop is no-op if the buffer is null
Ok(mem::replace(&mut self.buffer, ptr::null_mut()))
}
}

View File

@ -23,19 +23,9 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----
";
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(),
}?;
fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
let mut ctx = SslContextBuilder::new(method)?;
let mut opts = SslOptions::ALL
| SslOptions::NO_COMPRESSION
@ -77,7 +67,7 @@ impl SslConnector {
///
/// The default configuration is subject to change, and is currently derived from Python.
pub fn builder(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
let mut ctx = ctx(ContextType::WithMethod(method))?;
let mut ctx = ctx(method)?;
ctx.set_default_verify_paths()?;
ctx.set_cipher_list(
"DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
@ -87,17 +77,6 @@ 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)?;
ctx.set_cipher_list(
"DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
)?;
Ok(SslConnectorBuilder(ctx))
}
/// Initiates a client-side TLS session on a stream.
///
/// The domain is used for SNI and hostname verification.
@ -224,13 +203,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)?;
}
@ -292,21 +265,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.
///
@ -315,7 +273,7 @@ impl SslAcceptor {
///
/// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
pub fn mozilla_intermediate_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
let mut ctx = ctx(ContextType::WithMethod(method))?;
let mut ctx = ctx(method)?;
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)?;
@ -336,7 +294,7 @@ impl SslAcceptor {
/// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
// FIXME remove in next major version
pub fn mozilla_intermediate(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
let mut ctx = ctx(ContextType::WithMethod(method))?;
let mut ctx = ctx(method)?;
ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE);
ctx.set_options(SslOptions::NO_TLSV1_3);
let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
@ -362,7 +320,7 @@ impl SslAcceptor {
/// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
// FIXME remove in next major version
pub fn mozilla_modern(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
let mut ctx = ctx(ContextType::WithMethod(method))?;
let mut ctx = ctx(method)?;
ctx.set_options(
SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1,
);

View File

@ -0,0 +1,211 @@
#[cfg(feature = "rpk")]
use crate::cvt_p;
use crate::error::ErrorStack;
use crate::ex_data::Index;
use crate::pkey::{PKeyRef, Private};
use crate::ssl::callbacks;
use crate::ssl::PrivateKeyMethod;
use crate::{cvt_0i, cvt_n};
use crate::{ffi, free_data_box};
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl_macros::corresponds;
use std::any::TypeId;
use std::collections::HashMap;
use std::ffi::{c_int, c_void};
use std::mem;
use std::ptr;
use std::sync::{LazyLock, Mutex};
static SSL_CREDENTIAL_INDEXES: LazyLock<Mutex<HashMap<TypeId, c_int>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
foreign_type_and_impl_send_sync! {
type CType = ffi::SSL_CREDENTIAL;
fn drop = ffi::SSL_CREDENTIAL_free;
/// A credential.
pub struct SslCredential;
}
impl SslCredential {
/// Create a credential suitable for a handshake using a raw public key.
#[corresponds(SSL_CREDENTIAL_new_raw_public_key)]
#[cfg(feature = "rpk")]
pub fn new_raw_public_key() -> Result<SslCredentialBuilder, ErrorStack> {
unsafe {
Ok(SslCredentialBuilder(Self::from_ptr(cvt_p(
ffi::SSL_CREDENTIAL_new_raw_public_key(),
)?)))
}
}
/// Returns a new extra data index.
///
/// Each invocation of this function is guaranteed to return a distinct index. These can be used
/// to store data in the context that can be retrieved later by callbacks, for example.
#[corresponds(SSL_C_get_ex_new_index)]
pub fn new_ex_index<T>() -> Result<Index<Self, T>, ErrorStack>
where
T: 'static + Sync + Send,
{
unsafe {
ffi::init();
let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::<T>)))?;
Ok(Index::from_raw(idx))
}
}
// FIXME should return a result?
pub(crate) fn cached_ex_index<T>() -> Index<Self, T>
where
T: 'static + Sync + Send,
{
unsafe {
let idx = *SSL_CREDENTIAL_INDEXES
.lock()
.unwrap_or_else(|e| e.into_inner())
.entry(TypeId::of::<T>())
.or_insert_with(|| Self::new_ex_index::<T>().unwrap().as_raw());
Index::from_raw(idx)
}
}
}
impl SslCredentialRef {
/// Returns a reference to the extra data at the specified index.
#[corresponds(SSL_CREDENTIAL_get_ex_data)]
#[must_use]
pub fn ex_data<T>(&self, index: Index<SslCredential, T>) -> Option<&T> {
unsafe {
let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw());
if data.is_null() {
None
} else {
Some(&*(data as *const T))
}
}
}
// Unsafe because SSL contexts are not guaranteed to be unique, we call
// this only from SslCredentialBuilder.
#[corresponds(SSL_CREDENTIAL_get_ex_data)]
pub(crate) unsafe fn ex_data_mut<T>(
&mut self,
index: Index<SslCredential, T>,
) -> Option<&mut T> {
let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw());
if data.is_null() {
None
} else {
Some(&mut *(data as *mut T))
}
}
// Unsafe because SSL contexts are not guaranteed to be unique, we call
// this only from SslCredentialBuilder.
#[corresponds(SSL_CREDENTIAL_set_ex_data)]
pub(crate) unsafe fn replace_ex_data<T>(
&mut self,
index: Index<SslCredential, T>,
data: T,
) -> Option<T> {
if let Some(old) = self.ex_data_mut(index) {
return Some(mem::replace(old, data));
}
unsafe {
let data = Box::into_raw(Box::new(data)) as *mut c_void;
ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data);
}
None
}
}
/// A builder for [`SslCredential`]
pub struct SslCredentialBuilder(SslCredential);
impl SslCredentialBuilder {
/// Sets or overwrites the extra data at the specified index.
///
/// This can be used to provide data to callbacks registered with the context. Use the
/// `SslCredential::new_ex_index` method to create an `Index`.
///
/// Any previous value will be returned and replaced by the new one.
#[corresponds(SSL_CREDENTIAL_set_ex_data)]
pub fn replace_ex_data<T>(&mut self, index: Index<SslCredential, T>, data: T) -> Option<T> {
unsafe { self.0.replace_ex_data(index, data) }
}
// Sets the private key of the credential.
#[corresponds(SSL_CREDENTIAL_set1_private_key)]
pub fn set_private_key(&mut self, private_key: &PKeyRef<Private>) -> Result<(), ErrorStack> {
unsafe {
cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key(
self.0.as_ptr(),
private_key.as_ptr(),
))
.map(|_| ())
}
}
/// Configures a custom private key method on the credential.
///
/// See [`PrivateKeyMethod`] for more details.
#[corresponds(SSL_CREDENTIAL_set_private_key_method)]
pub fn set_private_key_method<M>(&mut self, method: M) -> Result<(), ErrorStack>
where
M: PrivateKeyMethod,
{
unsafe {
self.replace_ex_data(SslCredential::cached_ex_index::<M>(), method);
cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method(
self.0.as_ptr(),
&ffi::SSL_PRIVATE_KEY_METHOD {
sign: Some(callbacks::raw_sign::<M>),
decrypt: Some(callbacks::raw_decrypt::<M>),
complete: Some(callbacks::raw_complete::<M>),
},
))
.map(|_| ())
}
}
// Sets the SPKI of the raw public key credential.
//
// If `spki` is `None`, the SPKI is extracted from the credential's private key.
#[corresponds(SSL_CREDENTIAL_set1_spki)]
#[cfg(feature = "rpk")]
pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> {
unsafe {
let spki = spki
.map(|spki| {
cvt_p(ffi::CRYPTO_BUFFER_new(
spki.as_ptr(),
spki.len(),
ptr::null_mut(),
))
})
.transpose()?
.unwrap_or(ptr::null_mut());
let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ());
if !spki.is_null() {
ffi::CRYPTO_BUFFER_free(spki);
}
ret
}
}
#[must_use]
pub fn build(self) -> SslCredential {
self.0
}
}
unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int {
ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f)
}

View File

@ -79,6 +79,7 @@ impl ErrorCode {
}
#[corresponds(SSL_error_description)]
#[must_use]
pub fn description(self) -> Option<&'static str> {
unsafe {
let msg = ffi::SSL_error_description(self.0);
@ -249,8 +250,7 @@ fn fmt_mid_handshake_error(
f: &mut fmt::Formatter,
prefix: &str,
) -> fmt::Result {
#[cfg(feature = "rpk")]
if s.ssl().ssl_context().is_rpk() {
if !s.ssl().ssl_context().has_x509_support() {
write!(f, "{}", prefix)?;
return write!(f, " {}", s.error());
}

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@ fn server_only_cert_compression() {
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();
store.add_cert(&x509).unwrap();
let client = server.client();
@ -67,7 +67,7 @@ fn client_only_cert_compression() {
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();
store.add_cert(&x509).unwrap();
let mut client = server_builder.client();
client
@ -90,7 +90,7 @@ fn client_and_server_cert_compression() {
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();
store.add_cert(&x509).unwrap();
let mut client = server.client();
client

View File

@ -40,7 +40,7 @@ fn ech() {
let (_server, client) = bootstrap_ech(ECH_CONFIG, ECH_KEY, ECH_CONFIG_LIST);
let ssl_stream = client.connect();
assert!(ssl_stream.ssl().ech_accepted())
assert!(ssl_stream.ssl().ech_accepted());
}
#[test]
@ -57,7 +57,7 @@ fn ech_rejection() {
Some(b"ech.com".to_vec().as_ref())
);
assert!(failed_ssl_stream.ssl().get_ech_retry_configs().is_some());
assert!(!failed_ssl_stream.ssl().ech_accepted())
assert!(!failed_ssl_stream.ssl().ech_accepted());
}
#[test]
@ -69,5 +69,5 @@ fn ech_grease() {
client.ssl().set_enable_ech_grease(true);
let ssl_stream = client.connect();
assert!(!ssl_stream.ssl().ech_accepted())
assert!(!ssl_stream.ssl().ech_accepted());
}

View File

@ -1,4 +1,3 @@
#![allow(deprecated)] // SslCurve
use foreign_types::{ForeignType, ForeignTypeRef};
use std::io;
use std::io::prelude::*;
@ -13,27 +12,25 @@ use crate::hash::MessageDigest;
use crate::pkey::PKey;
use crate::srtp::SrtpProfileId;
use crate::ssl::test::server::Server;
use crate::ssl::SslVersion;
use crate::ssl::{self, SslCurve};
use crate::ssl::{
ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder,
self, ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder,
SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode,
};
use crate::ssl::{HandshakeError, SslVersion};
use crate::x509::store::X509StoreBuilder;
use crate::x509::verify::X509CheckFlags;
use crate::x509::{X509Name, X509};
#[cfg(not(feature = "fips"))]
use super::CompliancePolicy;
mod cert_compressor;
mod cert_verify;
mod custom_verify;
#[cfg(not(feature = "fips"))]
mod ech;
mod private_key_method;
mod server;
mod session;
mod session_resumption;
mod verify;
static ROOT_CERT: &[u8] = include_bytes!("../../../test/root-ca.pem");
@ -330,8 +327,8 @@ fn test_mutable_store() {
assert_eq!(1, ctx.cert_store().objects_len());
let mut new_store = X509StoreBuilder::new().unwrap();
new_store.add_cert(cert).unwrap();
new_store.add_cert(cert2).unwrap();
new_store.add_cert(&cert).unwrap();
new_store.add_cert(&cert2).unwrap();
let new_store = new_store.build();
assert_eq!(2, new_store.objects_len());
@ -420,6 +417,68 @@ fn test_select_cert_alpn_extension() {
);
}
#[test]
fn test_io_retry() {
#[derive(Debug)]
struct RetryStream {
inner: TcpStream,
first_read: bool,
first_write: bool,
first_flush: bool,
}
impl Read for RetryStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if mem::replace(&mut self.first_read, false) {
Err(io::Error::new(io::ErrorKind::WouldBlock, "first read"))
} else {
self.inner.read(buf)
}
}
}
impl Write for RetryStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if mem::replace(&mut self.first_write, false) {
Err(io::Error::new(io::ErrorKind::WouldBlock, "first write"))
} else {
self.inner.write(buf)
}
}
fn flush(&mut self) -> io::Result<()> {
if mem::replace(&mut self.first_flush, false) {
Err(io::Error::new(io::ErrorKind::WouldBlock, "first flush"))
} else {
self.inner.flush()
}
}
}
let server = Server::builder().build();
let stream = RetryStream {
inner: server.connect_tcp(),
first_read: true,
first_write: true,
first_flush: true,
};
let ctx = SslContext::builder(SslMethod::tls()).unwrap();
let mut s = match Ssl::new(&ctx.build()).unwrap().connect(stream) {
Ok(mut s) => return s.read_exact(&mut [0]).unwrap(),
Err(HandshakeError::WouldBlock(s)) => s,
Err(_) => panic!("should not fail on setup"),
};
loop {
match s.handshake() {
Ok(mut s) => return s.read_exact(&mut [0]).unwrap(),
Err(HandshakeError::WouldBlock(mid_s)) => s = mid_s,
Err(_) => panic!("should not fail on handshake"),
}
}
}
#[test]
#[should_panic(expected = "blammo")]
fn write_panic() {
@ -955,59 +1014,15 @@ fn sni_callback_swapped_ctx() {
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[cfg(feature = "kx-safe-default")]
#[test]
fn client_set_default_curves_list() {
let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls())
.unwrap()
.build();
let mut ssl = Ssl::new(&ssl_ctx).unwrap();
// Panics if Kyber768 missing in boringSSL.
ssl.client_set_default_curves_list();
}
#[cfg(feature = "kx-safe-default")]
#[test]
fn server_set_default_curves_list() {
let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls())
.unwrap()
.build();
let mut ssl = Ssl::new(&ssl_ctx).unwrap();
// Panics if Kyber768 missing in boringSSL.
ssl.server_set_default_curves_list();
}
#[test]
fn get_curve() {
let server = Server::builder().build();
let client = server.client_with_root_ca();
let client_stream = client.connect();
let curve = client_stream.ssl().curve().expect("curve");
assert!(curve.name().is_some());
}
#[test]
fn get_curve_name() {
assert_eq!(SslCurve::SECP224R1.name(), Some("P-224"));
assert_eq!(SslCurve::SECP256R1.name(), Some("P-256"));
assert_eq!(SslCurve::SECP384R1.name(), Some("P-384"));
assert_eq!(SslCurve::SECP521R1.name(), Some("P-521"));
assert_eq!(SslCurve::X25519.name(), Some("X25519"));
}
#[cfg(not(feature = "kx-safe-default"))]
#[test]
fn set_curves() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.set_curves(&[
SslCurve::SECP224R1,
SslCurve::SECP256R1,
SslCurve::SECP384R1,
SslCurve::X25519,
])
.expect("Failed to set curves");
let curve = client_stream.ssl().curve();
assert!(curve.is_some());
let curve_name = client_stream.ssl().curve_name();
assert!(curve_name.is_some());
}
#[test]
@ -1038,7 +1053,6 @@ fn test_get_ciphers() {
}
#[test]
#[cfg(not(feature = "fips"))]
fn test_set_compliance() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.set_compliance_policy(CompliancePolicy::FIPS_202205)
@ -1058,7 +1072,7 @@ fn test_set_compliance() {
assert_eq!(ciphers.len(), FIPS_CIPHERS.len());
for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) {
assert_eq!(cipher.0.name(), cipher.1)
assert_eq!(cipher.0.name(), cipher.1);
}
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
@ -1077,7 +1091,7 @@ fn test_set_compliance() {
assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len());
for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) {
assert_eq!(cipher.0.name(), cipher.1)
assert_eq!(cipher.0.name(), cipher.1);
}
ctx.set_compliance_policy(CompliancePolicy::NONE)
@ -1119,7 +1133,6 @@ fn test_info_callback() {
assert!(CALLED_BACK.load(Ordering::Relaxed));
}
#[cfg(not(feature = "fips-compat"))]
#[test]
fn test_ssl_set_compliance() {
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
@ -1141,7 +1154,7 @@ fn test_ssl_set_compliance() {
assert_eq!(ciphers.len(), FIPS_CIPHERS.len());
for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) {
assert_eq!(cipher.0.name(), cipher.1)
assert_eq!(cipher.0.name(), cipher.1);
}
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
@ -1161,7 +1174,7 @@ fn test_ssl_set_compliance() {
assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len());
for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) {
assert_eq!(cipher.0.name(), cipher.1)
assert_eq!(cipher.0.name(), cipher.1);
}
ssl.set_compliance_policy(CompliancePolicy::NONE)
@ -1169,7 +1182,6 @@ fn test_ssl_set_compliance() {
}
#[test]
#[allow(deprecated)]
fn ex_data_drop() {
use crate::ssl::SslContextBuilder;
use std::sync::atomic::AtomicU32;
@ -1208,6 +1220,6 @@ fn ex_data_drop() {
ctx2.set_ex_data(index, TrackDrop(d1.clone()));
ctx2.set_ex_data(index, TrackDrop(d2.clone()));
drop(ctx2);
assert_eq!(101, d1.load(Relaxed), "set_ex_data has a leak");
assert_eq!(102, d1.load(Relaxed));
assert_eq!(202, d2.load(Relaxed));
}

View File

@ -49,7 +49,7 @@ fn new_get_session_callback() {
.ctx()
.set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL);
server.ctx().set_new_session_callback(|_, session| {
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap()
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap();
});
unsafe {
server.ctx().set_get_session_callback(|_, id| {
@ -76,7 +76,7 @@ fn new_get_session_callback() {
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
client.ctx().set_new_session_callback(|_, session| {
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap()
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap();
});
let client = client.build();

View File

@ -0,0 +1,242 @@
use super::server::Server;
use crate::ssl::test::MessageDigest;
use crate::ssl::HmacCtxRef;
use crate::ssl::SslRef;
use crate::ssl::SslSession;
use crate::ssl::SslSessionCacheMode;
use crate::ssl::TicketKeyCallbackResult;
use crate::symm::Cipher;
use crate::symm::CipherCtxRef;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::OnceLock;
static SUCCESS_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
static SUCCESS_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
static NOOP_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
static NOOP_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
#[test]
fn resume_session() {
static SESSION_TICKET: OnceLock<SslSession> = OnceLock::new();
static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0);
let mut server = Server::builder();
server.expected_connections_count(2);
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
client.ctx().set_new_session_callback(|_, session| {
NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst);
// The server sends multiple session tickets but we only care to retrieve one.
let _ = SESSION_TICKET.set(session);
});
let ssl_stream = client.connect();
assert!(!ssl_stream.ssl().session_reused());
assert!(SESSION_TICKET.get().is_some());
assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2);
// Retrieve the session ticket
let session_ticket = SESSION_TICKET.get().unwrap();
// Attempt to resume the connection using the session ticket
let client_2 = server.client();
let mut ssl_builder = client_2.build().builder();
unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() };
let ssl_stream_2 = ssl_builder.connect();
assert!(ssl_stream_2.ssl().session_reused());
}
#[test]
fn custom_callback_success() {
static SESSION_TICKET: OnceLock<SslSession> = OnceLock::new();
static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0);
let mut server = Server::builder();
server.expected_connections_count(2);
unsafe {
server
.ctx()
.set_ticket_key_callback(test_success_tickey_key_callback);
};
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
client.ctx().set_new_session_callback(|_, session| {
NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst);
// The server sends multiple session tickets but we only care to retrieve one.
let _ = SESSION_TICKET.set(session);
});
let ssl_stream = client.connect();
assert!(!ssl_stream.ssl().session_reused());
assert!(SESSION_TICKET.get().is_some());
assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2);
assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0);
assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2);
// Retrieve the session ticket
let session_ticket = SESSION_TICKET.get().unwrap();
// Attempt to resume the connection using the session ticket
let client_2 = server.client();
let mut ssl_builder = client_2.build().builder();
unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() };
let ssl_stream_2 = ssl_builder.connect();
assert!(ssl_stream_2.ssl().session_reused());
assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4);
assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1);
}
#[test]
fn custom_callback_unrecognized_decryption_ticket() {
static SESSION_TICKET: OnceLock<SslSession> = OnceLock::new();
static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0);
let mut server = Server::builder();
server.expected_connections_count(2);
unsafe {
server
.ctx()
.set_ticket_key_callback(test_noop_tickey_key_callback);
};
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
client.ctx().set_new_session_callback(|_, session| {
NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst);
// The server sends multiple session tickets but we only care to retrieve one.
let _ = SESSION_TICKET.set(session);
});
let ssl_stream = client.connect();
assert!(!ssl_stream.ssl().session_reused());
assert!(SESSION_TICKET.get().is_some());
assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2);
assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0);
assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2);
// Retrieve the session ticket
let session_ticket = SESSION_TICKET.get().unwrap();
// Attempt to resume the connection using the session ticket
let client_2 = server.client();
let mut ssl_builder = client_2.build().builder();
unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() };
let ssl_stream_2 = ssl_builder.connect();
// Second connection was NOT resumed due to TicketKeyCallbackResult::Noop on decryption
assert!(!ssl_stream_2.ssl().session_reused());
assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4);
assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1);
}
// Successfully return a session ticket in encryption mode but return a
// TicketKeyCallbackResult::Noop in decryption mode.
fn test_noop_tickey_key_callback(
_ssl: &SslRef,
key_name: &mut [u8; 16],
iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
evp_ctx: &mut CipherCtxRef,
hmac_ctx: &mut HmacCtxRef,
encrypt: bool,
) -> TicketKeyCallbackResult {
// These should only be used for testing purposes.
const TEST_KEY_NAME: [u8; 16] = [5; 16];
const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize];
const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16];
const TEST_HMAC_KEY: [u8; 32] = [3; 32];
let digest = MessageDigest::sha256();
let cipher = Cipher::aes_128_cbc();
if encrypt {
NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
// Ensure key_name and iv are initialized and set test values.
assert_eq!(key_name, &[0; 16]);
assert_eq!(iv, &[0; 16]);
key_name.copy_from_slice(&TEST_KEY_NAME);
iv.copy_from_slice(&TEST_CBC_IV);
// Set the encryption context.
evp_ctx
.init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV)
.unwrap();
// Set the hmac context.
hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap();
TicketKeyCallbackResult::Success
} else {
NOOP_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
// Check key_name matches.
assert_eq!(key_name, &TEST_KEY_NAME);
TicketKeyCallbackResult::Noop
}
}
// Custom callback to encrypt and decrypt session tickets
fn test_success_tickey_key_callback(
_ssl: &SslRef,
key_name: &mut [u8; 16],
iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
evp_ctx: &mut CipherCtxRef,
hmac_ctx: &mut HmacCtxRef,
encrypt: bool,
) -> TicketKeyCallbackResult {
// These should only be used for testing purposes.
const TEST_KEY_NAME: [u8; 16] = [5; 16];
const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize];
const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16];
const TEST_HMAC_KEY: [u8; 32] = [3; 32];
let digest = MessageDigest::sha256();
let cipher = Cipher::aes_128_cbc();
if encrypt {
SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
// Ensure key_name and iv are initialized and set test values.
assert_eq!(key_name, &[0; 16]);
assert_eq!(iv, &[0; 16]);
key_name.copy_from_slice(&TEST_KEY_NAME);
iv.copy_from_slice(&TEST_CBC_IV);
// Set the encryption context.
evp_ctx
.init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV)
.unwrap();
// Set the hmac context.
hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap();
} else {
SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
// Check key_name matches.
assert_eq!(key_name, &TEST_KEY_NAME);
// Set the decryption context.
evp_ctx
.init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, iv)
.unwrap();
// Set the hmac context.
hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap();
}
TicketKeyCallbackResult::Success
}

View File

@ -32,7 +32,7 @@ fn trusted_with_set_cert() {
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();
store.add_cert(&x509).unwrap();
let mut client = server.client();
client.ctx().set_verify(SslVerifyMode::PEER);

View File

@ -48,7 +48,7 @@ impl<T: Stackable> Drop for Stack<T> {
fn drop(&mut self) {
unsafe {
while self.pop().is_some() {}
OPENSSL_sk_free(self.0 as *mut _);
OPENSSL_sk_free(self.0.cast());
}
}
}
@ -58,7 +58,7 @@ impl<T: Stackable> Stack<T> {
unsafe {
ffi::init();
let ptr = cvt_p(OPENSSL_sk_new_null())?;
Ok(Stack(ptr as *mut _))
Ok(Stack(ptr.cast()))
}
}
}
@ -132,7 +132,7 @@ impl<T: Stackable> Drop for IntoIter<T> {
fn drop(&mut self) {
unsafe {
for _ in &mut *self {}
OPENSSL_sk_free(self.stack as *mut _);
OPENSSL_sk_free(self.stack.cast());
}
}
}
@ -144,7 +144,7 @@ impl<T: Stackable> Iterator for IntoIter<T> {
unsafe {
self.idxs
.next()
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _))
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast()))
}
}
@ -158,7 +158,7 @@ impl<T: Stackable> DoubleEndedIterator for IntoIter<T> {
unsafe {
self.idxs
.next_back()
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _))
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast()))
}
}
}
@ -176,7 +176,7 @@ unsafe impl<T: Stackable> ForeignTypeRef for StackRef<T> {
impl<T: Stackable> StackRef<T> {
fn as_stack(&self) -> *mut OPENSSL_STACK {
self.as_ptr() as *mut _
self.as_ptr().cast()
}
/// Returns the number of items in the stack.
@ -234,7 +234,7 @@ impl<T: Stackable> StackRef<T> {
/// Pushes a value onto the top of the stack.
pub fn push(&mut self, data: T) -> Result<(), ErrorStack> {
unsafe {
cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr() as *mut _))?;
cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr().cast()))?;
mem::forget(data);
Ok(())
}
@ -247,13 +247,13 @@ impl<T: Stackable> StackRef<T> {
if ptr.is_null() {
None
} else {
Some(T::from_ptr(ptr as *mut _))
Some(T::from_ptr(ptr.cast()))
}
}
}
unsafe fn _get(&self, idx: usize) -> *mut T::CType {
OPENSSL_sk_value(self.as_stack(), idx) as *mut _
OPENSSL_sk_value(self.as_stack(), idx).cast()
}
}
@ -333,7 +333,7 @@ impl<'a, T: Stackable> Iterator for Iter<'a, T> {
unsafe {
self.idxs
.next()
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
}
}
@ -347,7 +347,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for Iter<'a, T> {
unsafe {
self.idxs
.next_back()
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
}
}
}
@ -367,7 +367,7 @@ impl<'a, T: Stackable> Iterator for IterMut<'a, T> {
unsafe {
self.idxs
.next()
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
}
}
@ -381,7 +381,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for IterMut<'a, T> {
unsafe {
self.idxs
.next_back()
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
}
}
}

View File

@ -1,6 +1,6 @@
use crate::ffi;
use foreign_types::ForeignTypeRef;
use libc::{c_char, c_void};
use libc::c_char;
use std::convert::AsRef;
use std::ffi::CStr;
use std::fmt;
@ -83,5 +83,5 @@ impl fmt::Debug for OpensslStringRef {
}
unsafe fn free(buf: *mut c_char) {
crate::ffi::OPENSSL_free(buf as *mut c_void);
crate::ffi::OPENSSL_free(buf.cast());
}

View File

@ -53,14 +53,15 @@
//! ```
use crate::ffi;
use libc::{c_int, c_uint};
use foreign_types::ForeignTypeRef;
use openssl_macros::corresponds;
use std::cmp;
use std::ffi::c_int;
use std::ptr;
use crate::error::ErrorStack;
use crate::nid::Nid;
use crate::{cvt, cvt_p};
use crate::{cvt, cvt_p, try_int};
#[derive(Copy, Clone)]
pub enum Mode {
@ -68,12 +69,75 @@ pub enum Mode {
Decrypt,
}
foreign_type_and_impl_send_sync! {
type CType = ffi::EVP_CIPHER_CTX;
fn drop = ffi::EVP_CIPHER_CTX_free;
pub struct CipherCtx;
}
impl CipherCtxRef {
/// Configures CipherCtx for a fresh encryption operation using `cipher`.
///
#[corresponds(EVP_EncryptInit_ex)]
pub fn init_encrypt(
&mut self,
cipher: &Cipher,
key: &[u8],
iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize],
) -> Result<(), ErrorStack> {
ffi::init();
if key.len() != cipher.key_len() {
return Err(ErrorStack::internal_error_str("invalid key size"));
}
unsafe {
cvt(ffi::EVP_EncryptInit_ex(
self.as_ptr(),
cipher.as_ptr(),
// ENGINE api is deprecated
ptr::null_mut(),
key.as_ptr(),
iv.as_ptr(),
))
}
}
/// Configures CipherCtx for a fresh decryption operation using `cipher`.
///
#[corresponds(EVP_DecryptInit_ex)]
pub fn init_decrypt(
&mut self,
cipher: &Cipher,
key: &[u8],
iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize],
) -> Result<(), ErrorStack> {
ffi::init();
if key.len() != cipher.key_len() {
return Err(ErrorStack::internal_error_str("invalid key size"));
}
unsafe {
cvt(ffi::EVP_DecryptInit_ex(
self.as_ptr(),
cipher.as_ptr(),
// ENGINE api is deprecated
ptr::null_mut(),
key.as_ptr(),
iv.as_ptr(),
))
}
}
}
/// Represents a particular cipher algorithm.
///
/// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms.
///
/// [`EVP_EncryptInit`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_EncryptInit.html
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Cipher(*const ffi::EVP_CIPHER);
impl Cipher {
@ -81,7 +145,7 @@ impl Cipher {
#[corresponds(EVP_get_cipherbynid)]
#[must_use]
pub fn from_nid(nid: Nid) -> Option<Cipher> {
let ptr = unsafe { ffi::EVP_get_cipherbyname(ffi::OBJ_nid2sn(nid.as_raw())) };
let ptr = unsafe { ffi::EVP_get_cipherbynid(nid.as_raw()) };
if ptr.is_null() {
None
} else {
@ -237,6 +301,14 @@ impl Cipher {
pub fn block_size(&self) -> usize {
unsafe { EVP_CIPHER_block_size(self.0) as usize }
}
/// Returns the cipher's NID.
#[corresponds(EVP_CIPHER_nid)]
pub fn nid(&self) -> Nid {
ffi::init();
let nid = unsafe { ffi::EVP_CIPHER_nid(self.as_ptr()) };
Nid::from_raw(nid)
}
}
unsafe impl Sync for Cipher {}
@ -347,25 +419,22 @@ impl Crypter {
mode,
))?;
assert!(key.len() <= c_int::MAX as usize);
cvt(ffi::EVP_CIPHER_CTX_set_key_length(
crypter.ctx,
key.len() as c_uint,
try_int(key.len())?,
))?;
let key = key.as_ptr() as *mut _;
let iv = match (iv, t.iv_len()) {
(Some(iv), Some(len)) => {
if iv.len() != len {
assert!(iv.len() <= c_int::MAX as usize);
cvt(ffi::EVP_CIPHER_CTX_ctrl(
crypter.ctx,
ffi::EVP_CTRL_GCM_SET_IVLEN,
iv.len() as c_int,
try_int(iv.len())?,
ptr::null_mut(),
))?;
}
iv.as_ptr() as *mut _
iv.as_ptr().cast_mut()
}
(Some(_) | None, None) => ptr::null_mut(),
(None, Some(_)) => panic!("an IV is required for this cipher"),
@ -374,7 +443,7 @@ impl Crypter {
crypter.ctx,
ptr::null(),
ptr::null_mut(),
key,
key.as_ptr().cast_mut(),
iv,
mode,
))?;
@ -398,15 +467,13 @@ impl Crypter {
/// When decrypting cipher text using an AEAD cipher, this must be called before `finalize`.
pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> {
unsafe {
assert!(tag.len() <= c_int::MAX as usize);
// NB: this constant is actually more general than just GCM.
cvt(ffi::EVP_CIPHER_CTX_ctrl(
self.ctx,
ffi::EVP_CTRL_GCM_SET_TAG,
tag.len() as c_int,
tag.as_ptr() as *mut _,
try_int(tag.len())?,
tag.as_ptr().cast_mut().cast(),
))
.map(|_| ())
}
}
@ -416,15 +483,13 @@ impl Crypter {
/// to use a value different than the default 12 bytes.
pub fn set_tag_len(&mut self, tag_len: usize) -> Result<(), ErrorStack> {
unsafe {
assert!(tag_len <= c_int::MAX as usize);
// NB: this constant is actually more general than just GCM.
cvt(ffi::EVP_CIPHER_CTX_ctrl(
self.ctx,
ffi::EVP_CTRL_GCM_SET_TAG,
tag_len as c_int,
try_int(tag_len)?,
ptr::null_mut(),
))
.map(|_| ())
}
}
@ -434,16 +499,14 @@ impl Crypter {
/// CCM mode.
pub fn set_data_len(&mut self, data_len: usize) -> Result<(), ErrorStack> {
unsafe {
assert!(data_len <= c_int::MAX as usize);
let mut len = 0;
cvt(ffi::EVP_CipherUpdate(
self.ctx,
ptr::null_mut(),
&mut len,
ptr::null_mut(),
data_len as c_int,
try_int(data_len)?,
))
.map(|_| ())
}
}
@ -454,16 +517,14 @@ impl Crypter {
/// `update`.
pub fn aad_update(&mut self, input: &[u8]) -> Result<(), ErrorStack> {
unsafe {
assert!(input.len() <= c_int::MAX as usize);
let mut len = 0;
cvt(ffi::EVP_CipherUpdate(
self.ctx,
ptr::null_mut(),
&mut len,
input.as_ptr(),
input.len() as c_int,
try_int(input.len())?,
))
.map(|_| ())
}
}
@ -479,8 +540,6 @@ impl Crypter {
///
/// Panics for block ciphers if `output.len() < input.len() + block_size`,
/// where `block_size` is the block size of the cipher (see `Cipher::block_size`).
///
/// Panics if `output.len() > c_int::MAX`.
pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
unsafe {
let block_size = if self.block_size > 1 {
@ -489,16 +548,14 @@ impl Crypter {
0
};
assert!(output.len() >= input.len() + block_size);
assert!(output.len() <= c_int::MAX as usize);
let mut outl = output.len() as c_int;
let inl = input.len() as c_int;
let mut outl = try_int(output.len())?;
cvt(ffi::EVP_CipherUpdate(
self.ctx,
output.as_mut_ptr(),
&mut outl,
input.as_ptr(),
inl,
try_int(input.len())?,
))?;
Ok(outl as usize)
@ -543,14 +600,12 @@ impl Crypter {
/// bytes, for example.
pub fn get_tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> {
unsafe {
assert!(tag.len() <= c_int::MAX as usize);
cvt(ffi::EVP_CIPHER_CTX_ctrl(
self.ctx,
ffi::EVP_CTRL_GCM_GET_TAG,
tag.len() as c_int,
tag.as_mut_ptr() as *mut _,
try_int(tag.len())?,
tag.as_mut_ptr().cast(),
))
.map(|_| ())
}
}
}
@ -985,4 +1040,129 @@ mod tests {
.unwrap();
assert_eq!(pt, hex::encode(out));
}
#[test]
fn test_nid_roundtrip() {
for cipher in [
Cipher::aes_128_gcm(),
Cipher::aes_192_gcm(),
Cipher::aes_256_gcm(),
Cipher::aes_128_ecb(),
Cipher::aes_128_cbc(),
Cipher::aes_128_ctr(),
Cipher::aes_128_ofb(),
Cipher::aes_192_ecb(),
Cipher::aes_192_cbc(),
Cipher::aes_192_ctr(),
Cipher::aes_192_ofb(),
Cipher::aes_256_ecb(),
Cipher::aes_256_cbc(),
Cipher::aes_256_ctr(),
Cipher::aes_256_ofb(),
Cipher::des_ecb(),
Cipher::des_ede3_cbc(),
Cipher::des_cbc(),
Cipher::rc4(),
] {
let name = cipher.nid().short_name().unwrap_or("unknown");
assert_eq!(Cipher::from_nid(cipher.nid()), Some(cipher), "{}", name);
}
assert_eq!(Cipher::from_nid(Cipher::des_ede3().nid()), None);
}
// Make sure the NIDs don't actually change upstream.
#[test]
fn test_nid_regression() {
struct TestCase {
cipher: Cipher,
nid: c_int,
}
for t in [
TestCase {
cipher: Cipher::aes_128_ecb(),
nid: 418,
},
TestCase {
cipher: Cipher::aes_128_cbc(),
nid: 419,
},
TestCase {
cipher: Cipher::aes_128_ctr(),
nid: 904,
},
TestCase {
cipher: Cipher::aes_128_gcm(),
nid: 895,
},
TestCase {
cipher: Cipher::aes_128_ofb(),
nid: 420,
},
TestCase {
cipher: Cipher::aes_192_ecb(),
nid: 422,
},
TestCase {
cipher: Cipher::aes_192_cbc(),
nid: 423,
},
TestCase {
cipher: Cipher::aes_192_ctr(),
nid: 905,
},
TestCase {
cipher: Cipher::aes_192_gcm(),
nid: 898,
},
TestCase {
cipher: Cipher::aes_192_ofb(),
nid: 424,
},
TestCase {
cipher: Cipher::aes_256_ecb(),
nid: 426,
},
TestCase {
cipher: Cipher::aes_256_cbc(),
nid: 427,
},
TestCase {
cipher: Cipher::aes_256_ctr(),
nid: 906,
},
TestCase {
cipher: Cipher::aes_256_gcm(),
nid: 901,
},
TestCase {
cipher: Cipher::aes_256_ofb(),
nid: 428,
},
TestCase {
cipher: Cipher::des_ecb(),
nid: 29,
},
TestCase {
cipher: Cipher::des_ede3_cbc(),
nid: 44,
},
TestCase {
cipher: Cipher::des_cbc(),
nid: 31,
},
TestCase {
cipher: Cipher::rc4(),
nid: 5,
},
TestCase {
cipher: Cipher::des_ede3(),
nid: 33,
},
] {
let name = t.cipher.nid().short_name().unwrap_or("unknown");
assert_eq!(t.cipher.nid().as_raw(), t.nid, "{}", name);
}
}
}

View File

@ -46,17 +46,17 @@ pub unsafe extern "C" fn invoke_passwd_cb<F>(
where
F: FnOnce(&mut [u8]) -> Result<usize, ErrorStack>,
{
let callback = &mut *(cb_state as *mut CallbackState<F>);
let callback = &mut *cb_state.cast::<CallbackState<F>>();
let result = panic::catch_unwind(AssertUnwindSafe(|| {
let pass_slice = slice::from_raw_parts_mut(buf as *mut u8, size as usize);
let pass_slice = slice::from_raw_parts_mut(buf.cast::<u8>(), size as usize);
callback.cb.take().unwrap()(pass_slice)
}));
match result {
Ok(Ok(len)) => len as c_int,
Ok(Err(_)) => {
// FIXME restore error stack
Ok(Err(err)) => {
err.put();
0
}
Err(err) => {
@ -80,14 +80,14 @@ impl<FT: ForeignType> ForeignTypeExt for FT {}
pub trait ForeignTypeRefExt: ForeignTypeRef {
unsafe fn from_const_ptr<'a>(ptr: *const Self::CType) -> &'a Self {
Self::from_ptr(ptr as *mut Self::CType)
Self::from_ptr(ptr.cast_mut())
}
unsafe fn from_const_ptr_opt<'a>(ptr: *const Self::CType) -> Option<&'a Self> {
if ptr.is_null() {
None
} else {
Some(Self::from_const_ptr(ptr as *mut Self::CType))
Some(Self::from_const_ptr(ptr.cast_mut()))
}
}
}

View File

@ -20,7 +20,7 @@ use std::net::IpAddr;
use std::path::Path;
use std::ptr;
use std::str;
use std::sync::{LazyLock, Once};
use std::sync::LazyLock;
use crate::asn1::{
Asn1BitStringRef, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef,
@ -36,6 +36,7 @@ use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public};
use crate::ssl::SslRef;
use crate::stack::{Stack, StackRef, Stackable};
use crate::string::OpensslString;
use crate::try_int;
use crate::util::ForeignTypeRefExt;
use crate::x509::crl::X509CRL;
use crate::x509::verify::{X509VerifyParam, X509VerifyParamRef};
@ -107,12 +108,9 @@ impl X509StoreContextRef {
#[must_use]
pub fn ex_data<T>(&self, index: Index<X509StoreContext, T>) -> Option<&T> {
unsafe {
let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw());
if data.is_null() {
None
} else {
Some(&*(data as *const T))
}
ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw())
.cast::<T>()
.as_ref()
}
}
@ -120,12 +118,9 @@ impl X509StoreContextRef {
#[corresponds(X509_STORE_CTX_get_ex_data)]
pub fn ex_data_mut<T>(&mut self, index: Index<X509StoreContext, T>) -> Option<&mut T> {
unsafe {
let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw());
if data.is_null() {
None
} else {
Some(&mut *(data as *mut T))
}
ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw())
.cast::<T>()
.as_mut()
}
}
@ -148,7 +143,7 @@ impl X509StoreContextRef {
ffi::X509_STORE_CTX_set_ex_data(
self.as_ptr(),
index.as_raw(),
Box::into_raw(data) as *mut c_void,
Box::into_raw(data).cast(),
);
}
}
@ -397,13 +392,13 @@ impl X509Builder {
/// Sets the notAfter constraint on the certificate.
#[corresponds(X509_set1_notAfter)]
pub fn set_not_after(&mut self, not_after: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())).map(|_| ()) }
unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())) }
}
/// Sets the notBefore constraint on the certificate.
#[corresponds(X509_set1_notBefore)]
pub fn set_not_before(&mut self, not_before: &Asn1TimeRef) -> Result<(), ErrorStack> {
unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())).map(|_| ()) }
unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())) }
}
/// Sets the version of the certificate.
@ -412,7 +407,7 @@ impl X509Builder {
/// the X.509 standard should pass `2` to this method.
#[corresponds(X509_set_version)]
pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version.into())).map(|_| ()) }
unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version.into())) }
}
/// Sets the serial number of the certificate.
@ -423,7 +418,6 @@ impl X509Builder {
self.0.as_ptr(),
serial_number.as_ptr(),
))
.map(|_| ())
}
}
@ -435,7 +429,6 @@ impl X509Builder {
self.0.as_ptr(),
issuer_name.as_ptr(),
))
.map(|_| ())
}
}
@ -464,7 +457,6 @@ impl X509Builder {
self.0.as_ptr(),
subject_name.as_ptr(),
))
.map(|_| ())
}
}
@ -474,7 +466,7 @@ impl X509Builder {
where
T: HasPublic,
{
unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())) }
}
/// Returns a context object which is needed to create certain X509 extension values.
@ -513,16 +505,9 @@ impl X509Builder {
}
}
/// Adds an X509 extension value to the certificate.
///
/// This works just as `append_extension` except it takes ownership of the `X509Extension`.
pub fn append_extension(&mut self, extension: X509Extension) -> Result<(), ErrorStack> {
self.append_extension2(&extension)
}
/// Adds an X509 extension value to the certificate.
#[corresponds(X509_add_ext)]
pub fn append_extension2(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> {
pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_add_ext(self.0.as_ptr(), extension.as_ptr(), -1))?;
Ok(())
@ -535,7 +520,7 @@ impl X509Builder {
where
T: HasPrivate,
{
unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())) }
}
/// Consumes the builder, returning the certificate.
@ -585,7 +570,7 @@ impl X509Ref {
if stack.is_null() {
None
} else {
Some(Stack::from_ptr(stack as *mut _))
Some(Stack::from_ptr(stack.cast()))
}
}
}
@ -614,7 +599,7 @@ impl X509Ref {
if stack.is_null() {
None
} else {
Some(Stack::from_ptr(stack as *mut _))
Some(Stack::from_ptr(stack.cast()))
}
}
}
@ -655,14 +640,14 @@ impl X509Ref {
buf: [0; ffi::EVP_MAX_MD_SIZE as usize],
len: ffi::EVP_MAX_MD_SIZE as usize,
};
let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap();
let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?;
cvt(ffi::X509_digest(
self.as_ptr(),
hash_type.as_ptr(),
digest.buf.as_mut_ptr() as *mut _,
digest.buf.as_mut_ptr(),
&mut len,
))?;
digest.len = len as usize;
digest.len = try_int(len)?;
Ok(digest)
}
@ -703,7 +688,7 @@ impl X509Ref {
let mut signature = ptr::null();
X509_get0_signature(&mut signature, ptr::null_mut(), self.as_ptr());
assert!(!signature.is_null());
Asn1BitStringRef::from_ptr(signature as *mut _)
Asn1BitStringRef::from_ptr(signature.cast_mut())
}
}
@ -715,7 +700,7 @@ impl X509Ref {
let mut algor = ptr::null();
X509_get0_signature(ptr::null_mut(), &mut algor, self.as_ptr());
assert!(!algor.is_null());
X509AlgorithmRef::from_ptr(algor as *mut _)
X509AlgorithmRef::from_ptr(algor.cast_mut())
}
}
@ -779,7 +764,7 @@ impl X509Ref {
unsafe {
cvt_n(ffi::X509_check_host(
self.as_ptr(),
host.as_ptr() as _,
host.as_ptr().cast(),
host.len(),
0,
std::ptr::null_mut(),
@ -931,7 +916,7 @@ pub struct X509v3Context<'a>(ffi::X509V3_CTX, PhantomData<(&'a X509Ref, &'a Conf
impl X509v3Context<'_> {
#[must_use]
pub fn as_ptr(&self) -> *mut ffi::X509V3_CTX {
&self.0 as *const _ as *mut _
std::ptr::addr_of!(self.0).cast_mut()
}
}
@ -986,8 +971,8 @@ impl X509Extension {
&mut ctx
}
};
let name = name.as_ptr() as *mut _;
let value = value.as_ptr() as *mut _;
let name = name.as_ptr().cast_mut();
let value = value.as_ptr().cast_mut();
cvt_p(ffi::X509V3_EXT_nconf(conf, context_ptr, name, value))
.map(|p| X509Extension::from_ptr(p))
@ -1032,7 +1017,7 @@ impl X509Extension {
}
};
let name = name.as_raw();
let value = value.as_ptr() as *mut _;
let value = value.as_ptr().cast_mut();
cvt_p(ffi::X509V3_EXT_nconf_nid(conf, context_ptr, name, value))
.map(|p| X509Extension::from_ptr(p))
@ -1116,17 +1101,15 @@ impl X509NameBuilder {
pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> {
unsafe {
let field = CString::new(field).map_err(ErrorStack::internal_error)?;
assert!(value.len() <= ValueLen::MAX as usize);
cvt(ffi::X509_NAME_add_entry_by_txt(
self.0.as_ptr(),
field.as_ptr() as *mut _,
field.as_ptr().cast_mut(),
ffi::MBSTRING_UTF8,
value.as_ptr(),
value.len() as ValueLen,
try_int(value.len())?,
-1,
0,
))
.map(|_| ())
}
}
@ -1140,17 +1123,15 @@ impl X509NameBuilder {
) -> Result<(), ErrorStack> {
unsafe {
let field = CString::new(field).map_err(ErrorStack::internal_error)?;
assert!(value.len() <= ValueLen::MAX as usize);
cvt(ffi::X509_NAME_add_entry_by_txt(
self.0.as_ptr(),
field.as_ptr() as *mut _,
field.as_ptr().cast_mut(),
ty.as_raw(),
value.as_ptr(),
value.len() as ValueLen,
try_int(value.len())?,
-1,
0,
))
.map(|_| ())
}
}
@ -1158,17 +1139,15 @@ impl X509NameBuilder {
#[corresponds(X509_NAME_add_entry_by_NID)]
pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> {
unsafe {
assert!(value.len() <= ValueLen::MAX as usize);
cvt(ffi::X509_NAME_add_entry_by_NID(
self.0.as_ptr(),
field.as_raw(),
ffi::MBSTRING_UTF8,
value.as_ptr() as *mut _,
value.len() as ValueLen,
value.as_ptr().cast_mut(),
try_int(value.len())?,
-1,
0,
))
.map(|_| ())
}
}
@ -1181,17 +1160,15 @@ impl X509NameBuilder {
ty: Asn1Type,
) -> Result<(), ErrorStack> {
unsafe {
assert!(value.len() <= ValueLen::MAX as usize);
cvt(ffi::X509_NAME_add_entry_by_NID(
self.0.as_ptr(),
field.as_raw(),
ty.as_raw(),
value.as_ptr() as *mut _,
value.len() as ValueLen,
value.as_ptr().cast_mut(),
try_int(value.len())?,
-1,
0,
))
.map(|_| ())
}
}
@ -1205,11 +1182,6 @@ impl X509NameBuilder {
}
}
#[cfg(not(feature = "fips-compat"))]
type ValueLen = isize;
#[cfg(feature = "fips-compat")]
type ValueLen = i32;
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_NAME;
fn drop = ffi::X509_NAME_free;
@ -1388,7 +1360,7 @@ impl X509ReqBuilder {
/// Set the numerical value of the version field.
#[corresponds(X509_REQ_set_version)]
pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())).map(|_| ()) }
unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())) }
}
/// Set the issuer name.
@ -1399,7 +1371,6 @@ impl X509ReqBuilder {
self.0.as_ptr(),
subject_name.as_ptr(),
))
.map(|_| ())
}
}
@ -1409,7 +1380,7 @@ impl X509ReqBuilder {
where
T: HasPublic,
{
unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())) }
}
/// Return an `X509v3Context`. This context object can be used to construct
@ -1447,7 +1418,6 @@ impl X509ReqBuilder {
self.0.as_ptr(),
extensions.as_ptr(),
))
.map(|_| ())
}
}
@ -1463,7 +1433,6 @@ impl X509ReqBuilder {
key.as_ptr(),
hash.as_ptr(),
))
.map(|_| ())
}
}
@ -1858,7 +1827,7 @@ impl X509AlgorithmRef {
let mut oid = ptr::null();
X509_ALGOR_get0(&mut oid, ptr::null_mut(), ptr::null_mut(), self.as_ptr());
assert!(!oid.is_null());
Asn1ObjectRef::from_ptr(oid as *mut _)
Asn1ObjectRef::from_ptr(oid.cast_mut())
}
}
}
@ -1901,16 +1870,9 @@ use crate::ffi::X509_OBJECT_get0_X509;
#[allow(bad_style)]
unsafe fn X509_OBJECT_free(x: *mut ffi::X509_OBJECT) {
ffi::X509_OBJECT_free_contents(x);
ffi::OPENSSL_free(x as *mut libc::c_void);
ffi::OPENSSL_free(x.cast());
}
unsafe fn get_new_x509_store_ctx_idx(f: ffi::CRYPTO_EX_free) -> c_int {
// hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest
static ONCE: Once = Once::new();
ONCE.call_once(|| {
ffi::X509_STORE_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None);
});
ffi::X509_STORE_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f)
}

View File

@ -36,7 +36,7 @@
//!
//! let certificate: X509 = builder.build();
//! let mut builder = X509StoreBuilder::new().unwrap();
//! let _ = builder.add_cert(certificate);
//! let _ = builder.add_cert(&certificate);
//! let store: X509Store = builder.build();
//! ```
@ -45,11 +45,11 @@ use crate::ffi;
use crate::stack::StackRef;
use crate::x509::crl::X509CRL;
use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef};
use crate::x509::{X509Object, X509};
use crate::x509::{X509Object, X509Ref};
use crate::{cvt, cvt_p};
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl_macros::corresponds;
use std::mem;
use std::mem::ManuallyDrop;
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_STORE;
@ -74,18 +74,16 @@ impl X509StoreBuilder {
/// Constructs the `X509Store`.
#[must_use]
pub fn build(self) -> X509Store {
let store = X509Store(self.0);
mem::forget(self);
store
X509Store(ManuallyDrop::new(self).0)
}
}
impl X509StoreBuilderRef {
/// Adds a certificate to the certificate store.
// FIXME should take an &X509Ref
#[corresponds(X509_STORE_add_cert)]
pub fn add_cert(&mut self, cert: X509) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) }
pub fn add_cert(&mut self, cert: impl AsRef<X509Ref>) -> Result<(), ErrorStack> {
let cert = cert.as_ref();
unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())) }
}
/// Adds a CRL to the certificate store.
@ -113,7 +111,7 @@ impl X509StoreBuilderRef {
/// build time otherwise.
#[corresponds(X509_STORE_set_default_paths)]
pub fn set_default_paths(&mut self) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())) }
}
/// Sets certificate chain validation related flags.
@ -133,7 +131,7 @@ impl X509StoreBuilderRef {
/// Sets certificate chain validation related parameters.
#[corresponds(X509_STORE_set1_param)]
pub fn set_param(&mut self, param: &X509VerifyParamRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())) }
}
/// For testing only
@ -153,6 +151,23 @@ foreign_type_and_impl_send_sync! {
pub struct X509Store;
}
impl ToOwned for X509StoreRef {
type Owned = X509Store;
fn to_owned(&self) -> X509Store {
unsafe {
ffi::X509_STORE_up_ref(self.as_ptr());
X509Store::from_ptr(self.as_ptr())
}
}
}
impl Clone for X509Store {
fn clone(&self) -> X509Store {
(**self).to_owned()
}
}
impl X509StoreRef {
/// **Warning: this method is unsound**
///
@ -179,12 +194,16 @@ impl X509StoreRef {
}
#[test]
#[allow(dead_code)]
// X509Store must not implement Clone because `SslContextBuilder::cert_store_mut` lets
// you get a mutable reference to a store that could have been cloned before being
// passed to `SslContextBuilder::set_cert_store`.
fn no_clone_for_x509store() {
trait MustNotImplementClone {}
impl<T: Clone> MustNotImplementClone for T {}
impl MustNotImplementClone for X509Store {}
#[allow(clippy::redundant_clone)]
#[should_panic = "Shared X509Store can't be mutated"]
fn set_cert_store_pevents_mutability() {
use crate::ssl::*;
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
let store = X509StoreBuilder::new().unwrap().build();
ctx.set_cert_store(store.clone());
// This is bad.
let _aliased_store = ctx.cert_store_mut();
}

View File

@ -84,7 +84,7 @@ fn test_subject_read_cn() {
let cert = X509::from_pem(cert).unwrap();
let subject = cert.subject_name();
let cn = subject.entries_by_nid(Nid::COMMONNAME).next().unwrap();
assert_eq!(cn.data().as_slice(), b"foobar.com")
assert_eq!(cn.data().as_slice(), b"foobar.com");
}
#[test]
@ -261,34 +261,36 @@ fn x509_builder() {
.unwrap();
let basic_constraints = BasicConstraints::new().critical().ca().build().unwrap();
builder.append_extension(basic_constraints).unwrap();
builder
.append_extension(basic_constraints.as_ref())
.unwrap();
let key_usage = KeyUsage::new()
.digital_signature()
.key_encipherment()
.build()
.unwrap();
builder.append_extension(key_usage).unwrap();
builder.append_extension(&key_usage).unwrap();
let ext_key_usage = ExtendedKeyUsage::new()
.client_auth()
.server_auth()
.other("2.999.1")
.build()
.unwrap();
builder.append_extension(ext_key_usage).unwrap();
builder.append_extension(&ext_key_usage).unwrap();
let subject_key_identifier = SubjectKeyIdentifier::new()
.build(&builder.x509v3_context(None, None))
.unwrap();
builder.append_extension(subject_key_identifier).unwrap();
builder.append_extension(&subject_key_identifier).unwrap();
let authority_key_identifier = AuthorityKeyIdentifier::new()
.keyid(true)
.build(&builder.x509v3_context(None, None))
.unwrap();
builder.append_extension(authority_key_identifier).unwrap();
builder.append_extension(&authority_key_identifier).unwrap();
let subject_alternative_name = SubjectAlternativeName::new()
.dns("example.com")
.build(&builder.x509v3_context(None, None))
.unwrap();
builder.append_extension(subject_alternative_name).unwrap();
builder.append_extension(&subject_alternative_name).unwrap();
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
@ -503,7 +505,7 @@ fn test_verify_cert() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
let store = store_bldr.build();
let empty_store = X509StoreBuilder::new().unwrap().build();
@ -536,7 +538,7 @@ fn test_verify_fails() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
@ -556,7 +558,7 @@ fn test_verify_revoked() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr.add_crl(crl).unwrap();
store_bldr
.param_mut()
@ -592,7 +594,7 @@ fn test_untrusted_valid_crl() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
@ -612,11 +614,13 @@ fn test_untrusted_valid_crl() {
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
assert_eq!(context.verify_result(), Err(X509VerifyError::CERT_REVOKED));
context
.init(&store, &cert, &chain, |c| {
assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap());
assert_eq!(c.verify_result(), Err(X509VerifyError::CERT_REVOKED));
Ok(())
})
.unwrap();
}
#[test]
@ -628,7 +632,7 @@ fn test_untrusted_invalid_crl() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
@ -639,27 +643,28 @@ fn test_untrusted_invalid_crl() {
let crl = include_bytes!("../../../test/invalid_crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
assert_eq!(
context.verify_result(),
Err(X509VerifyError::UNABLE_TO_GET_CRL)
);
context
.init(&store, &cert, &chain, |c| {
assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap());
assert_eq!(c.verify_result(), Err(X509VerifyError::UNABLE_TO_GET_CRL));
Ok(())
})
.unwrap();
// this CRL has an invalid signature
let crl = include_bytes!("../../../test/bad_sig.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(!context
.init(&store, &cert, &chain, |c| c
.verify_cert_with_crls(stack_of(crl)))
.unwrap());
assert_eq!(
context.verify_result(),
Err(X509VerifyError::CRL_SIGNATURE_FAILURE)
);
context
.init(&store, &cert, &chain, |c| {
assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap());
assert_eq!(
c.verify_result(),
Err(X509VerifyError::CRL_SIGNATURE_FAILURE)
);
Ok(())
})
.unwrap();
}
#[test]
@ -741,7 +746,7 @@ fn test_custom_time_valid() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
@ -760,7 +765,7 @@ fn test_custom_time_expired() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr.param_mut().set_time(1786838400); // 2026-08-16, after the root and leaf expiration
let store = store_bldr.build();
@ -779,7 +784,7 @@ fn test_custom_time_too_soon() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr.param_mut().set_time(1262304000); // 2010-01-01, before the root and leaf issuance
let store = store_bldr.build();
@ -800,7 +805,7 @@ fn test_custom_time_crl() {
let chain = Stack::new().unwrap();
let mut store_bldr = X509StoreBuilder::new().unwrap();
store_bldr.add_cert(ca).unwrap();
store_bldr.add_cert(&ca).unwrap();
store_bldr
.param_mut()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);

View File

@ -15,7 +15,7 @@ fn test_verify_cert() {
assert_eq!(Ok(()), verify(&leaf, &[&root1], &[&intermediate], |_| {}));
#[cfg(not(feature = "fips-compat"))]
#[cfg(not(feature = "legacy-compat-deprecated"))]
assert_eq!(
Ok(()),
verify(
@ -26,7 +26,7 @@ fn test_verify_cert() {
)
);
#[cfg(feature = "fips-compat")]
#[cfg(feature = "legacy-compat-deprecated")]
assert_eq!(
Err(X509VerifyError::CERT_HAS_EXPIRED),
verify(
@ -60,8 +60,8 @@ fn test_verify_cert() {
assert_eq!(
Ok(()),
verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| {
param.clear_flags(X509VerifyFlags::TRUSTED_FIRST)
},)
param.clear_flags(X509VerifyFlags::TRUSTED_FIRST);
})
);
}
@ -75,7 +75,7 @@ fn verify(
let mut builder = X509StoreBuilder::new().unwrap();
for cert in trusted {
builder.add_cert((**cert).to_owned()).unwrap();
builder.add_cert(cert).unwrap();
}
builder.build()

View File

@ -20,20 +20,14 @@ bitflags! {
const NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as _;
#[cfg(feature = "underscore-wildcards")]
const UNDERSCORE_WILDCARDS = ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS as _;
#[deprecated(since = "0.10.6", note = "renamed to NO_WILDCARDS")]
const FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _;
}
}
#[doc(hidden)]
#[deprecated(note = "X509Flags renamed to X509VerifyFlags")]
pub use X509VerifyFlags as X509Flags;
bitflags! {
/// Flags used to configure verification of an `X509` certificate
/// Flags used to check an `X509` certificate.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[doc(alias = "X509Flags")]
pub struct X509VerifyFlags: c_ulong {
const CB_ISSUER_CHECK = ffi::X509_V_FLAG_CB_ISSUER_CHECK as _;
const USE_CHECK_TIME = ffi::X509_V_FLAG_USE_CHECK_TIME as _;
@ -126,10 +120,9 @@ impl X509VerifyParamRef {
let raw_host = if host.is_empty() { "\0" } else { host };
cvt(ffi::X509_VERIFY_PARAM_set1_host(
self.as_ptr(),
raw_host.as_ptr() as *const _,
raw_host.as_ptr().cast(),
host.len(),
))
.map(|_| ())
}
}
@ -141,10 +134,9 @@ impl X509VerifyParamRef {
let raw_email = if email.is_empty() { "\0" } else { email };
cvt(ffi::X509_VERIFY_PARAM_set1_email(
self.as_ptr(),
raw_email.as_ptr() as *const _,
raw_email.as_ptr().cast(),
email.len(),
))
.map(|_| ())
}
}
@ -165,10 +157,9 @@ impl X509VerifyParamRef {
};
cvt(ffi::X509_VERIFY_PARAM_set1_ip(
self.as_ptr(),
buf.as_ptr() as *const _,
buf.as_ptr().cast(),
len,
))
.map(|_| ())
}
}
@ -189,6 +180,6 @@ impl X509VerifyParamRef {
/// If a parameter is unset in `src`, the existing value in `self`` is preserved.
#[corresponds(X509_VERIFY_PARAM_set1)]
pub fn copy_from(&mut self, src: &Self) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_VERIFY_PARAM_set1(self.as_ptr(), src.as_ptr())).map(|_| ()) }
unsafe { cvt(ffi::X509_VERIFY_PARAM_set1(self.as_ptr(), src.as_ptr())) }
}
}

View File

@ -9,59 +9,35 @@ repository = { workspace = true }
documentation = "https://docs.rs/hyper-boring"
readme = "README.md"
exclude = ["test/*"]
rust-version = "1.80"
rust-version = { workspace = true }
[package.metadata.docs.rs]
features = ["pq-experimental"]
features = []
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["runtime"]
runtime = ["hyper_old/runtime"]
default = ["boring/default", "tokio-boring/default"]
# Use a FIPS-validated version of boringssl.
fips = ["tokio-boring/fips"]
# Use a FIPS build of BoringSSL, but don't set "fips-compat".
#
# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer
# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen,
# which means the "fips-compat" feature is no longer needed.
#
# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply
# "fips-compat".
fips-precompiled = ["tokio-boring/fips-precompiled"]
# Link with precompiled FIPS-validated `bcm.o` module.
fips-link-precompiled = ["tokio-boring/fips-link-precompiled"]
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
pq-experimental = ["tokio-boring/pq-experimental"]
# Enable Hyper 1 support
hyper1 = ["dep:http", "dep:hyper", "dep:hyper-util", "dep:tower-service"]
fips = ["boring/fips", "tokio-boring/fips"]
[dependencies]
antidote = { workspace = true }
http = { workspace = true, optional = true }
http_old = { workspace = true }
hyper = { workspace = true, optional = true }
hyper-util = { workspace = true, optional = true, features = ["client", "client-legacy"] }
hyper_old = { workspace = true, features = ["client"] }
http = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true, features = ["client", "client-legacy"] }
linked_hash_set = { workspace = true }
boring = { workspace = true }
boring = { workspace = true, default-features = false }
tokio = { workspace = true }
tokio-boring = { workspace = true }
tokio-boring = { workspace = true, default-features = false }
tower-layer = { workspace = true }
tower-service = { workspace = true, optional = 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"] }
hyper_old = { workspace = true, features = [ "full" ] }
tokio = { workspace = true, features = [ "full" ] }
tower = { workspace = true, features = ["util"] }
futures = { workspace = true }

View File

@ -86,9 +86,8 @@ impl SessionCache {
}
pub fn remove(&mut self, session: &SslSessionRef) {
let key = match self.reverse.remove(session.id()) {
Some(key) => key,
None => return,
let Some(key) = self.reverse.remove(session.id()) else {
return;
};
if let Entry::Occupied(mut sessions) = self.sessions.entry(key) {

View File

@ -10,12 +10,9 @@ use std::sync::LazyLock;
use tokio_boring::SslStream;
mod cache;
mod v0;
/// Hyper 1 support.
#[cfg(feature = "hyper1")]
pub mod v1;
mod v1;
pub use self::v0::*;
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());

View File

@ -1,346 +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_old::uri::Scheme;
use hyper_old::client::connect::{Connected, Connection};
#[cfg(feature = "runtime")]
use hyper_old::client::HttpConnector;
use hyper_old::service::Service;
use hyper_old::Uri;
use std::error::Error;
use std::future::Future;
use std::net;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{fmt, io};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tower_layer::Layer;
/// A Connector using BoringSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
inner: Inner,
}
#[cfg(feature = "runtime")]
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.
///
/// Requires the `runtime` Cargo feature.
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 = 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<S> Service<Uri> for HttpsConnector<S>
where
S: Service<Uri> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
S::Response: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
{
type Response = MaybeHttpsStream<S::Response>;
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)?;
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 (chars.next(), chars.last()) == (Some('['), Some(']'))
&& 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> AsyncRead for MaybeHttpsStream<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf),
}
}
}
impl<T> AsyncWrite 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(s) => Pin::new(s).poll_write(ctx, buf),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_write(ctx, buf),
}
}
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_flush(ctx),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_flush(ctx),
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut *self {
MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx),
MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx),
}
}
}

View File

@ -29,14 +29,11 @@ pub struct HttpsConnector<T> {
inner: Inner,
}
#[cfg(feature = "runtime")]
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.
///
/// Requires the `runtime` Cargo feature.
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new();
http.enforce_http(false);
@ -116,7 +113,7 @@ impl 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())
Self::with_connector_and_settings(ssl, HttpsLayerSettings::default())
}
/// Creates a new `HttpsLayer` with settings
@ -246,9 +243,8 @@ where
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 Some((inner, uri)) = tls_setup else {
return Ok(MaybeHttpsStream::Http(conn));
};
let mut host = uri.host().ok_or("URI missing host")?;
@ -256,15 +252,12 @@ where
// 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];
}
}
if let Some(ipv6) = host
.strip_prefix('[')
.and_then(|h| h.strip_suffix(']'))
.filter(|h| h.parse::<net::Ipv6Addr>().is_ok())
{
host = ipv6;
}
let ssl = inner.setup_ssl(&uri, host)?;

View File

@ -1,156 +0,0 @@
use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod};
use futures::StreamExt;
use hyper_boring::HttpsConnector;
use hyper_old::client::HttpConnector;
use hyper_old::server::conn::Http;
use hyper_old::{service, Response};
use hyper_old::{Body, Client};
use std::convert::Infallible;
use std::{io, iter};
use tokio::net::TcpListener;
#[tokio::test]
#[cfg(feature = "runtime")]
async fn google() {
let ssl = HttpsConnector::new().unwrap();
let client = Client::builder()
.pool_max_idle_per_host(0)
.build::<_, Body>(ssl);
for _ in 0..3 {
let resp = client
.get("https://www.google.com".parse().unwrap())
.await
.expect("connection should succeed");
let mut body = 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(Body::empty())) });
Http::new()
.http1_keep_alive(false)
.serve_connection(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().build::<_, Body>(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 = 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(Body::empty())) });
Http::new()
.http2_only(true)
.serve_connection(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().build::<_, Body>(ssl);
let resp = client
.get(format!("https://foobar.com:{port}").parse().unwrap())
.await
.unwrap();
assert!(resp.status().is_success(), "{}", resp.status());
let mut body = resp.into_body();
while body.next().await.transpose().unwrap().is_some() {}
}

View File

@ -1,11 +1,9 @@
#![cfg(feature = "hyper1")]
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::v1::HttpsConnector;
use hyper_boring::HttpsConnector;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
use hyper_util::rt::{TokioExecutor, TokioIo};
@ -80,7 +78,7 @@ async fn localhost() {
let file = File::create("../target/keyfile.log").unwrap();
ssl.set_keylog_callback(move |_, line| {
let _ = writeln!(&file, "{}", line);
let _ = writeln!(&file, "{line}");
});
let ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
@ -88,7 +86,7 @@ async fn localhost() {
for _ in 0..3 {
let resp = client
.get(format!("https://foobar.com:{}", port).parse().unwrap())
.get(format!("https://foobar.com:{port}").parse().unwrap())
.await
.unwrap();
assert!(resp.status().is_success(), "{}", resp.status());
@ -151,7 +149,7 @@ async fn alpn_h2() {
let client = Client::builder(TokioExecutor::new()).build::<_, Empty<Bytes>>(ssl);
let resp = client
.get(format!("https://foobar.com:{}", port).parse().unwrap())
.get(format!("https://foobar.com:{port}").parse().unwrap())
.await
.unwrap();
assert!(resp.status().is_success(), "{}", resp.status());

View File

@ -4,6 +4,7 @@ version = { workspace = true }
authors = ["Alex Crichton <alex@alexcrichton.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = { workspace = true }
rust-version = { workspace = true }
repository = { workspace = true }
homepage = "https://github.com/cloudflare/boring"
documentation = "https://docs.rs/tokio-boring"
@ -12,35 +13,21 @@ An implementation of SSL streams for Tokio backed by BoringSSL
"""
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental"]
features = ["rpk"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["boring/default"]
# Use a FIPS-validated version of boringssl.
fips = ["boring/fips", "boring-sys/fips"]
# Use a FIPS build of BoringSSL, but don't set "fips-compat".
#
# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer
# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen,
# which means the "fips-compat" feature is no longer needed.
#
# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply
# "fips-compat".
fips-precompiled = ["boring/fips-precompiled"]
# Link with precompiled FIPS-validated `bcm.o` module.
fips-link-precompiled = ["boring/fips-link-precompiled", "boring-sys/fips-link-precompiled"]
# 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 }
boring = { workspace = true, default-features = false }
boring-sys = { workspace = true, default-features = false }
tokio = { workspace = true }
[dev-dependencies]

View File

@ -6,7 +6,7 @@ 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 server = ssl::SslMethod::tls();
let mut ssl_builder = boring::ssl::SslAcceptor::mozilla_modern(server)?;
ssl_builder.set_default_verify_paths()?;
ssl_builder.set_verify(ssl::SslVerifyMode::PEER);

View File

@ -41,7 +41,7 @@ pub trait SslContextBuilderExt: private::Sealed {
///
/// # Safety
///
/// The returned [`SslSession`] must not be associated with a different [`SslContext`].
/// The returned [`boring::ssl::SslSession`] must not be associated with a different [`boring::ssl::SslContext`].
unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static;

View File

@ -26,7 +26,7 @@ async fn test() {
builder
.set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL);
builder.set_new_session_callback(|_, session| {
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap()
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap();
});
unsafe {
@ -49,7 +49,7 @@ async fn test() {
let connector = create_connector(|builder| {
builder.set_session_cache_mode(SslSessionCacheMode::CLIENT);
builder.set_new_session_callback(|_, session| {
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap()
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap();
});
builder.set_ca_file("tests/cert.pem")

View File

@ -1,109 +1,151 @@
#[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};
#![cfg(feature = "rpk")]
fn create_server() -> (
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
SocketAddr,
) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
use boring::pkey::PKey;
use boring::ssl::{
CertificateType, SslAcceptor, SslAlert, SslConnector, SslCredential, SslMethod, SslVerifyError,
SslVerifyMode,
};
use futures::future;
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::OnceLock;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio_boring::{HandshakeError, SslStream};
listener.set_nonblocking(true).unwrap();
fn create_server() -> (
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
SocketAddr,
) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let listener = TcpListener::from_std(listener).unwrap();
let addr = listener.local_addr().unwrap();
listener.set_nonblocking(true).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();
let listener = TcpListener::from_std(listener).unwrap();
let addr = listener.local_addr().unwrap();
acceptor.set_rpk_certificate(&cert).unwrap();
acceptor.set_null_chain_private_key(&pkey).unwrap();
let server = async move {
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
let private_key =
PKey::private_key_from_pem(&std::fs::read("tests/key.pem").unwrap()).unwrap();
let spki = std::fs::read("tests/pubkey.der").unwrap();
let acceptor = acceptor.build();
acceptor
.add_credential({
let mut cred = SslCredential::new_raw_public_key().unwrap();
let stream = listener.accept().await.unwrap().0;
cred.set_private_key(&private_key).unwrap();
cred.set_spki_bytes(Some(&spki)).unwrap();
tokio_boring::accept(&acceptor, stream).await
};
&cred.build()
})
.unwrap();
(server, addr)
}
let acceptor = acceptor.build();
#[tokio::test]
async fn server_rpk() {
let (stream, addr) = create_server();
let stream = listener.accept().await.unwrap().0;
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");
tokio_boring::accept(&acceptor, stream).await
};
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;
}
(server, addr)
}
async fn connect(
addr: SocketAddr,
spki_path: &str,
is_ok_cell: &Arc<OnceLock<bool>>,
) -> Result<SslStream<TcpStream>, HandshakeError<TcpStream>> {
let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
let spki = PKey::public_key_from_der(&std::fs::read(spki_path).unwrap()).unwrap();
let is_ok_cell = Arc::clone(is_ok_cell);
connector
.set_server_certificate_types(&[CertificateType::RAW_PUBLIC_KEY])
.unwrap();
connector.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
let public_key = ssl
.peer_pubkey()
.ok_or(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))?;
let is_ok = public_key.public_eq(&spki);
is_ok_cell.set(is_ok).unwrap();
if !is_ok {
return Err(SslVerifyError::Invalid(SslAlert::BAD_CERTIFICATE));
}
Ok(())
});
let config = connector.build().configure().unwrap();
tokio_boring::connect(
config,
"localhost",
TcpStream::connect(&addr).await.unwrap(),
)
.await
}
#[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 is_ok_cell = Arc::new(OnceLock::new());
let mut stream = connect(addr, "tests/pubkey.der", &is_ok_cell)
.await
.unwrap();
assert!(is_ok_cell.get().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 is_ok_cell = Arc::new(OnceLock::new());
let err = connect(addr, "tests/pubkey2.der", &is_ok_cell)
.await
.unwrap_err();
assert!(!is_ok_cell.get().unwrap());
// NOTE: smoke test for https://github.com/cloudflare/boring/issues/140
let _ = err.to_string();
};
future::join(server, client).await;
}