Compare commits

..

433 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
Andrew
a721d89d08 Merge in upstream v4.21.1 2026-02-02 22:39:20 -05: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
395f57810b v4.21.1
Only fixes docs.rs builds
2026-01-23 13:21:20 +00:00
Kornel
64b3e14976 Fix docs.rs build 2026-01-23 13:03:08 +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
33b36ab7a1 Release 4.21.0 2026-01-21 13:25:44 +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
Kornel
80c3a8911e Fix build with --no-default-features 2026-01-20 17:05:55 +00:00
Kornel
f10414d927 Ensure dependency requirements are bumped
#436
2026-01-20 17:05:55 +00:00
Kornel
98215dbdb0 Bump and fix boring-sys minimum version
Fixes #436
2026-01-20 17:05:55 +00:00
Kornel
05e3aafd0b Fix deprecated call 2026-01-20 17:05:55 +00:00
Kornel
35774a0116 Can't cross-build with clang-12 due to libc++ mismatch 2026-01-20 17:05:55 +00:00
Kornel
16290eb11b Use fips-build-compatible ERR_add_error_data 2026-01-20 17:05:55 +00:00
Kornel
97aa4a987e Include err.h in FFI bindings 2026-01-20 17:05:55 +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
5a3d1ae4b2 Make set_curves_list always available
kx-* features don't exist in v5
2026-01-05 23:25:21 +00:00
Kornel
8dfa471c86 Remove deprecated X509CheckFlags flag 2026-01-05 23:23:50 +00:00
Kornel
a54b351cc0 Warn about set_curves() removal
b46d77087e
2026-01-05 22:36:36 +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
Kornel
36a2a1ca44 Deprecate set_ex_data 2026-01-05 20:52:12 +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
Kornel
feb62b627b Release notes 2025-12-05 15:33:20 +00:00
Kornel
7efa9e1dd0 Bump 2025-12-05 14:15:58 +00:00
Kornel
ff3d69c075 Fix string data conversion in ErrorStack::put() 2025-12-05 14:15:58 +00:00
Jaap Aarts
f2f4871a39 Freebsd build 2025-12-05 14:15:58 +00:00
Bas Westerbaan
237c82d394 pq: fix MSVC C4146 warning 2025-12-05 14:15:58 +00:00
Kornel
327162be0d Add more reliable library_reason() 2025-12-05 14:15:58 +00:00
Kornel
7c8dd2678a Ensure that ERR_LIB type can be named 2025-12-05 14:15:58 +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
Kornel
c3f33f0ea1 Upgrade deps 2025-09-26 13:34:13 +01:00
Kornel
3116032a83 Skip Rust version detection for bindgen 2025-09-26 13:34:13 +01:00
Kornel
9bad96e48b Style nits 2025-09-26 13:33:19 +01:00
Kornel
fa9df8081d Deprecated GHA feature 2025-09-26 13:20:26 +01:00
Kornel
4814eb8547 Ensure rustfmt and clippy are available 2025-09-26 13:20:26 +01:00
Kornel
a50a39fde7 Support TARGET_CC and CC_{target} 2025-09-26 10:57:01 +01:00
Kornel
21f2885be3 Fix swapped host/target args 2025-09-26 10:57:01 +01:00
Kornel
79338a99ea CStr UTF-8 improvements 2025-09-26 10:55:46 +01:00
Evan Rittenhouse
330bf825d4
Release 4.19.0 (#382) 2025-09-05 12:13:20 -07:00
Evan Rittenhouse
963425eb82 Add binding for X509_check_ip_asc
The binding corresponds to
https://boringssl.googlesource.com/boringssl.git/+/refs/heads/master/include/openssl/x509.h#4690.

To see the SANs covered by the specified cert, use:

```shell
❯ openssl x509 -in ./boring/test/alt_name_cert.pem -noout -text | grep -A1 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
                DNS:example.com, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, email:test@example.com, URI:http://www.example.com
```
2025-09-05 10:23:00 +01:00
Kornel
50fa2e672f Use ERR_clear_error 2025-09-03 17:24:30 +01:00
Kornel
a91bfdc67d Error descriptions and docs 2025-09-03 17:24:30 +01:00
Kornel
8d77a5d40e Boring doesn't use function codes 2025-09-03 17:24:30 +01:00
Kornel
c5045fb6b4 Fix patched docs.rs builds 2025-09-03 17:24:22 +01:00
Kornel
8966ca27b7 Test docs.rs docs 2025-09-03 17:24:22 +01:00
Kornel
3de1385660 Fix doc links 2025-09-03 17:24:22 +01:00
Alex Bakon
81d4aa19ac Merge in upstream v4.18.0 2025-08-29 15:46:15 -04:00
Kornel
404a753921 Bump 2025-08-29 19:45:01 +01:00
Kornel
a264df22fa Clippy 2025-08-29 10:51:09 -07:00
Harry Stern
26ac58b2bd Remove some comments referring to OpenSSL
Signed-off-by: Harry Stern <hstern@cloudflare.com>
2025-07-21 09:39:25 -07:00
Kornel
0ca11b5680 Use cargo:warning for warnings 2025-06-13 15:06:50 +02:00
Kornel
8d5fba3767 Don't link binaries on docs.rs 2025-06-13 15:06:50 +02:00
Jordan Rose
b01510d050
Expose PKey::raw_{private,public}_key (#364) 2025-06-13 09:11:51 +01:00
Alex Bakon
c596d7d47c Upgrade bindgen to v0.72.0
This release includes a fix for a build issue with the latest XCode
release.
2025-06-10 12:39:19 +01:00
Justin-Kwan
17d137e33b
Expose SSL_set1_groups to Efficiently Set Curves on SSL Session (#346) 2025-06-06 02:25:28 +01:00
Kornel
5fa9c81c88
Sprinkle #[must_use] (#368) 2025-06-05 20:40:35 +01:00
Kornel
5d57b3a057 Make X509Store shareable between contexts
#362
2025-06-05 14:45:40 +01:00
Kornel
4d178a7f9f Clippy 2025-06-05 10:16:08 +01:00
Kornel
bcec9462af Don't unwrap when Result can be returned instead 2025-06-05 10:14:54 +01:00
Kornel
29c05d41cd Avoid panicking in error handling 2025-06-05 10:14:54 +01:00
Kornel
05f798adc4 Rename to reset_with_context_data 2025-06-05 01:06:09 +01:00
Anthony Ramine
56e9fef055 Add X509StoreContextRef::init_without_cleanup
As X509_STORE_CTX_init requires its arguments to outlive
the store context, we take ownership of all of them
and put them in the store context's ex data, ensuring
the soundness of the operation without the mandatory
call to X509_STORE_CTX_cleanup after a closure
is run.
2025-06-05 01:06:09 +01:00
Anthony Ramine
45f8589d48 Add mutable ex_data APIs for X509StoreContext 2025-06-05 01:06:09 +01:00
Anthony Ramine
15975ddde4
Ensure we call X509_STORE_CTX_cleanup on error path too (#360)
As X509_STORE_CTX_init may fail after setting some values
that should outlive the store context, we must ensure we
clean things up on its error path too.

We also know it's always ok to call X509_STORE_CTX_cleanupas X509_STORE_CTX_init starts with a call to it.
2025-06-02 16:40:44 +02:00
Anthony Ramine
6789a72fc0
Fix X509VerifyContextRef::set_verify_param (#358)
This method takes ownership of the given verify param.
2025-06-02 16:39:25 +02:00
Anthony Ramine
7a52fbbe99
Add X509VerifyParamRef::copy_from (#361) 2025-06-02 16:39:11 +02:00
James Larisch
2bc82e8d1c Add support for X509_STORE_CTX_get0_untrusted 2025-06-01 19:04:22 +01:00
James Larisch
e99d162891 Add set_verify_param 2025-05-30 02:17:53 +02:00
Anthony Ramine
5e8aaf63f0
Release 4.17.0 (#354) 2025-05-28 11:53:09 +02:00
Anthony Ramine
560925293b
Revert "feat(x509): Implement Clone for X509Store (#339)" (#353)
* Revert "feat(x509): Implement `Clone` for `X509Store` (#339)"

This reverts commit 49a8d0906a.

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

* Ensure Clone is not added to X509Store

* Add comment about why X509Store must not implement Clone

---------

Co-authored-by: Kornel <kornel@cloudflare.com>
2025-05-27 18:19:35 +02:00
Yury Yarashevich
4ea82a2e1b Update bindgen from 0.70.1 -> 0.71.1. 2025-05-27 15:04:04 +01:00
James Larisch
eefc7b7265 Add X509_STORE_CTX_get0_cert interface
This method reliably retrieves the certificate the `X509_STORE_CTX` is
verifying, unlike `X509_STORE_CTX_get_current_cert`, which may return
the "problematic" cert when verification fails.
2025-05-27 14:41:16 +01:00
0x676e67
6e35abb2cd boring(ssl): use corresponds macro in add_certificate_compression_algorithm 2025-05-26 08:51:37 -07:00
0x676e67
15281c77e2 Update Cargo.toml 2025-05-26 08:51:37 -07:00
0x676e67
eb48ab9a26 build: Fix the build for 32-bit Linux platform 2025-05-26 08:51:37 -07:00
Kornel
3ab8b53532 rustfmt ;( 2025-05-26 16:30:09 +01:00
Kornel
0327dd03c6 Fix linking SystemFunction036 from advapi32 in Rust 1.87 2025-05-26 16:30:09 +01:00
Kornel
23863ffd1b Clippy 2025-05-26 16:30:09 +01:00
Eric Rosenberg
9b34d3524b add SslCurve::X25519_MLKEM768 constant 2025-05-03 01:25:12 +09:00
Rushil Mehra
9c4ea22f72 Use ubuntu-latest for all ci jobs
ubuntu 20.04 is now deprecated:
https://github.com/actions/runner-images/issues/11101
2025-04-17 01:18:30 -07:00
Shih-Chiang Chien
b29537e08f fix clippy error 2025-04-17 01:18:30 -07:00
Shih-Chiang Chien
220bedf239 expose SSL_set_compliance_policy 2025-04-17 01:18:30 -07:00
0x676e67
49a8d0906a
feat(x509): Implement Clone for X509Store (#339)
* boring(x509): impl Clone of X509Store
2025-04-07 18:05:27 +01:00
Rushil Mehra
20ad2665b2
Release 4.16.0 (#341) 2025-04-02 18:26:29 -07:00
Rushil Mehra
721b6fca2e
Add fips-precompiled feature to support newer versions of FIPS (#338)
Newer versions of FIPS don't need any special casing in our bindings,
unlike the submoduled boringssl-fips. In addition, many users currently
use FIPS by precompiling BoringSSL with the proper build tools and
passing that in to the bindings.

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

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

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

This commit introduces the `fips-precompiled` feature and removes the
`fips-no-compat` feature.
2025-03-31 12:34:29 -07:00
Felix Hanau
d5bd85b3e5
Document linking to C++ standard library (#335)
This was added in #264, but not documented so far.
2025-03-18 10:16:43 -05:00
Rushil Mehra
11630058f0
Revert "Remove "fips-no-compat", decouple "fips-compat" from "fips"" (#334) 2025-03-17 21:37:14 -05:00
Christopher Patton
d8975dc413 boring: Disable SslCurve API with "fips" feature
The "fips" feature implies use of a prebuilt boringSSL. The boringSSL
API consumed by `SslCurve` in incompatible with older versions of
boringSSL.

In the `ffi` bindings, the following symbols don't exist in older
builds:

* NID_X25519MLKEM768
* SSL_CURVE_X25519_MLKEM768
* NID_X25519Kyber768Draft00Old

The following symbols have been renamed:
* SSL_CURVE_P256KYBER768DRAFT00 => SSL_CURVE_P256_KYBER768_DRAFT00
* SSL_CURVE_X25519KYBER512DRAFT00 => SSL_CURVE_X25519_KYBER512_DRAFT00
* SSL_CURVE_X25519KYBER768DRAFT00OLD => SSL_CURVE_X25519_KYBER768_DRAFT00_OLD
* SSL_CURVE_P256KYBER768DRAFT00 => SSL_CURVE_P256_KYBER768_DRAFT00

Meanwhile, the `ssl_set_curves_list()` API is stable across these
versions of boringSSL.

These codepoints are added to the `SslCurve` API whenever
"pq-experimental" is enabled. Since this feature is no longer mutually
exclusive with prebuilt boringSSL (`boring-sys` just ignores patches),
we also need to disable this API whenever "fips" is enabled.
2025-03-16 08:45:01 +00:00
Christopher Patton
867f2b3b99 boring-sys: Ignore patches when boringSSL is precompiled
Internal users often have two builds for `boring`, one using a
precompiled build of boringSSL and another built from source with
patches applied. However the features that enable these builds are
mutually exclusive. For example, the `"pq-experimental"` feature is
required to build the source with all of the necessary codepoints for PQ
key exchange, but if this feature is enabled and a precompiled boringSSL
is provided, then the build will fail. This means users will have to
also control their builds with mutually exclusive features.

An alternative is to *ignore* features that enable patches whenever a
precompiled boringSSL is provided. This is a little different from the
"assume patched" environment variable, which applies whenever we're
building from source.
2025-03-16 08:45:01 +00:00
Christopher Patton
57307d739e Remove "fips-no-compat", decouple "fips-compat" from "fips"
Modify the "fips" feature so that it no longer implies "fips-compat".
The latter is no longer needed for recent builds of boringSSL; users who
need older builds will need to enable "fips-compat" explicitly.

Also, remove the "fipps-no-compat" feature, as it's now equivalent to
"fips".
2025-03-16 08:43:52 +00:00
Christopher Patton
c774afc859 Add feature "fips-no-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 for
"fips" users.

Currently "fips" implies "fips-compat". To allow users to upgrade
without breaking API compatibility with boring version 4, add a new
feature, "fips-no-compat", that does not imply "fips-compat".

In boring 5, we should remove "fips-no-compat" and decouple
"fips-compat" from "fips".
2025-03-14 22:22:41 +00:00
Andrew
bb42da53b3 Pull in release v4.15.0 plus the CI fix from upstream 'cloudflare/boring' 2025-03-10 17:05:16 -04:00
Christopher Patton
dde4b9ccde
Advertise X25519MLKEM768 with "kx-client-pq-preferred" (#329)
This algorithm is advertised with "kx-client-pq-supported" but not with
"preferred". However the algorithm is wide spread enough that preferring
it is not a significant risk.
2025-03-10 11:30:56 -07:00
andrew-signal
221efdfea9
Update to actions/cache@v4 (#328) 2025-03-10 10:03:32 -07:00
Rushil Mehra
ae1851ba03
Add missing release notes entry (#324)
v4.15.0 was tagged a few days ago but wasn't released until today, where
a new commit made it to master and snuck into the publish. I updated the
v4.15.0 tag to point to the right commit hash as well.
2025-02-28 10:54:55 -08:00
Yury Yarashevich
5268f63a77
Expose API to enable certificate compression. (#241) 2025-02-27 15:09:07 +00:00
Rushil Mehra
5e0002bfa8 Release 4.15.0 2025-02-24 10:54:54 +00:00
Rushil Mehra
9ba00ea586 Fix lifetimes in ssl::select_next_proto
See https://github.com/sfackler/rust-openssl/pull/2360 and
https://nvd.nist.gov/vuln/detail/CVE-2025-24898. From the rust-openssl
PR:

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

Thanks to Matt Mastracci for reporting this issue.
2025-02-23 20:19:10 +00:00
Rushil Mehra
7ba322560f
Revert cmake bump (for now) as it is overly restrictive (#321)
Some users of boring have issues with newer versions of cmake. Because
we have an alternative solution, we can hold off on the bump for now.
2025-02-22 17:16:53 -08:00
Rushil Mehra
abaf06731b
Introduce a builder pattern for SslEchKeys + make set_ech_keys take a reference (#320)
Previously, set_ech_keys would consume the SslEchKeys struct to enforce
the requirement that the struct is immutable after initializing it on a
SSL_CTX. The problem with this is that it requires applications to
needlessly reallocate the SslEchKeys struct if they want to initialize
keys on multiple SSL_CTXs, which is a pretty common pattern. To work
around this, we introduce a builder (SslEchKeysBuilder) that requires
mutable access to add keys to the underlying struct. set_ech_keys takes
in a reference to SslEchKeys, which can only be made via consuming the
builder.
2025-02-21 14:33:59 -08:00
Rushil Mehra
f439f92564 Revert "Refactor!: Remove strict TokioIo response requirement from hyper_boring::v1::HttpsConnector"
This reverts commit e518c2444a.
2025-02-21 10:41:18 +00:00
Rushil Mehra
7b4bfcbbee Revert "Refactor!: Introduce a Cargo feature for optional Hyper 0 support"
This reverts commit 49d5a61163.
2025-02-21 10:41:18 +00:00
Rushil Mehra
bc17a06df7 Address clippy lints 2025-02-21 10:40:33 +00:00
Rushil Mehra
cbdf963464 Actually expose SslEchKeys 2025-02-21 10:40:33 +00:00
Rushil Mehra
156cf04c43
Release 4.14.0 (#317) 2025-02-19 03:46:15 -08:00
Rushil Mehra
c3c7e16b9d Bump cmake-rs to improve Mac OS build parallelism
There's a bug on OSX that prevents the CMake jobserver from working
properly, and so CMake defaults to a single-threaded build. It's not
clear when this is actually going to get fixed, so recent versions of
cmake-rs just disable the jobserver and have CMake fall back to the
number of available cores:
https://github.com/rust-lang/cmake-rs/pull/229

This means we don't need e6833b0074
2025-02-19 01:30:11 -08:00
Rushil Mehra
3b5fa65860 Expose SSL_CTX_set1_ech_keys from SslContextRef
We currently expose this method on `SslContextBuilder`, which is fine
for bootstrapping an `SSL_CTX`, but subsequent attempts to set ECH keys
(like during key rotation) can only happen via `SslContextRef`. Also
update the method on the builder to take an immutable reference to self
because the API is thread safe.
2025-02-19 01:27:51 -08:00
Rushil Mehra
e6833b0074 Set CMAKE_BUILD_PARALLEL_LEVEL to available_parallelism
cmake-rs' jobserver doesn't work reliably, if at all. One workaround is
to set CMAKE_BUILD_PARALLEL_LEVEL to available_parallelism(). On my
machine it shaves ~35 seconds off of boring-sys builds.
2025-02-18 22:37:08 -08:00
0x676e67
c95d764cdd
build: Fix the build for 32-bit Linux platform (#312)
build: Fix the build for 32-bit Linux platform
2025-02-13 18:54:39 -08:00
Kornel
bf0e21cec8 Use corresponds macro 2025-02-13 09:34:13 -08:00
Rushil Mehra
05270fa100 Expose SSL_set_enable_ech_grease 2025-02-13 11:27:18 +00:00
Rushil Mehra
24003a04e8 Clean up ECH tests 2025-02-13 11:27:18 +00:00
Rushil Mehra
5af82912df Expose client/server-side ECH
Resolves https://github.com/cloudflare/boring/issues/282
2025-02-12 17:07:17 +00:00
Rushil Mehra
2561bdf64d Expose EVP_HPKE_KEY 2025-02-12 17:07:17 +00:00
Rushil Mehra
a613d4b510 Clean up boring_sys::init()
We don't need the workaround that was initially introduced for a bug in
openssl, and OPENSSL_init_ssl always calls into CRYPTO_library_init on
boringssl, so just call it explicitly.
2025-02-11 17:48:58 -08:00
Kornel
3b36cb6c78 Detailed error codes 2025-02-11 23:53:46 +00:00
Evan Rittenhouse
3355ccca67 chore: Fix docs on SslRef::replace_ex_data 2025-02-04 15:10:41 +00:00
Alessandro Ghedini
76b592673f fix manual_c_str_literals clippy warning 2025-01-22 17:52:35 +00:00
Alessandro Ghedini
af9df3765d replace once_cell with LazyLock
We can drop the once_cell dependency since the same functionality is
implemented in std now.

Requires bumping MSRV to 1.80.
2025-01-22 17:52:35 +00:00
Bas Westerbaan
6ca27a7738 RTG-3333 Support X25519MLKEM768 by default, but don't sent it as client
X25519MLKEM768 is the standardised successor of the preliminary
X25519Kyber768Draft00. Latest browsers have switched to X25519MLKEM768.
Cloudflare supports both on the edge.

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

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

BoringSSL against which we build must support X25519MLKEM768, otherwise
this will fail.
2025-01-13 18:10:32 +01:00
Rushil Mehra
796afe1637 Allow dead_code instead of disabling clippy entirely for bindgen 2025-01-06 16:12:53 +00:00
Leo Blöcher
baede6c0af Remove INVALID_CALL from mid-handshake error message
Mid-handshake errors that occur before certificate verification
currently look like this:

```
TLS handshake failed: cert verification failed - Invalid certificate verification context [WRONG_VERSION_NUMBER]
```

Despite no certificate even being received yet, the error complains
about a failed verification. The cause here is that `cert verification
failed` is only omitted if the verification result is `OK`. The default
in BoringSSL before verification runs is `INVALID_CALL`, however.

`INVALID_CALL` is set/returned in these places:
- 44b3df6f03/src/ssl/internal.h (L3904)
- 44b3df6f03/src/ssl/ssl_session.cc (L396)
- 44b3df6f03/src/ssl/ssl_x509.cc (L713)

It is not used anywhere else as a verification result code. To improve
the error message, this commit adds `INVALID_CALL` as a verification
result for which no additional error is dislayed.
2025-01-06 16:12:05 +00:00
Rushil Mehra
33b511331b Fix bug with accessing memzero'd X509StoreContext in tests
As of https://boringssl-review.googlesource.com/c/boringssl/+/64141,
X509_STORE_CTX_cleanup will zero the memory allocated to the
X509_STORE_CTX. Because X509StoreContextRef::init invokes
X509_STORE_CTX_cleanup once the with_context closure has finished,
calling X509StoreContextRef::verify_result (or any API really) is going
to be invalid because memory has been zerod out. This is a pretty big
footgun, so maybe we should consider screaming a bit louder for this
case.
2025-01-06 16:08:33 +00:00
Rushil Mehra
c05a339911 Support linking with a runtime cpp library
As of https://boringssl-review.googlesource.com/c/boringssl/+/66288,
libssl allows a C++ runtime dependency. As such, we need to link with a
cpp runtime library. Implementation is inspired heavily from
54c956b2e6.

Before releasing this change, we'll need to figure out a way to support
this for windows.
2025-01-06 16:08:33 +00:00
Paul Mabileau
49d5a61163 Refactor!: Introduce a Cargo feature for optional Hyper 0 support
Closes #294. Requires breaking changes. The default v0 is changed in
favor of v1, but v0 is still kept available, just in a forced module
path. It enables dependency de-duplication when consuming it.

Signed-off-by: Paul Mabileau <paul.mabileau@harfanglab.fr>
2024-12-07 21:07:31 +00:00
Paul Mabileau
e518c2444a Refactor!: Remove strict TokioIo response requirement from hyper_boring::v1::HttpsConnector
Closes #295.

Signed-off-by: Paul Mabileau <paul.mabileau@harfanglab.fr>
2024-12-07 19:20:22 +00:00
Andrew
615af5aafb Add history back from upstream so future merges apply cleanly 2024-12-04 22:14:22 -05:00
Andrew
1af143f509 Revert 12d00aa166
This reverts commit 12d00aa166.

We do not use this flag, so revert this commit to get back closer to upstream.
2024-12-04 18:48:37 -05:00
Andrew
f7b6f5dfa1 Merge upstream 4.13.0; resolve conflicts 2024-12-04 18:48:15 -05:00
Rushil Mehra
4685af00bb Release 4.13.0 2024-11-28 12:47:31 -08:00
Kornel
57fbe0f594 Sync X509StoreBuilder with openssl 2024-11-28 11:48:09 -08:00
Kornel
b26b78611b Sync X509VerifyFlags with openssl 2024-11-28 11:48:09 -08:00
Steven Fackler
6ef5c28239 More corresponds from openssl 2024-11-28 11:48:09 -08:00
Kornel
1946603e15 Work around Rust settings inconsistent iOS SDK version 2024-11-28 11:21:39 -08:00
Kornel
81e84c26a2 Clippy 2024-11-28 11:21:39 -08:00
Paul Mabileau
7ac0d2105c Fix Windows build
Only with this does it work for us.

Signed-off-by: Paul Mabileau <paul.mabileau@harfanglab.fr>
2024-11-27 12:56:03 -08:00
Evan Rittenhouse
2b75e1e127 Release 4.12.0 2024-11-20 08:59:31 -08:00
Evan Rittenhouse
c113ec5a0d Add bindings for SSL_CB_ACCEPT_EXIT and SSL_CB_CONNECT_EXIT 2024-11-20 08:17:44 -08:00
Rushil Mehra
7bb3647406 (ci): brew link x86 toolchain for macos13 runner
It seems we need to manually symlink the x86_64-unknown-linux-gnu
toolchain for the macos13 runner. Also, we don't need to overwrite the
python version anymore

Fixes https://github.com/cloudflare/boring/issues/285
2024-10-24 09:49:47 -07:00
Jordan Rose
3044e9ba98 Skip bindgen 0.70's layout tests before Rust 1.77 2024-10-22 16:53:40 -07:00
Jordan Rose
3d4180b232 Skip bindgen 0.70's layout tests before Rust 1.77 2024-10-22 16:08:26 -07:00
Mike Aizatsky
889ad0f41e Update bindgen to 0.70.1
bindgen has had a lot of improvements since 0.68, and this newer version seems to able to compile libbssl from within edgeworker
2024-10-22 10:55:12 -07:00
James Larisch
bb373e5550 Add set_cert_verify_callback (SSL_CTX_set_cert_verify)
Add a wrapper for `SSL_CTX_set_cert_verify`, which allows consumers to
override the default certificate verification behavior.

The binding resembles `SSL_CTX_set_verify`'s.

See
https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/
for more details.
2024-10-22 00:05:57 -07:00
Evan Rittenhouse
ec3b412cc8 Release 4.11.0 2024-10-17 10:18:52 -07:00
Evan Rittenhouse
80e3aab37d boring-sys: include HPKE header file for bindgen
BoringSSL doesn't expose these APIs for FIPs builds, so we gate them
here as well
2024-10-17 10:18:52 -07:00
Alessandro Ghedini
a57cb3a997
Add "fips-compat" feature (#286)
This adds a feature to build against a BoringSSL version compatible with
the current boringssl-fips, but _without_ actually enabling the `fips`
feature.

This can be useful to use with `fips-link-precompiled` while using a
custom BoringSSL version based on the older FIPS branch.
2024-10-17 09:58:02 -07:00
Hrushikesh Deshpande
d3177a4300 Create semgrep.yml
Creating Semgrep.yml file - Semgrep is a tool that will be used to scan Cloudflare's public repos for Supply chain, code and secrets. This work is part of Application & Product Security team's initiative to onboard Semgrep onto all of Cloudflare's public repos.

In case of any questions, please reach out to "Hrushikesh Deshpande" on cf internal chat.
2024-10-14 11:33:22 -04:00
Rushil Mehra
294796f7f6
Release 4.10.3 (#280) 2024-09-23 10:06:51 +02:00
Yuchen Wu
0596b2dcab
Set MSRV to 1.70 (#279)
With the bindgen 0.70 upgrade, the default rust target is set to be 1.77,
which becomes the de facto MSRV of boring-sys since the change.

This change makes sure that the MSRV of boring-sys is kept at 1.70,
which is the same as that of bindgen.
2024-09-21 21:42:39 +02:00
Rushil Mehra
1a00540c16
Release 4.10.2 (#278) 2024-09-19 00:38:25 +02:00
Bas Westerbaan
b1a7434c19 boring-pq.patch Fix by not updating crypto_test_data.cc
crypto_test_data.cc is not included anymore since 2475ef386b
2024-09-19 00:17:37 +02:00
Rushil Mehra
df1c4f55e6
Release 4.10.1 (#276) 2024-09-18 22:53:02 +02:00
Bas Westerbaan
b7eaa5bc95 Don't support X25519MLKEM768 by default (yet) 2024-09-18 22:36:04 +02:00
Rushil Mehra
b7f47dec46
Release 4.10.0 (#274) 2024-09-18 14:20:28 +02:00
Anthony Ramine
193bf3b9d7
Implement optional Hyper 1 support in hyper-boring (#246) 2024-09-18 13:24:35 +02:00
Bas Westerbaan
6d3639f173 Add post-quantum key agreement X25519MLKEM768
This is the successor of X25519Kyber768Draft00.

Spec:

https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/02/

IANA has assigned the codepoint.

https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8

Upstream BoringSSL support landed in.

7fb4d3da50

The version of BoringSSL we patch does not include it, so we add it manually.

Chrome and Firefox are planning to enable in October.

This PR is based on the IPD-Wing patch reviewed here:

https://github.com/cloudflare/boring/pull/243

There are two changes. First we simplify the patch a bit as we do not
need IPD-Wing. Secondly, we perform the encapsulation key check, which
was a last minute addition of NIST. We perform this check also for Kyber.
2024-09-18 12:00:27 +02:00
Bas Westerbaan
2c0a14253a Revert "PQ: fix timing sidechannels and add IPDWing"
For TLS, early adopters prefer X25519MLKEM768. Remove IPDWing in
preparation for adding X25519MLKEM768.

https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/

This reverts commit 4725a930d5.
2024-09-18 12:00:27 +02:00
Mike Aizatsky
8cb5da61a6 Update bindgen to 0.70.1
bindgen has had a lot of improvements since 0.68, and this newer version seems to able to compile libbssl from within edgeworker
2024-09-18 01:30:38 +02:00
Bas Westerbaan
4b37d88b80
Expose SSL(_CTX)_set1_curves_list (#270)
set_surves_list is similar to set_curves, but the curves are specified
by a string. This makes it convenient when the supported curves of
the underlying BoringSSL is not known at compile time.

Also fix a bug in checking return value of SSL_set1_curves_list.
2024-09-17 10:00:25 +02:00
Evan Rittenhouse
b2525f2ed2
Expose SSL_CTX_set_info_callback (#266)
Model callback arguments as structs
2024-09-11 10:35:51 +02:00
Rushil Mehra
7324db2b75 Use ForeignType::into_ptr wherever applicable 2024-09-04 04:04:30 -07:00
Evan Rittenhouse
e5b6627efc Expose RSAPSS public key Id type 2024-08-19 15:54:22 -07:00
Rushil Mehra
a7bfe0d92c Fix macos FIPS crossbuild 2024-08-15 15:09:02 -07:00
Rushil Mehra
ef8146be7c Add tests for X509Ref::subject_key_id, X509Ref::authority_key_id, and X509NameRef::print_ex 2024-08-15 15:09:02 -07:00
Rushil Mehra
96981dd6c6 Expose X509NameRef::print_ex 2024-08-15 15:09:02 -07:00
Rushil Mehra
fae2f7fbf1 Introduce corresponds macro from openssl-macros
Our rustdocs are miserably broken. We manually link to openssl docs in
most binding definitions, and openssl keeps changing their documentation
URL, so in order to fix everything I'd have to touch every single
binding definition in every single file. Instead, we should use the
`corresponds` macro from the openssl-macros crate which nicely adds a
link to the openssl documentation on our behalf. If the openssl
documentation url ever changes again in the future, a simple dependency
bump should solve the issue.
2024-08-15 15:09:02 -07:00
Rushil Mehra
2be6e100b6 Introduce ForeignTypeExt and ForeignTypeRefExt
`ForeignTypeExt` and `ForeignTypeRefExt` are inspired by
https://github.com/sfackler/rust-openssl/pull/1345, which make dealing
with FFI safer and more ergonomic. The new APIs (e.g.
from_const_ptr_opt`) also allow for gracefully handling instances where
the initial API call results in `NULL`. Instead of crashing the program,
`None` will be returned.
2024-08-15 15:09:02 -07:00
Rushil Mehra
1b5ae3251f Expose mTLS related APIs 2024-08-15 15:09:02 -07:00
0x676e67
9053b5d905
chore(boring-sys): Fix git apply patch on Windows (#261)
* chore(boring-sys): Fix git apply patch on Windows

* cargo fmt --all
2024-08-14 02:53:57 -07:00
Rushil Mehra
72b343def1
Release 4.9.1 (#259) 2024-08-04 12:40:55 -07:00
Rushil Mehra
89dc444fb3 Properly handle Option<i32> in SslRef::set_curves 2024-08-04 11:49:30 -07:00
Jordan Rose
59883d7e23 CI: Adjust CI branch to "main" instead of "master" and "libsignal" 2024-08-02 10:29:53 -07:00
Jordan Rose
fbea17998a Merge tag 'v4.9.0' into libsignal 2024-08-02 10:21:51 -07:00
Rushil Mehra
cfc8f2db4f Actually Release 4.9.0 2024-08-02 02:14:46 -07:00
Rushil Mehra
6c4a02f131 Actually Release 4.9.0 2024-08-02 01:49:23 -07:00
Rushil Mehra
291aaba3ea Release 4.9.0 2024-08-02 01:19:08 -07:00
Anthony Ramine
8ece78238c
Guard against empty strings given to select_next_proto (#252) 2024-08-02 09:26:03 +02:00
Rushil Mehra
5e304d9248 Document SslCurve::nid() 2024-08-01 23:18:30 -07:00
Rushil Mehra
0e33475b93 Add SslCurve::to_nid() and remove SslCurveId
We previously added an `SslCurveId` struct to house SSL_CURVE variants of
the internal NID constants, to allow `SslRef::curve()` to properly
instantiate `SslCurve` structures. This was done to ensure
`SslRef::set_curves()` did not break, as it expects the internal NID
constants instead of the public SSL_CURVE ones. In future versions of
boringssl, this problem is solved by virtue of the
SSL_CTX_set1_group_ids API. Since we don't have this yet, this commit
adds `SslCurve::nid()` so `SslRef::set_curves()` can convert the
SSL_CURVE constants to the NID representation internally
without breaking the public API.
2024-08-01 12:34:33 -07:00
Evan Rittenhouse
07bfd55a4d Fix x509_check_host return value
The [x509_check_host docs](https://www.openssl.org/docs/man1.1.1/man3/X509_check_host.html)
state:
> The functions return 1 for a successful match, 0 for a failed match
and -1 for an internal error: typically a memory allocation failure or
an ASN.1 decoding error.
All functions can also return -2 if the input is malformed. For example,
X509_check_host() returns -2 if the provided name contains embedded
NULs.

The current implementation will return `true` for 1, -1, and -2,
therefore returning an incorrect value if any of the above error cases
are hit.
2024-07-31 11:20:47 -07:00
Rushil Mehra
04abc99fb2 Fix clippy lints re: docs indentation + unused feature 2024-07-30 12:28:38 -07:00
Rushil Mehra
5dce4d331b Ignore clippy / rustfmt on autogenerated code 2024-07-30 12:28:38 -07:00
Rushil Mehra
6e9ba1cb96 Clean up legacy const_fn feature gates 2024-07-30 12:28:38 -07:00
Rushil Mehra
4fe43f85d1 Impl From for SslVersion 2024-07-30 12:28:38 -07:00
Julien Rouviere
c7fd3249a7 Split SSL curve identifiers into a separate enum.
Some functions use the NID_* constants, and some use the SSL_CURVE_* ones.
Extract from the documentation:
> Where NIDs are unstable constants specific to OpenSSL and BoringSSL, group IDs are defined by the TLS protocol. Prefer the group ID representation if storing persistently, or exporting to another process or library.
2024-07-30 02:23:13 -07:00
Rushil Mehra
8786cda639 (ci): Fix macos crossbuild action by forcing brew link w python@3.11 2024-07-24 18:19:22 +01:00
Yury Yarashevich
31665926c9 Expose set_permute_extensions 2024-07-09 22:37:42 -07:00
Bas Westerbaan
4725a930d5 PQ: fix timing sidechannels and add IPDWing
Fix three potential timing sidechannels. These don't affect ephemeral
usage of Kyber as in TLS, but it's good practice to get rid of them anyway.

Also adds IPDWing, a preliminary version of X-Wing using the initial public
draft (IPD) of ML-KEM. Don't use it.
2024-07-08 19:47:43 +01:00
Ivan Nikulin
6ca7b34fd0 Release 4.8.0 2024-07-05 11:55:30 -07:00
Ivan Nikulin
d8e821bae9 Expose hmac_sha1 function 2024-06-28 10:51:46 +01:00
Rushil Mehra
b7baacc047 Fix workflows file, pin mac os FIPS crossbuild runner to macos-13
The macos-13 runner uses intel chips and thus x86, so clang 12.0.0 is
easily available.
2024-06-26 16:13:58 +01:00
Rushil Mehra
924f452130 clippy: *::max_value() -> *::MAX 2024-06-26 16:13:58 +01:00
Rushil Mehra
884c91e770 Expose X509_check_host 2024-06-26 16:13:58 +01:00
Rushil Mehra
ac208ede66 Expose SSL_add1_chain_cert 2024-06-26 16:13:58 +01:00
Rushil Mehra
fb1283ef23 Expose SSL_{set|clear}_mode 2024-06-26 16:13:58 +01:00
Rushil Mehra
2997b07d06 Expose SSL_{get|set}_{max|min}_proto_version 2024-06-26 16:13:58 +01:00
Rushil Mehra
936d81b4ff Add APIs to expose client and server cipher lists
The client sent ciphers in the ClientHello are unparsed and thus require
the user to convert u16s into SslCipher instances. It could be worth
doing this parsing in the library itself to make things consistent and
always return a StackRef<SslCipher>.
2024-06-26 16:13:58 +01:00
Rushil Mehra
1879e9cff0 Expose SSL_CIPHER_is_aead and SSL_CIPHER_auth_nid 2024-06-26 16:13:58 +01:00
Rushil Mehra
a88704076c Add NIDs for cipher authentication types 2024-06-26 16:13:58 +01:00
Rushil Mehra
760e99db46 Impl From for SslSignatureAlgorithm
This is useful for comparing raw u16s with the various sigalg constants.
2024-06-26 16:13:58 +01:00
Isaiah Becker-Mayer
b3eaacc33a Updates license field to valid SPDX format 2024-06-02 10:40:17 +01:00
Ivan Nikulin
7b97ff7bf5 Release 4.7.0 2024-05-31 18:18:56 +01:00
Ivan Nikulin
d9f7e4daf6 Fix crosscompile 2024-05-31 09:38:29 +01:00
Ivan Nikulin
738d80a653 Expose hmac_sha256/512 functions 2024-05-31 09:38:29 +01:00
Jordan Rose
b9a75167dc Merge tag 'v4.6.0' into libsignal
chore: Release boring-sys version 4.6.0
2024-04-11 16:16:29 -07:00
Anthony Ramine
b8044706ad Release 4.6.0 2024-04-09 11:25:44 +02:00
Anthony Ramine
87ed6ab9bb Tweak cliff config to exclude merge and release commits from changelog 2024-04-09 11:18:05 +02:00
Eric Rosenberg
870ccd9084 builder 2024-04-07 17:03:52 -07:00
Eric Rosenberg
2cee0af3d2 HttpsLayerSettings 2024-04-07 17:03:52 -07:00
Eric Rosenberg
8db6134c75 bound session cache
When establishing new TLS sessions, servers may send multiple session
tickets (RFC8446 4.6.1). hyper-boring caches tickets without placing a
limit on how many tickets are cached. This leads to unbounded growth of
hyper-boring's cache and leaves clients vulnerable to malicious servers
who might send many session tickets to exhaust a client's available
memory.

This change bounds the cache to a default of 8 tickets.
2024-04-07 17:03:52 -07:00
Eric Rosenberg
3d9a5e3244
add get_curve (#226) 2024-03-26 14:48:53 +01:00
Rushil Mehra
167f5aece1 Remove kx-safe-default gate on SslCurve
While setting curves should be restricted by the kx-safe-default
feature, reading the curve is allowed.
2024-03-24 11:11:22 -07:00
Liu Dingming
b96d2b88a4 Enable layout tests on iOS target 2024-03-24 11:11:05 -07:00
Anthony Ramine
30b33991e5 Fix clippy lints 2024-03-24 10:52:05 -07:00
Julien Rouviere
713558993a Add getters for client hello message 2024-03-22 20:44:55 +01:00
Isaiah Becker-Mayer
ba85412229 Removes vestigial build script 2024-02-26 13:25:46 +01:00
Kevin Guthrie
bc42edc552 Introduce and use read_uninit and write_uninit duplicated from openssl-0.10.61 and tokio-openssl-0.6.4 2024-02-21 09:07:39 +01:00
Anthony Ramine
db01409165 Release 4.5.0 2024-02-08 11:00:16 +01:00
Anthony Ramine
8ab1873d8b Introduce SslRef::set_private_key 2024-02-08 10:10:50 +01:00
Rushil Mehra
7ead83cf40 Release 4.4.1 2024-02-02 10:44:53 -08:00
Rushil Mehra
d3a42b0aeb Fix building with BORING_BSSL_PATH / BORING_BSSL_FIPS_PATH
When passing BORING_BSSL_FIPS_PATH, you need to add /lib/ to the search
path, and when passing BORING_BSSL_PATH you need to add /crypto/ and
/ssl/ to the search path.
2024-02-02 10:23:41 -08:00
Rushil Mehra
5aed467dcb Fix building with non bazel commits of boringssl
We need to add `/build/crypto` and `/build/ssl` to the library search
path to handle the case where we pass `BORING_BSSL_SOURCE_PATH` when
building without enabling any fips features. Otherwise, non bazel
commits will not work because `/build/` itself will not contain any
crypto libraries to link with
2024-02-02 14:56:55 +00:00
Anthony Ramine
3cf8bc445a Release 4.4.0 2024-01-17 17:58:57 +01:00
Jonathan Hoyland
e370083af5 Expose set_compliance_policy and get_ciphers 2024-01-17 17:57:47 +01:00
Evan Rittenhouse
0f5731b1d8 Expose SSL_get_error 2024-01-09 16:42:58 +01:00
Anthony Ramine
20f9991c18 Fix support for fips-link-precompiled
This feature expects a recent boringssl checkout (such as the one
found in boring-sys/deps/boringssl), so it should not be using
the same bindings as the fips feature, which are based on
boring-sys/deps/boringssl-fips, which is older and with a different
API.
2024-01-08 14:37:55 +01:00
Anthony Ramine
f9dfd2c47e Release 4.3.0 2024-01-03 19:39:59 +01:00
Anthony Ramine
dfd49f4fef Introduce X509Flags
For now it has a single associated constant, X509Flags::TRUSTED_FIRST.
2024-01-03 19:37:59 +01:00
Anthony Ramine
94457340ac Move x509 tests to a subdirectory 2024-01-03 19:37:59 +01:00
Anthony Ramine
1321ded678 Rearrange imports in x509 module 2024-01-03 19:37:59 +01:00
Anthony Ramine
3637bfed2f Introduce HttpsLayer::set_ssl_callback
This lets us customize the Ssl of each connection,
like set_callback which lets us customize the ConnectConfiguration
a step earlier.
2024-01-03 15:43:52 +01:00
Anthony Ramine
9b0e422c8d Don't use self-signed certs in hyper-boring tests 2024-01-03 15:43:52 +01:00
Anthony Ramine
a8dea4a22c Introduce X509CheckFlags::UNDERSCORE_WILDCARDS 2024-01-02 15:37:36 +01:00
Anthony Ramine
3df405443a Release 4.2.0 2023-12-14 16:35:54 +01:00
Anthony Ramine
72f4bf5724 Introduce set_custom_verify_callback and set_async_custom_verify_callback 2023-12-14 16:31:09 +01:00
Anthony Ramine
b97446a3c9 Restore rpk feature in tokio-boring
It serves no purpose as there is no additional API in tokio-boring when
this feature is enabled, but there is a test gated by it and it can't
be conditionnally enabled only when boring's feature is enabled.
2023-12-14 16:30:49 +01:00
Anthony Ramine
6f5f59d7a9 Remove rpk from hyper docsrs features
The feature doesn't exist anymore.
2023-12-14 16:30:49 +01:00
Anthony Ramine
446b65550a Introduce tokio_boring::SslStreamBuilder 2023-12-14 16:05:09 +01:00
Chris Eager
dd281f6ab6 Swap build and run order; always build 2023-12-14 13:25:47 +01:00
Chris Eager
59ef71327f Remove unused target_env from boring-sys build config 2023-12-14 13:25:47 +01:00
Jordan Rose
44f8f72a16 boring-sys: Blocklist max_align_t in bindgen
https://github.com/rust-lang/rust-bindgen/issues/1823
2023-12-14 13:25:47 +01:00
Jordan Rose
5dc531a38c boring-sys: Don't check for MSVC with target_env
x86_64-pc-windows-gnu is identified as `target_env = "msvc"` too,
but doesn't use the Visual Studio CMake generator.
2023-12-14 13:25:47 +01:00
Jordan Rose
4d66ada007 Use gcc/g++ as the compilers for windows-gnu, not Clang 2023-12-14 13:25:47 +01:00
Jordan Rose
016d5cb61e Always run tests in bash
This is only necessary for the windows-gnu target, but causes no
issues anywhere else.
2023-12-14 13:25:47 +01:00
Jordan Rose
8b86852d8a Add arm64-macos cross-compile to CI 2023-12-14 13:25:47 +01:00
Jordan Rose
f82f3fcb0f Add custom linker for Android cross-compilation 2023-12-14 13:25:47 +01:00
Jordan Rose
16327cf5c5 Add custom environment for ARM Linux cross-compilation 2023-12-14 13:25:47 +01:00
Chris Eager
2f62df492b Build tests even for cross-compiling 2023-12-14 13:25:47 +01:00
Jordan Rose
1028909b40 Remove musl test; there's no standard musl C++ setup for us to use 2023-12-14 13:25:47 +01:00
Chris Eager
08c417a97e Make arm and Android builds check-only 2023-12-14 13:25:47 +01:00
Chris Eager
f5583312c5 Add matrix.apt_packages 2023-12-14 13:25:47 +01:00
Chris Eager
bccb80b115 Refactor check-only and extra-test-args 2023-12-14 13:25:47 +01:00
Chris Eager
bbe8cd1b9e Add relevant --target to cargo test in CI 2023-12-14 13:25:47 +01:00
vuittont60
4d1b7fc816 Fix typos 2023-12-14 02:28:11 +01:00
Jordan Rose
af0c36a22f boring-sys: Don't use CMake cross-compilation for macOS->iOS
(or macOS->macOS)
2023-11-30 15:03:23 +01:00
Anthony Ramine
9cf03ae4c1 Move async callbacks infra to boring itself
This helps drive async callbacks from outside tokio-boring, such as in quiche.

Not a breaking change because every public item in tokio-boring is preserved as is.
2023-11-27 11:53:35 +01:00
Anthony Ramine
c38ed71511 Use replace_ex_data more
Setting callbacks multiple times on a SslContextBuilder causes the previous callback
installed to leak, using replace_ex_data internally prevents that.

We also start using it in tokio-boring in with_ex_data_future, my understanding
is that the futures currently in use are never installed twice by that function
but that could change in the future with the addition of more async callbacks.
2023-11-23 11:07:17 +01:00
Anthony Ramine
2ab71411c1 Release 4.1.0 2023-11-16 19:50:55 +01:00
Anthony Ramine
a32783374f Properly drop overwritten ex data
We introduce new methods replace_ex_data for both SslContextBuilder
and Ssl in case anyone is relying on the leaking behaviour of their
set_ex_data methods, but we do document that they leak now.
2023-11-16 19:47:16 +01:00
Anthony Ramine
d7a13a8468 Fix clippy lint 2023-11-16 19:47:16 +01:00
Jordan Rose
7c88181568 boring-sys: Don't use CMake cross-compilation for macOS->iOS
(or macOS->macOS)
2023-11-10 16:34:06 -08:00
Jordan Rose
e17586365c Drop Android 19 feature flag, part 2 2023-11-10 16:09:32 -08:00
Jordan Rose
e8e6122545 Merge tag 'v4.0.0' into libsignal 2023-11-10 16:08:07 -08:00
Anthony Ramine
2f63b8affd Introduce struct Config in build script
Using a struct improves navigation of the build script,
as we can rely on rust-analyzer to help us check how
a feature flag or an environment variable is used,
as opposed to grepping for multiple env::var calls
or #[cfg] attributes.

This commit also removes some obsolete blocks of code
related to the now defunct ndk-old-gcc and fuzzing features.
2023-11-10 15:48:50 -08:00
Jordan Rose
7f4dca3405 Drop Android 19 feature flag 2023-11-10 15:48:50 -08:00
Chris Branch
cdb76dcba2 Release 4.0.0 2023-11-10 11:51:13 +00:00
Anthony Ramine
d4518f5af7 Release 4.0.0-rc.2 2023-11-06 13:31:47 +00:00
Anthony Ramine
8c90adbcdc Update release notes 2023-11-06 13:31:47 +00:00
Anthony Ramine
b5c76432b8 Add cargo-release metadata 2023-11-06 13:31:47 +00:00
Anthony Ramine
7c5fdfa0a8 Remove Sync trait bounds on callback futures
They are unnecessary as we ever only retrieve the futures from ex data
to poll them, thus when we have mutable access to them, so Send is all we need.
2023-11-03 13:31:11 +00:00
Anthony Ramine
7a7de40833 Update Cargo.toml 2023-10-30 13:21:44 +01:00
Sebastian Hahn
fdef98407c hyper and tokio "full" feature for dev builds only
This was an accidental regression in
d1ee9bfd86 which leads to pulling in the
full featureset of tokio and hyper for all dependents of tokio-boring
and hyper-boring.

Fixes #179.
2023-10-30 13:21:44 +01:00
Chris Branch
fa155a1bf2 Release 4.0.0-rc.1 2023-10-26 16:31:07 +01:00
Chris Branch
46d482a734 Specify exact versions of dependent crates in the workspace manifest
This is needed for cargo release to update to a release candidate
version successfully; without it, only the major version is bumped,
and cargo prevents you from using a prerelease version if you didn't
specifically request it.
2023-10-26 16:31:07 +01:00
Anthony Ramine
0f74eade4b Add CI for cross-building from macOS 2023-10-26 11:31:47 +01:00
Anthony Ramine
7434e35faa Introduce BORING_BSSL_SYSROOT and BORING_BSSL_EXTERNAL_TOOLCHAIN
These variables let us configure CMAKE_SYSROOT and
CMAKE_{C,CXX,ASM}_EXTERNAL_TOOLCHAIN from env variables
without needing an error-prone custom toolchain file.

Most users won't need BORING_BSSL_EXTERNAL_TOOLCHAIN, but some
packages (such as Homebrew package
messense/macos-cross-toolchains/x86_64-unknown-linux-gnu) don't
install the sysroot at the root of the GCC installation, so clang-12
cannot find crt1.o and crti.o.

Finally, we also set up CMAKE_CROSSCOMPILING and
CMAKE_{C,CXX,ASM}_COMPILER_TARGET to make cross compilation work
with compilers that have cross-compiling drivers (i.e. clang).

We can now cross build boring-sys from macOS to Linux with
fips feature turned on:

brew tap nox/misc
brew install llvm@12.0.0
export PATH="$(brew --prefix llvm@12.0.0)/bin:$PATH"

brew tap messense/macos-cross-toolchains
brew install x86_64-unknown-linux-gnu
export BORING_BSSL_FIPS_EXTERNAL_TOOLCHAIN="$(brew --prefix x86_64-unknown-linux-gnu)/toolchain"
export BORING_BSSL_FIPS_SYSROOT="$BORING_BSSL_FIPS_EXTERNAL_TOOLCHAIN/x86_unknown-linux-gnu/sysroot"

cargo build --target x86_64-unknown-linux-gnu -p boring-sys --features fips
2023-10-26 11:31:47 +01:00
Anthony Ramine
ba0ea33ab4 Check for CMAKE_TOOLCHAIN_FILE when creating cmake config
We don't do anything fancy anymore for non-cross builds
and when a specific CMAKE_TOOLCHAIN_FILE is specified.
2023-10-26 11:31:47 +01:00
Anthony Ramine
80b97c8318 Introduce target-specific env vars
The logic is stolen from cmake-rs, and it is important to
follow it as we will need to look for CMAKE_TOOLCHAIN_FILE
the same way cmake-rs does.

When checking for env variable BORING_BSSL_PATH during a
cross build for target x86_64-unknown-linux-gnu, boring-sys
build script will attempt to read:

  BORING_BSSL_PATH_x86_64-unknown-linux-gnu
  BORING_BSSL_PATH_x86_64_unknown_linux_gnu
  TARGET_BORING_BSSL_PATH
  BORING_BSSL_PATH
2023-10-26 11:31:47 +01:00
Anthony Ramine
d8c2122c73 Continue looping if candidate cxx isn't found in verify_fips_clang_version
A basic LLVM 12 build provides clang-12 but not clang++-12, but
it does provide both clang and clang++, so we shouldn't hard fail
when first checking for clang-12 and clang++-12.
2023-10-26 11:31:47 +01:00
Anthony Ramine
1ca7f76607 Introduce set_async_get_session_callback 2023-10-25 10:25:55 +01:00
Anthony Ramine
8a26577b5d Allow returning GetSessionPendingError from get session callbacks 2023-10-25 10:25:55 +01:00
Anthony Ramine
1e2a4812d2 Test set_get_session_callback 2023-10-25 10:25:55 +01:00
Anthony Ramine
965fde7bae Test new session callback on server side 2023-10-25 10:25:55 +01:00
Anthony Ramine
c48ed2ef62 Move session tests to their own module 2023-10-25 10:25:55 +01:00
Anthony Ramine
9a0bd94f99 Replace feature no-patches with BORING_BSSL{,_FIPS}_ASSUME_PATCHED
Feature no-patches is ever only useful when setting other env variables
BORING_BSSL{,_FIPS}{,_SOURCE}_PATH, and it has no impact on the APIs
provided by any of the boring crates, so we may as well make it an env
variable itself so downstream users have less features to propagate
across their own crate graph.
2023-10-23 15:06:23 +02:00
Anthony Ramine
f5f47dd5a7 Remove feature rpk from hyper-boring and tokio-boring
Feature rpk in boring doesn't do anything unless you
explicitly use `SslAcceptor::rpk` or `SslContext::rpk_builder`,
and neither of these types are directly reachable if the
user depends only on tokio-boring or hyper-boring, which
means you still need to explicitly depend on the boring crate
to use RPK, in which case you can enable the feature there.
2023-10-23 15:06:23 +02:00
Anthony Ramine
7ddb106c20 Introduce a new set of env variables for FIPS-like builds
Builds using feature fips or fips-link-precompiled now
read variables prefixed by BORING_BSSL_FIPS_ instead of
BORING_BSSL_. This helps complex builds where build dependencies
also use boring, where we may not want to use fips there.

Without those separate variables, the boring build for the
build dependencies end up relying on e.g. BORING_BSSL_PATH,
causing errors if this path is a boring checkout intended for
fips builds, while the fips feature isn't enabled for
the build dependency.
2023-10-23 12:28:12 +02:00
Anthony Ramine
bc095478fc Use prefix BORING_BSSL_ for all boringssl env variables
This means BORING_SSL_PRECOMPILED_BCM_O is now
BORING_BSSL_PRECOMPILED_BCM_O.

Prefix BORING_BSSL_ has been chosen because that's the
one that is used the most among all the variables
the build script uses.
2023-10-23 12:28:12 +02:00
Anthony Ramine
ebea825d18 Don't read BORINGSSL_BUILD_DIR anymore
It's actually unused.
2023-10-23 12:28:12 +02:00
Anthony Ramine
6b52c1e93c Don't use env::current_dir in build script
The current directory from a build script executed by cargo
is always the manifest dir, so we may as well only use
the manifest dir.
2023-10-23 12:28:12 +02:00
Anthony Ramine
0d25d74cd6 Introduce struct Config in build script
Using a struct improves navigation of the build script,
as we can rely on rust-analyzer to help us check how
a feature flag or an environment variable is used,
as opposed to grepping for multiple env::var calls
or #[cfg] attributes.

This commit also removes some obsolete blocks of code
related to the now defunct ndk-old-gcc and fuzzing features.
2023-10-23 12:28:12 +02:00
Chris Eager
8245063ae6 Update CI to build all matrix targets
This adds the matrix `--target` to relevant `cargo` commands. Some
targets are now “check-only”, which confirms cross-compilation succeeds,
without doing a full test run, because running these tests may require
emulation.

musl and MinGW have been dropped, because musl has no standard C++
setup, and MinGW isn’t a target for us.

All this requires some adjustments to boring-sys:

- Blocklist max_align_t in bindgen
   - https://github.com/rust-lang/rust-bindgen/issues/1823
- Don't check for MSVC with target_env
   - x86_64-pc-windows-gnu is identified as `target_env = "msvc"` too,
but doesn't use the Visual Studio CMake generator.
2023-10-18 15:50:51 -05:00
Anthony Ramine
8d4822be72 Fix clippy lints 2023-10-12 15:27:55 -07:00
Jordan Rose
02e4a3f9aa Merge upstream release 3.1.0 into libsignal branch 2023-10-12 15:19:22 -07:00
Jordan Rose
3c50b28a61 Remove Catalyst support 2023-10-10 16:14:12 -07:00
Jordan Rose
3809a7e1ca Update to a more recent BoringSSL 2023-05-08 16:39:30 -07:00
Jordan Rose
9534656269
Merge branch 'cloudflare:master' into libsignal 2023-05-08 15:29:47 -07:00
Lauren N. Liberda
bdf9d2ac06 upgrade bindgen build-dependency to 0.62.0
fixes build error on too new clang: https://github.com/cloudflare/boring/issues/109
2023-04-24 09:55:14 -07:00
Jordan Rose
25e1dd8fa5 boring-sys: Update Android NDK sysroot path
This longer path (inside the prebuilt toolchain included in the NDK)
has been the preferred sysroot since NDK r19. Newer NDKs no longer
have a top-level "sysroot" directory at all.
2022-11-30 15:32:47 -08:00
morph027
73ee9d7085 boring-sys: Add support for Linux ARM(v7)
Signed-off-by: morph027 <stefan.heitmueller@gmx.com>
2022-11-10 14:17:08 -08:00
Jordan Rose
81eeaab77a Merge remote-tracking branch 'origin/master' into libsignal 2022-11-10 14:16:43 -08:00
Jordan Rose
b95cb545b9 boring-sys: Add an android-api-19 feature for compat with Android 4.4 2022-08-22 12:40:38 -07:00
Jordan Rose
e1c719c096 boring-sys: Always treat macOS targets as a cross-compile, like iOS
This fixes actual cross-compile cases, like compiling for Apple
Silicon on an Intel Mac. It also makes builds more consistent by using
the SDK in the installed Xcode instead of the host root headers, which
is how Apple recommends people build anyway. (If you only have the
command line tools installed, though, it should fall back to those
without a problem.)
2022-07-29 17:42:43 -07:00
Jordan Rose
8a2d686286 boring: Expose PKey::private_key_to_der_pkcs8(_passphrase) 2022-07-28 16:28:31 -07:00
Jordan Rose
8e6f19aa9a boring-sys: Use the Android NDK sysroot when running bindgen 2022-07-25 15:04:01 -07:00
Jordan Rose
879ffef1b5 boring: Expose X509VerifyParamRef::set_time 2022-07-19 15:29:21 -07:00
Jordan Rose
a04d27b0c4 Put BoringSSL's SSL/TLS support behind a new feature 'ssl'
BoringSSL vends two libraries: libcrypto and libssl. Some clients
don't need the libssl part. With a feature flag, we can skip building
it (saving on compile time) and initializing it (saving on code size
and a tiny bit of run time).

The new feature is on by default for backwards compatibility.
2022-07-12 14:56:46 -07:00
Ravi Khadiwala
7451d82bb2 boring: use the right no_name value for oid_string 2022-07-05 14:57:27 -05:00
Chris Eager
b73043e51b boring: add Asn1Object::oid_string 2022-07-05 14:57:27 -05:00
Chris Eager
7afa7798cf boring: add X509Ref::extensions
This corresponds to `X509_get0_extensions`.
2022-07-05 14:57:27 -05:00
Ravi Khadiwala
29b7ed4cbd boring: add bindings to validate against CRLs
Adds CRL types and the ability to verify certificates against CRLs with
an X509StoreContext
2022-07-01 14:35:24 -05:00
Ravi Khadiwala
1e2df380d9 Enable CI on pushes to libsignal 2022-07-01 14:15:42 -05:00
Ravi Khadiwala
2dbdb97351 boring: Fix memory leak in Deriver 2022-07-01 12:30:42 -05:00
Jordan Rose
ea59de3542 Add minimal cross-compilation support for Windows and AArch64 Linux
Cross-compiling to AArch64 Linux can be done with a CMake toolchain
file, along with setting the correct compiler and include paths in the
environment.

Cross-compiling from X64 Windows to ARM64 Windows doesn't look at the
toolchain at all, because CMake + Visual Studio can already
cross-compile. Unfortunately, the Visual Studio CMake generator
doesn't set CMAKE_SYSTEM_PROCESSOR, which is what the BoringSSL
CMakeLists.txt is looking at to choose the architecture. For now,
disable the use of assembly when cross-compiling on Windows (assuming
that the Visual Studio generator will be used there).
2022-06-27 17:57:53 -07:00
Jordan Rose
ceb140105b Add support for Mac Catalyst (on Intel and Apple Silicon chips)
This is a Tier 3 target, but apart from working around a hardcoded
version in the 'cc' crate everything does work.
2022-06-16 12:30:46 -07:00
116 changed files with 13027 additions and 11671 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@ -3,61 +3,76 @@ name: CI
on:
pull_request:
branches:
- master
- main
push:
branches:
- master
- main
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
jobs:
rustfmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable && rustup default stable
- uses: actions/checkout@v4
- name: Install Rustfmt
run: rustup default stable && rustup component add rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
clippy:
name: clippy
runs-on: ubuntu-latest
env:
CARGO_HOME: ${{ github.workspace }}/.cache/cargo
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust
run: rustup update stable && rustup default stable
run: rustup toolchain add stable --no-self-update --component clippy && rustup default stable
- name: Get rust version
id: rust-version
run: echo "::set-output name=version::$(rustc --version)"
shell: bash
run: |
echo "version=$(rustc --version)" >> $GITHUB_OUTPUT
- name: Cache cargo index
uses: actions/cache@v1
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@v1
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@v1
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 -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards
env:
CARGO_BUILD_RUSTDOCFLAGS: "--cfg=docsrs"
RUSTC_BOOTSTRAP: 1
DOCS_RS: 1
- name: Cargo.toml boring versions consistency
shell: bash
run: |
WORKSPACE_VERSION=$(grep -F '[workspace.package]' -A1 Cargo.toml | grep -F version | grep -Eo '".*"')
if [[ -z "$WORKSPACE_VERSION" ]]; then echo 2>&1 "error: can't find boring version"; exit 1; fi
if grep -E 'boring.* =' Cargo.toml | grep -vF "$WORKSPACE_VERSION"; then
echo 2>&1 "error: boring dependencies must match workspace version $WORKSPACE_VERSION"
exit 1
fi
test:
name: Test
runs-on: ${{ matrix.os }}
@ -65,7 +80,7 @@ jobs:
matrix:
thing:
- stable
- macos-x86_64
- i686-mingw
- arm-android
- arm64-android
- i686-android
@ -76,85 +91,210 @@ jobs:
- i686-linux
- arm-linux
- aarch64-linux
- x86_64-musl
- arm64-macos
- x86_64-macos
- x86_64-mingw
- i686-msvc
- x86_64-msvc
include:
- check_only: false
- extra_test_args: ''
- apt_packages: ''
- custom_env: {}
- thing: stable
target: x86_64-unknown-linux-gnu
rust: stable
os: ubuntu-latest
- thing: macos-x86_64
target: x86_64-apple-darwin
rust: stable
os: macos-latest
- thing: arm-android
target: arm-linux-androideabi
target: armv7-linux-androideabi
rust: stable
os: ubuntu-latest
check_only: true
- thing: arm64-android
target: aarch64-linux-android
rust: stable
os: ubuntu-latest
check_only: true
- thing: i686-android
target: i686-linux-android
rust: stable
os: ubuntu-latest
check_only: true
custom_env:
CXXFLAGS: -msse2
- thing: x86_64-android
target: x86_64-linux-android
rust: stable
os: ubuntu-latest
check_only: true
- thing: aarch64-ios
target: aarch64-apple-ios
os: macos-latest
check_only: true
custom_env:
IPHONEOS_DEPLOYMENT_TARGET: 17.5
# It's... theoretically possible to run tests on iPhone Simulator,
# but for now, make sure that BoringSSL only builds.
- thing: aarch64-ios-sim
target: aarch64-apple-ios-sim
os: macos-latest
check_only: true
custom_env:
IPHONEOS_DEPLOYMENT_TARGET: 17.5
- thing: x86_64-ios
target: x86_64-apple-ios
os: macos-latest
check_only: true
custom_env:
IPHONEOS_DEPLOYMENT_TARGET: 17.5
- thing: i686-linux
target: i686-unknown-linux-gnu
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
os: ubuntu-latest
apt_packages: gcc-arm-linux-gnueabi g++-arm-linux-gnueabi
check_only: true
custom_env:
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
rust: stable
os: ubuntu-latest
- thing: x86_64-musl
target: x86_64-unknown-linux-musl
apt_packages: crossbuild-essential-arm64
check_only: true
custom_env:
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
rust: stable
os: ubuntu-latest
os: macos-latest
check_only: true
- thing: x86_64-macos
target: x86_64-apple-darwin
rust: stable
os: macos-latest
- thing: x86_64-mingw
target: x86_64-pc-windows-gnu
rust: stable
os: ubuntu-latest
os: windows-latest
check_only: true # tests are flaky for unclear reasons
custom_env:
CC: gcc
CXX: g++
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 -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 -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 -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@v2
- 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
- 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 }}
shell: bash
- run: rustup target add ${{ matrix.target }}
- name: Install nasm
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
@ -164,18 +304,38 @@ jobs:
- name: Set LIBCLANG_PATH
if: startsWith(matrix.os, 'windows')
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
- if: "startsWith(matrix.os, 'windows') && !contains(matrix.target, 'ios')"
# CI's Windows doesn't have require root certs
run: cargo test --workspace --exclude tokio-boring --exclude hyper-boring
name: Run tests (Windows)
- if: "!startsWith(matrix.os, 'windows') && !contains(matrix.target, 'ios')"
run: cargo test
name: Run tests (not Windows)
- if: "contains(matrix.target, 'ios')"
# It's... theoretically possible to run tests on iPhone Simulator,
# but for now, make sure that BoringSSL only builds.
run: cargo check --target ${{ matrix.target }} --all-targets
name: Check tests (iOS)
- 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: Fetch deps
run: cargo fetch --target ${{ matrix.target }}
shell: bash
env: ${{ matrix.custom_env }}
# 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
env: ${{ matrix.custom_env }}
- name: Test boring-sys cargo publish
# Running `cargo publish --dry-run` tests two things:
#
@ -187,28 +347,26 @@ 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
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- name: Install Clang-12
uses: KyleMayes/install-llvm-action@v1
- name: Install golang
uses: actions/setup-go@v5
with:
version: "12.0.0"
directory: ${{ runner.temp }}/llvm
- name: Add clang++-12 link
working-directory: ${{ runner.temp }}/llvm/bin
run: ln -s clang clang++-12
- run: cargo test --features fips
name: Run tests
go-version: '>=1.22.0'
- name: Run tests
run: cargo test --features fips
- name: Test boring-sys cargo publish (FIPS)
# Running `cargo publish --dry-run` tests two things:
#
@ -222,21 +380,58 @@ jobs:
# submodules to a new revision, so it's important to test this on CI.
run: cargo publish --dry-run -p boring-sys --features fips
cross-build:
name: Cross build from macOS to Linux
runs-on: macos-latest
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust (rustup)
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
run: echo "BORING_BSSL_SYSROOT=$(brew --prefix ${{ matrix.target }})/toolchain/${{ matrix.target }}/sysroot" >> $GITHUB_ENV
shell: bash
- name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER
run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV
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-20.04
runs-on: ubuntu-latest
env:
CARGO_INCREMENTAL: 1
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- 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 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 underscore-wildcards
name: Run `underscore-wildcards` tests
- run: cargo test --features rpk,underscore-wildcards
name: Run `rpk,underscore-wildcards` tests
- run: cargo test --features rpk,underscore-wildcards,mlkem
name: Run `rpk,underscore-wildcards` tests

23
.github/workflows/semgrep.yml vendored Normal file
View File

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

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,30 +8,43 @@ members = [
resolver = "2"
[workspace.package]
version = "3.1.0"
version = "5.0.2"
rust-version = "1.85"
repository = "https://github.com/cloudflare/boring"
edition = "2021"
[workspace.dependencies]
boring-sys = { version = "3", path = "./boring-sys" }
boring = { version = "3", path = "./boring" }
tokio-boring = { version = "3", path = "./tokio-boring" }
[workspace.metadata.release]
pre-release-commit-message = "Release {{version}}"
shared-version = true
tag-prefix = ""
publish = false
bindgen = { version = "0.68.1", default-features = false, features = ["runtime"] }
cmake = "0.1.18"
[workspace.dependencies]
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"
brotli = "8.0"
bytes = "1"
cmake = "0.1.54"
fs_extra = "1.3.0"
fslock = "0.2"
bitflags = "2.4"
foreign-types = "0.5"
libc = "0.2"
hex = "0.4"
rusty-hook = "^0.11"
futures = "0.3"
tokio = { version = "1", features = ["full"] }
tokio = "1"
anyhow = "1"
antidote = "1.0.0"
http = "0.2"
hyper = { version = "0.14", default-features = false, features = ["full"] }
http = "1"
http-body-util = "0.1.2"
hyper = "1"
hyper-util = "0.1.6"
linked_hash_set = "0.1"
once_cell = "1.0"
openssl-macros = "0.1.1"
tower = "0.4"
tower-layer = "0.3"
tower-service = "0.3"

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

File diff suppressed because it is too large Load Diff

View File

@ -9,77 +9,71 @@ description = "FFI bindings to BoringSSL"
repository = { workspace = true }
documentation = "https://docs.rs/boring-sys"
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 = { workspace = true }
include = [
"/*.md",
"/*.toml",
"/LICENSE-MIT",
"/cmake/*.cmake",
# boringssl (non-FIPS)
"/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/**/*.[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.rs",
"/build/*",
"/src",
"/patches",
]
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental"]
features = ["rpk", "underscore-wildcards", "mlkem"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
# Use a FIPS-validated version of boringssl.
# Compile boringssl using the FIPS build flag if building boringssl from
# scratch.
#
# See
# https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md
# for instructions and more details on the boringssl FIPS flag.
fips = []
# 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_SOURCE_PATH`.
pq-experimental = []
# Require mlkem.h
mlkem = []
# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, but
# keeps the related Rust API.
#
# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL_PATH` env variable) or
# with custom BoringSSL sources (via `BORING_BSSL_SOURCE_PATH` env variable) already containing
# required patches.
no-patches = []
# Applies a patch (`patches/underscore-wildcards.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
# (`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]
bindgen = { workspace = true }
cmake = { workspace = true }
fs_extra = { workspace = true }
fslock = { workspace = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(const_fn)'] }

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

@ -1,743 +0,0 @@
use fslock::LockFile;
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::Once;
// NOTE: this build script is adopted from quiche (https://github.com/cloudflare/quiche)
// Additional parameters for Android build of BoringSSL.
//
// Android NDK < 18 with GCC.
const CMAKE_PARAMS_ANDROID_NDK_OLD_GCC: &[(&str, &[(&str, &str)])] = &[
(
"aarch64",
&[("ANDROID_TOOLCHAIN_NAME", "aarch64-linux-android-4.9")],
),
(
"arm",
&[("ANDROID_TOOLCHAIN_NAME", "arm-linux-androideabi-4.9")],
),
(
"x86",
&[("ANDROID_TOOLCHAIN_NAME", "x86-linux-android-4.9")],
),
(
"x86_64",
&[("ANDROID_TOOLCHAIN_NAME", "x86_64-linux-android-4.9")],
),
];
// Android NDK >= 19.
const CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[
("aarch64", &[("ANDROID_ABI", "arm64-v8a")]),
("arm", &[("ANDROID_ABI", "armeabi-v7a")]),
("x86", &[("ANDROID_ABI", "x86")]),
("x86_64", &[("ANDROID_ABI", "x86_64")]),
];
fn cmake_params_android() -> &'static [(&'static str, &'static str)] {
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let cmake_params_android = if cfg!(feature = "ndk-old-gcc") {
CMAKE_PARAMS_ANDROID_NDK_OLD_GCC
} else {
CMAKE_PARAMS_ANDROID_NDK
};
for (android_arch, params) in cmake_params_android {
if *android_arch == arch {
return params;
}
}
&[]
}
const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
// iOS
(
"aarch64-apple-ios",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphoneos"),
],
),
(
"aarch64-apple-ios-sim",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
],
),
(
"x86_64-apple-ios",
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
],
),
// macOS
(
"aarch64-apple-darwin",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "macosx"),
],
),
(
"x86_64-apple-darwin",
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "macosx"),
],
),
];
fn cmake_params_apple() -> &'static [(&'static str, &'static str)] {
let target = env::var("TARGET").unwrap();
for (next_target, params) in CMAKE_PARAMS_APPLE {
if *next_target == target {
return params;
}
}
&[]
}
fn get_apple_sdk_name() -> &'static str {
for (name, value) in cmake_params_apple() {
if *name == "CMAKE_OSX_SYSROOT" {
return value;
}
}
let target = env::var("TARGET").unwrap();
panic!("cannot find SDK for {} in CMAKE_PARAMS_APPLE", target);
}
/// Returns an absolute path to the BoringSSL source.
fn get_boringssl_source_path() -> String {
#[cfg(feature = "fips")]
const SUBMODULE_DIR: &str = "boringssl-fips";
#[cfg(not(feature = "fips"))]
const SUBMODULE_DIR: &str = "boringssl";
static COPY_SOURCES: Once = Once::new();
if let Ok(src_path) = env::var("BORING_BSSL_SOURCE_PATH") {
return src_path;
}
let out_dir = env::var("OUT_DIR").unwrap();
let src_path = Path::new(&out_dir).join(SUBMODULE_DIR);
COPY_SOURCES.call_once(|| {
let submodule_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("deps")
.join(SUBMODULE_DIR);
if !submodule_path.join("CMakeLists.txt").exists() {
println!("cargo:warning=fetching boringssl git submodule");
run_command(Command::new("git").args([
"submodule",
"update",
"--init",
"--recursive",
&submodule_path.display().to_string(),
]))
.unwrap();
}
let _ = fs::remove_dir_all(&src_path);
fs_extra::dir::copy(submodule_path, &out_dir, &Default::default()).unwrap();
// NOTE: .git can be both file and dir, depening on whether it was copied from a submodule
// or created by the patches code.
let src_git_path = src_path.join(".git");
let _ = fs::remove_file(&src_git_path);
let _ = fs::remove_dir_all(&src_git_path);
});
src_path.display().to_string()
}
/// Returns the platform-specific output path for lib.
///
/// 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() -> String {
if cfg!(target_env = "msvc") {
// Code under this branch should match the logic in cmake-rs
let debug_env_var = env::var("DEBUG").expect("DEBUG variable not defined in env");
let deb_info = match &debug_env_var[..] {
"false" => false,
"true" => true,
unknown => panic!("Unknown DEBUG={} env var.", unknown),
};
let opt_env_var = env::var("OPT_LEVEL").expect("OPT_LEVEL variable not defined in env");
let subdir = match &opt_env_var[..] {
"0" => "Debug",
"1" | "2" | "3" => {
if deb_info {
"RelWithDebInfo"
} else {
"Release"
}
}
"s" | "z" => "MinSizeRel",
unknown => panic!("Unknown OPT_LEVEL={} env var.", unknown),
};
subdir.to_string()
} else {
"".to_string()
}
}
/// Returns a new cmake::Config for building BoringSSL.
///
/// It will add platform-specific parameters if needed.
fn get_boringssl_cmake_config() -> cmake::Config {
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let host = env::var("HOST").unwrap();
let target = env::var("TARGET").unwrap();
let pwd = std::env::current_dir().unwrap();
let src_path = get_boringssl_source_path();
let mut boringssl_cmake = cmake::Config::new(&src_path);
if host != target {
// Add platform-specific parameters for cross-compilation.
match os.as_ref() {
"android" => {
// We need ANDROID_NDK_HOME to be set properly.
println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME");
let android_ndk_home = env::var("ANDROID_NDK_HOME")
.expect("Please set ANDROID_NDK_HOME for Android build");
let android_ndk_home = std::path::Path::new(&android_ndk_home);
for (name, value) in cmake_params_android() {
eprintln!("android arch={} add {}={}", arch, name, value);
boringssl_cmake.define(name, value);
}
let toolchain_file = android_ndk_home.join("build/cmake/android.toolchain.cmake");
let toolchain_file = toolchain_file.to_str().unwrap();
eprintln!("android toolchain={}", toolchain_file);
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");
}
"macos" => {
for (name, value) in cmake_params_apple() {
eprintln!("macos arch={} add {}={}", arch, name, value);
boringssl_cmake.define(name, value);
}
}
"ios" => {
for (name, value) in cmake_params_apple() {
eprintln!("ios arch={} add {}={}", arch, name, value);
boringssl_cmake.define(name, value);
}
// Bitcode is always on.
let bitcode_cflag = "-fembed-bitcode";
// Hack for Xcode 10.1.
let target_cflag = if arch == "x86_64" {
"-target x86_64-apple-ios-simulator"
} else {
""
};
let cflag = format!("{} {}", bitcode_cflag, target_cflag);
boringssl_cmake.define("CMAKE_ASM_FLAGS", &cflag);
boringssl_cmake.cflag(&cflag);
}
"windows" => {
if host.contains("windows") {
// BoringSSL's CMakeLists.txt isn't set up for cross-compiling using Visual Studio.
// Disable assembly support so that it at least builds.
boringssl_cmake.define("OPENSSL_NO_ASM", "YES");
}
}
"linux" => match arch.as_str() {
"x86" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
pwd.join(&src_path)
.join("src/util/32-bit-toolchain.cmake")
.as_os_str(),
);
}
"aarch64" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
pwd.join("cmake/aarch64-linux.cmake").as_os_str(),
);
}
"arm" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
pwd.join("cmake/armv7-linux.cmake").as_os_str(),
);
}
_ => {
eprintln!(
"warning: no toolchain file configured by boring-sys for {}",
target
);
}
},
_ => {}
}
}
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) -> String {
let output = match Command::new(tool).arg("--version").output() {
Ok(o) => o,
Err(e) => {
eprintln!("warning: missing {}, trying other compilers: {}", tool, e);
// NOTE: hard-codes that the loop below checks the version
return String::new();
}
};
if !output.status.success() {
return String::new();
}
let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output");
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 cc_version = version(cc);
if cc_version.contains(REQUIRED_CLANG_VERSION) {
assert!(
version(cxx).contains(REQUIRED_CLANG_VERSION),
"mismatched versions of cc and c++"
);
return (cc, cxx);
} else if cc == "cc" {
panic!(
"unsupported clang version \"{}\": FIPS requires clang {}",
cc_version, REQUIRED_CLANG_VERSION
);
} else if !cc_version.is_empty() {
eprintln!(
"warning: FIPS requires clang version {}, skipping incompatible version \"{}\"",
REQUIRED_CLANG_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.
// https://developer.android.com/ndk/guides/other_build_systems
for known_toolchain in ["linux-x86_64", "darwin-x86_64", "windows-x86_64"] {
if let Some(toolchain) = toolchains
.iter()
.find(|entry| entry.file_name() == known_toolchain)
{
return Ok(toolchain.file_name());
}
}
// Then fall back to any subdirectory, in case Google has added support for a new host.
// (Maybe there's a linux-aarch64 toolchain now.)
if let Some(toolchain) = toolchains
.into_iter()
.find(|entry| entry.file_type().map(|ty| ty.is_dir()).unwrap_or(false))
{
return Ok(toolchain.file_name());
}
// Finally give up.
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no subdirectories at given path",
))
}
fn get_extra_clang_args_for_bindgen() -> Vec<String> {
let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let mut params = Vec::new();
// Add platform-specific parameters.
#[allow(clippy::single_match)]
match os.as_ref() {
"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();
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() {
eprintln!("xcrun failed: exit code {}", exit_code);
} else {
eprintln!("xcrun failed: killed");
}
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 android_ndk_home = env::var("ANDROID_NDK_HOME")
.expect("Please set ANDROID_NDK_HOME for Android build");
let mut android_sysroot = std::path::PathBuf::from(android_ndk_home);
android_sysroot.extend(["toolchains", "llvm", "prebuilt"]);
let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) {
Ok(toolchain) => toolchain,
Err(e) => {
eprintln!(
"warning: failed to find prebuilt Android NDK toolchain for bindgen: {}",
e
);
// Uh... let's try anyway, I guess?
return params;
}
};
android_sysroot.push(toolchain);
android_sysroot.push("sysroot");
params.push("--sysroot".to_string());
// If ANDROID_NDK_HOME weren't a valid UTF-8 string,
// we'd already know from env::var.
params.push(android_sysroot.into_os_string().into_string().unwrap());
}
_ => {}
}
params
}
fn ensure_patches_applied() -> io::Result<()> {
let out_dir = env::var("OUT_DIR").unwrap();
let mut lock_file = LockFile::open(&PathBuf::from(&out_dir).join(".patch_lock"))?;
let src_path = get_boringssl_source_path();
let has_git = Path::new(&src_path).join(".git").exists();
lock_file.lock()?;
// NOTE: init git in the copied files, so we can apply patches
if !has_git {
run_command(Command::new("git").args(["init"]).current_dir(&src_path))?;
}
if cfg!(feature = "pq-experimental") {
println!("cargo:warning=applying experimental post quantum crypto patch to boringssl");
apply_patch("boring-pq.patch")?;
}
if cfg!(feature = "rpk") {
println!("cargo:warning=applying RPK patch to boringssl");
apply_patch("rpk.patch")?;
}
Ok(())
}
fn apply_patch(patch_name: &str) -> io::Result<()> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let src_path = get_boringssl_source_path();
let cmd_path = manifest_dir
.join("patches")
.join(patch_name)
.canonicalize()?;
run_command(
Command::new("git")
.args([
"apply",
"-v",
"--whitespace=fix",
&cmd_path.display().to_string(),
])
.current_dir(src_path),
)?;
Ok(())
}
fn run_command(command: &mut Command) -> io::Result<Output> {
let out = command.output()?;
println!("{}", std::str::from_utf8(&out.stdout).unwrap());
eprintln!("{}", std::str::from_utf8(&out.stderr).unwrap());
if !out.status.success() {
let err = match out.status.code() {
Some(code) => format!("{:?} exited with status: {}", command, code),
None => format!("{:?} was terminated by signal", command),
};
return Err(io::Error::new(io::ErrorKind::Other, err));
}
Ok(out)
}
fn build_boring_from_sources() -> String {
if cfg!(feature = "no-patches") {
println!(
"cargo:warning=skipping git patches application, provided\
native BoringSSL is expected to have the patches included"
);
} else {
ensure_patches_applied().unwrap();
}
let mut cfg = get_boringssl_cmake_config();
if cfg!(feature = "fuzzing") {
cfg.cxxflag("-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE")
.cxxflag("-DBORINGSSL_UNSAFE_FUZZER_MODE");
}
if cfg!(feature = "fips") {
let (clang, clangxx) = verify_fips_clang_version();
cfg.define("CMAKE_C_COMPILER", clang);
cfg.define("CMAKE_CXX_COMPILER", clangxx);
cfg.define("CMAKE_ASM_COMPILER", clang);
cfg.define("FIPS", "1");
}
if cfg!(feature = "fips-link-precompiled") {
cfg.define("FIPS", "1");
}
cfg.build_target("ssl").build();
cfg.build_target("crypto").build().display().to_string()
}
fn link_in_precompiled_bcm_o(bssl_dir: &str) {
println!("cargo:warning=linking in precompiled `bcm.o` module");
let bcm_o_src_path = env::var("BORING_SSL_PRECOMPILED_BCM_O")
.expect("`fips-link-precompiled` requires `BORING_SSL_PRECOMPILED_BCM_O` env variable to be specified");
let libcrypto_path = PathBuf::from(bssl_dir)
.join("build/crypto/libcrypto.a")
.canonicalize()
.unwrap()
.display()
.to_string();
let bcm_o_dst_path = PathBuf::from(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").args(["t", &libcrypto_path, "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",
&libcrypto_path,
bcm_o_dst_path.display().to_string().as_str(),
]))
.unwrap();
}
fn check_feature_compatibility() {
#[cfg(all(feature = "fips", feature = "rpk"))]
compile_error!("`fips` and `rpk` features are mutually exclusive");
let no_patches_enabled = cfg!(feature = "no-patches");
let is_external_native_lib_source =
env::var("BORING_BSSL_PATH").is_err() && env::var("BORING_BSSL_SOURCE_PATH").is_err();
if no_patches_enabled && is_external_native_lib_source {
panic!(
"`no-patches` feature is supposed to be used with `BORING_BSSL_PATH`\
or `BORING_BSSL_SOURCE_PATH` env variables"
)
}
let features_with_patches_enabled = cfg!(any(feature = "rpk", feature = "pq-experimental"));
let patches_required = features_with_patches_enabled && !no_patches_enabled;
let build_from_sources_required = cfg!(feature = "fips-link-precompiled") || patches_required;
let is_precompiled_native_lib = env::var("BORING_BSSL_PATH").is_ok();
if is_precompiled_native_lib && build_from_sources_required {
panic!("precompiled BoringSSL was provided, so FIPS configuration or optional patches can't be applied");
}
}
fn main() {
println!("cargo:rerun-if-env-changed=BORING_BSSL_PATH");
println!("cargo:rerun-if-env-changed=BORING_BSSL_INCLUDE_PATH");
println!("cargo:rerun-if-env-changed=BORING_BSSL_SOURCE_PATH");
println!("cargo:rerun-if-env-changed=BORING_SSL_PRECOMPILED_BCM_O");
println!("cargo:rerun-if-env-changed=BORINGSSL_BUILD_DIR");
check_feature_compatibility();
let bssl_dir = env::var("BORING_BSSL_PATH").unwrap_or_else(|_| build_boring_from_sources());
let build_path = get_boringssl_platform_output_path();
if cfg!(any(feature = "fips", feature = "fips-link-precompiled")) {
println!(
"cargo:rustc-link-search=native={}/build/crypto/{}",
bssl_dir, build_path
);
println!(
"cargo:rustc-link-search=native={}/build/ssl/{}",
bssl_dir, build_path
);
println!(
"cargo:rustc-link-search=native={}/lib/{}",
bssl_dir, build_path
);
} else {
println!(
"cargo:rustc-link-search=native={}/build/{}",
bssl_dir, build_path
);
}
if cfg!(feature = "fips-link-precompiled") {
link_in_precompiled_bcm_o(&bssl_dir);
}
println!("cargo:rustc-link-lib=static=crypto");
println!("cargo:rustc-link-lib=static=ssl");
let include_path = env::var("BORING_BSSL_INCLUDE_PATH").unwrap_or_else(|_| {
if let Ok(bssl_path) = env::var("BORING_BSSL_PATH") {
return format!("{}/include", bssl_path);
}
let src_path = get_boringssl_source_path();
if Path::new(&src_path).join("include").exists() {
format!("{}/include", &src_path)
} else {
format!("{}/src/include", &src_path)
}
});
let mut builder = bindgen::Builder::default()
.derive_copy(true)
.derive_debug(true)
.derive_default(true)
.derive_eq(true)
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
})
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.generate_comments(true)
.fit_macro_constants(false)
.size_t_is_usize(true)
.layout_tests(true)
.prepend_enum_name(true)
.clang_args(get_extra_clang_args_for_bindgen())
.clang_args(&["-I", &include_path]);
let target = env::var("TARGET").unwrap();
match target.as_ref() {
// bindgen produces alignment tests that cause undefined behavior [1]
// when applied to explicitly unaligned types like OSUnalignedU64.
//
// There is no way to disable these tests for only some types
// and it's not nice to suppress warnings for the entire crate,
// so let's disable all alignment tests and hope for the best.
//
// [1]: https://github.com/rust-lang/rust-bindgen/issues/1651
"aarch64-apple-ios" | "aarch64-apple-ios-sim" => {
builder = builder.layout_tests(false);
}
_ => {}
}
let headers = [
"aes.h",
"asn1_mac.h",
"asn1t.h",
"blake2.h",
"blowfish.h",
"cast.h",
"chacha.h",
"cmac.h",
"cpu.h",
"curve25519.h",
"des.h",
"dtls1.h",
"hkdf.h",
"hmac.h",
"hrss.h",
"md4.h",
"md5.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(
Path::new(&include_path)
.join("openssl")
.join(header)
.to_str()
.unwrap(),
);
}
let bindings = builder.generate().expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

191
boring-sys/build/config.rs Normal file
View File

@ -0,0 +1,191 @@
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
pub(crate) struct Config {
pub(crate) manifest_dir: PathBuf,
pub(crate) out_dir: PathBuf,
pub(crate) is_bazel: bool,
pub(crate) host: String,
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) rpk: bool,
pub(crate) underscore_wildcards: bool,
}
pub(crate) struct Env {
pub(crate) path: Option<PathBuf>,
pub(crate) include_path: Option<PathBuf>,
pub(crate) source_path: Option<PathBuf>,
pub(crate) assume_patched: bool,
pub(crate) sysroot: Option<PathBuf>,
pub(crate) compiler_external_toolchain: Option<PathBuf>,
pub(crate) debug: Option<OsString>,
pub(crate) opt_level: Option<OsString>,
pub(crate) android_ndk_home: Option<PathBuf>,
pub(crate) cmake_toolchain_file: Option<PathBuf>,
pub(crate) cpp_runtime_lib: Option<OsString>,
pub(crate) docs_rs: bool,
}
impl Config {
pub(crate) fn from_env() -> Self {
let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap().into();
let out_dir = env::var_os("OUT_DIR").unwrap().into();
let host = env::var("HOST").unwrap();
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());
let is_bazel = env
.source_path
.as_ref()
.is_some_and(|path| path.join("src").exists());
let config = Self {
manifest_dir,
out_dir,
is_bazel,
host,
target,
target_arch,
target_os,
unix,
target_env,
target_features,
features,
env,
};
config.check_feature_compatibility();
config
}
fn check_feature_compatibility(&self) {
if self.features.fips && self.features.rpk {
panic!("`fips` 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();
if self.env.assume_patched && is_external_native_lib_source {
panic!(
"`BORING_BSSL_{{,_FIPS}}_ASSUME_PATCHED` env variable is supposed to be used with\
`BORING_BSSL{{,_FIPS}}_PATH` or `BORING_BSSL{{,_FIPS}}_SOURCE_PATH` env variables"
);
}
let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards;
let patches_required = features_with_patches_enabled && !self.env.assume_patched;
if is_precompiled_native_lib && patches_required {
println!(
"cargo:warning=precompiled BoringSSL was provided, so patches will be ignored"
);
}
}
}
impl Features {
fn from_env() -> Self {
let fips = env::var_os("CARGO_FEATURE_FIPS").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,
rpk,
underscore_wildcards,
}
}
pub(crate) fn is_fips_like(&self) -> bool {
self.fips
}
}
impl Env {
fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self {
let target_with_underscores = target.replace('-', "_");
// 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!("{kind}_{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(BORING_BSSL_PREFIX));
let non_fips = target_var(name);
if is_fips_like {
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 {
non_fips
}
};
Self {
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), // 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"),
docs_rs: var("DOCS_RS").is_some(),
}
}
}
fn var(name: &str) -> Option<OsString> {
println!("cargo:rerun-if-env-changed={name}");
env::var_os(name)
}

767
boring-sys/build/main.rs Normal file
View File

@ -0,0 +1,767 @@
use fslock::LockFile;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::OnceLock;
use crate::config::Config;
mod config;
fn should_use_cmake_cross_compilation(config: &Config) -> bool {
if config.host == config.target {
return false;
}
match config.target_os.as_str() {
"macos" | "ios" => {
// Cross-compiling for Apple platforms on macOS is supported using the normal Xcode
// tools, along with the settings from `cmake_params_apple`.
!config.host.ends_with("-darwin")
}
_ => true,
}
}
// Android NDK >= 19.
const CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[
("aarch64", &[("ANDROID_ABI", "arm64-v8a")]),
("arm", &[("ANDROID_ABI", "armeabi-v7a")]),
("x86", &[("ANDROID_ABI", "x86")]),
("x86_64", &[("ANDROID_ABI", "x86_64")]),
];
fn cmake_params_android(config: &Config) -> &'static [(&'static str, &'static str)] {
for (android_arch, params) in CMAKE_PARAMS_ANDROID_NDK {
if *android_arch == config.target_arch {
return params;
}
}
&[]
}
const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
// iOS
(
"aarch64-apple-ios",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphoneos"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
(
"aarch64-apple-ios-sim",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
(
"x86_64-apple-ios",
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
("CMAKE_MACOSX_BUNDLE", "OFF"),
],
),
// macOS
(
"aarch64-apple-darwin",
&[
("CMAKE_OSX_ARCHITECTURES", "arm64"),
("CMAKE_OSX_SYSROOT", "macosx"),
],
),
(
"x86_64-apple-darwin",
&[
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
("CMAKE_OSX_SYSROOT", "macosx"),
],
),
];
fn cmake_params_apple(config: &Config) -> &'static [(&'static str, &'static str)] {
for (next_target, params) in CMAKE_PARAMS_APPLE {
if *next_target == config.target {
return params;
}
}
&[]
}
fn get_apple_sdk_name(config: &Config) -> &'static str {
for (name, value) in cmake_params_apple(config) {
if *name == "CMAKE_OSX_SYSROOT" {
return value;
}
}
panic!(
"cannot find SDK for {} in CMAKE_PARAMS_APPLE",
config.target
);
}
/// Returns an absolute path to the BoringSSL source.
fn get_boringssl_source_path(config: &Config) -> &Path {
static SOURCE_PATH: OnceLock<PathBuf> = OnceLock::new();
SOURCE_PATH.get_or_init(|| {
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);
let submodule_path = config.manifest_dir.join("deps").join(submodule_dir);
if !submodule_path.join("CMakeLists.txt").exists() {
println!("cargo:warning=fetching boringssl git submodule");
run_command(
Command::new("git")
.args(["submodule", "update", "--init", "--recursive"])
.arg(&submodule_path),
)
.expect("git submodule update");
}
let _ = fs::remove_dir_all(&src_path);
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.
let src_git_path = src_path.join(".git");
let _ = fs::remove_file(&src_git_path);
let _ = fs::remove_dir_all(&src_git_path);
src_path
})
}
/// Returns the platform-specific output path for lib.
///
/// 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 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
.env
.debug
.as_ref()
.expect("DEBUG variable not defined in env");
let deb_info = match debug_env_var.to_str() {
Some("false") => false,
Some("true") => true,
_ => panic!("Unknown DEBUG={debug_env_var:?} env var."),
};
let opt_env_var = config
.env
.opt_level
.as_ref()
.expect("OPT_LEVEL variable not defined in env");
let subdir = match opt_env_var.to_str() {
Some("0") => "Debug",
Some("1" | "2" | "3") => {
if deb_info {
"RelWithDebInfo"
} else {
"Release"
}
}
Some("s" | "z") => "MinSizeRel",
_ => panic!("Unknown OPT_LEVEL={opt_env_var:?} env var."),
};
Some(subdir)
} else {
None
}
}
/// Returns a new `cmake::Config` for building BoringSSL.
///
/// It will add platform-specific parameters if needed.
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.env.cmake_toolchain_file.is_some() {
return boringssl_cmake;
}
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;
}
if should_use_cmake_cross_compilation(config) {
boringssl_cmake
.define("CMAKE_CROSSCOMPILING", "true")
.define("CMAKE_C_COMPILER_TARGET", &config.target)
.define("CMAKE_CXX_COMPILER_TARGET", &config.target)
.define("CMAKE_ASM_COMPILER_TARGET", &config.target);
}
if let Some(sysroot) = &config.env.sysroot {
boringssl_cmake.define("CMAKE_SYSROOT", sysroot);
}
if let Some(toolchain) = &config.env.compiler_external_toolchain {
boringssl_cmake
.define("CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN", toolchain)
.define("CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN", toolchain)
.define("CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN", toolchain);
}
// Add platform-specific parameters for cross-compilation.
match &*config.target_os {
"android" => {
// We need ANDROID_NDK_HOME to be set properly.
let android_ndk_home = config
.env
.android_ndk_home
.as_ref()
.expect("Please set ANDROID_NDK_HOME for Android build");
for (name, value) in cmake_params_android(config) {
eprintln!("android arch={} add {}={}", config.target_arch, name, value);
boringssl_cmake.define(name, value);
}
let toolchain_file = android_ndk_home.join("build/cmake/android.toolchain.cmake");
let toolchain_file = toolchain_file.to_str().unwrap();
eprintln!("android toolchain={toolchain_file}");
boringssl_cmake.define("CMAKE_TOOLCHAIN_FILE", toolchain_file);
// 21 is the minimum level tested. You can give higher value.
boringssl_cmake.define("CMAKE_SYSTEM_VERSION", "21");
boringssl_cmake.define("CMAKE_ANDROID_STL_TYPE", "c++_shared");
}
"macos" => {
for (name, value) in cmake_params_apple(config) {
eprintln!("macos arch={} add {}={}", config.target_arch, name, value);
boringssl_cmake.define(name, value);
}
}
"ios" => {
for (name, value) in cmake_params_apple(config) {
eprintln!("ios arch={} add {}={}", config.target_arch, name, value);
boringssl_cmake.define(name, value);
}
// Bitcode is always on.
let bitcode_cflag = "-fembed-bitcode";
// Hack for Xcode 10.1.
let target_cflag = if config.target_arch == "x86_64" {
"-target x86_64-apple-ios-simulator"
} else {
""
};
let cflag = format!("{bitcode_cflag} {target_cflag}");
boringssl_cmake.define("CMAKE_ASM_FLAGS", &cflag);
boringssl_cmake.cflag(&cflag);
}
"windows" => {
if config.host.contains("windows") {
// BoringSSL's CMakeLists.txt isn't set up for cross-compiling using Visual Studio.
// Disable assembly support so that it at least builds.
boringssl_cmake.define("OPENSSL_NO_ASM", "YES");
}
}
"linux" => match &*config.target_arch {
"x86" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
// `src_path` can be a path relative to the manifest dir, but
// cmake hates that.
config
.manifest_dir
.join(src_path)
.join("util/32-bit-toolchain.cmake")
.as_os_str(),
);
}
"aarch64" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
config
.manifest_dir
.join("cmake/aarch64-linux.cmake")
.as_os_str(),
);
}
"arm" => {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
config
.manifest_dir
.join("cmake/armv7-linux.cmake")
.as_os_str(),
);
}
_ => {
println!(
"cargo:warning=no toolchain file configured by boring-sys for {}",
config.target
);
}
},
_ => {}
}
boringssl_cmake
}
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.
// https://developer.android.com/ndk/guides/other_build_systems
for known_toolchain in ["linux-x86_64", "darwin-x86_64", "windows-x86_64"] {
if let Some(toolchain) = toolchains
.iter()
.find(|entry| entry.file_name() == known_toolchain)
{
return Ok(toolchain.file_name());
}
}
// Then fall back to any subdirectory, in case Google has added support for a new host.
// (Maybe there's a linux-aarch64 toolchain now.)
if let Some(toolchain) = toolchains
.into_iter()
.find(|entry| entry.file_type().map(|ty| ty.is_dir()).unwrap_or(false))
{
return Ok(toolchain.file_name());
}
// Finally give up.
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no subdirectories at given path",
))
}
fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
let mut params = Vec::new();
// Add platform-specific parameters.
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);
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?
}
}
}
"android" => {
let mut android_sysroot = config
.env
.android_ndk_home
.clone()
.expect("Please set ANDROID_NDK_HOME for Android build");
android_sysroot.extend(["toolchains", "llvm", "prebuilt"]);
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());
}
Err(e) => {
println!("cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}");
// Uh... let's try anyway, I guess?
}
}
}
_ => {}
}
params
}
fn ensure_patches_applied(config: &Config) -> io::Result<()> {
if config.env.assume_patched || config.env.path.is_some() {
println!(
"cargo:warning=skipping git patches application, provided\
native BoringSSL is expected to have the patches included"
);
return Ok(());
} else if config.env.source_path.is_some()
&& (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, underscore-wildcards"
);
}
let mut lock_file = LockFile::open(&config.out_dir.join(".patch_lock"))?;
let src_path = get_boringssl_source_path(config);
let has_git = src_path.join(".git").exists();
lock_file.lock()?;
// NOTE: init git in the copied files, so we can apply patches
if !has_git {
run_command(Command::new("git").arg("init").current_dir(src_path))?;
}
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");
apply_patch(config, "rpk.patch")?;
}
if config.features.underscore_wildcards {
println!("cargo:warning=applying underscore wildcards patch to boringssl");
apply_patch(config, "underscore-wildcards.patch")?;
}
Ok(())
}
fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> {
let src_path = get_boringssl_source_path(config);
#[cfg(not(windows))]
let cmd_path = config
.manifest_dir
.join("patches")
.join(patch_name)
.canonicalize()?;
#[cfg(windows)]
let cmd_path = config.manifest_dir.join("patches").join(patch_name);
let mut args = vec!["apply", "-v", "--whitespace=fix"];
// non-bazel versions of BoringSSL have no src/ dir
if config.is_bazel {
args.push("-p2");
}
run_command(
Command::new("git")
.args(&args)
.arg(cmd_path)
.current_dir(src_path),
)?;
Ok(())
}
fn run_command(command: &mut Command) -> io::Result<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(),
),
)
})?;
std::io::stderr().write_all(&out.stderr)?;
std::io::stdout().write_all(&out.stdout)?;
if !out.status.success() {
let err = match out.status.code() {
Some(code) => format!("{command:?} exited with status: {code}"),
None => format!("{command:?} was terminated by signal"),
};
return Err(io::Error::other(err));
}
Ok(out)
}
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(|| {
std::thread::available_parallelism()
.ok()
.map(|t| t.to_string())
});
if let Some(num_jobs) = num_jobs {
cfg.env("CMAKE_BUILD_PARALLEL_LEVEL", num_jobs);
}
if config.features.fips {
cfg.define("CMAKE_C_COMPILER", "clang")
.define("CMAKE_CXX_COMPILER", "clang++")
.define("CMAKE_ASM_COMPILER", "clang")
.define("FIPS", "1");
}
cfg.build_target("ssl").build();
let path = cfg.build_target("crypto").build();
let build_dir = path.join("build");
if build_dir.exists() {
build_dir
} else {
path
}
})
}
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();
}
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,
}
}
fn main() {
let config = Config::from_env();
ensure_patches_applied(&config).unwrap();
if !config.env.docs_rs {
emit_link_directives(&config);
}
generate_bindings(&config);
}
fn emit_link_directives(config: &Config) {
let bssl_dir = build_boringssl_or_get_prebuilt(config);
let msvc_lib_subdir = msvc_lib_subdir(config);
let subdirs =
if config.is_bazel || (config.features.is_fips_like() && config.env.path.is_some()) {
&["lib"][..]
} else {
&["lib", "crypto", "ssl", ""][..]
};
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) {
println!("cargo:rustc-link-lib={cpp_lib}");
}
println!("cargo:rustc-link-lib=static=crypto");
println!("cargo:rustc-link-lib=static=ssl");
if config.target_os == "windows" {
// Rust 1.87.0 compat - https://github.com/rust-lang/rust/pull/138233
println!("cargo:rustc-link-lib=advapi32");
}
}
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 check_include_path(bssl_path.join("include"))
.expect("config has invalid include path");
}
let src_path = get_boringssl_source_path(config);
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 =
bindgen::RustTarget::stable(77, 0).expect("bindgen does not recognize target rust version");
let mut builder = bindgen::Builder::default()
.rust_target(target_rust_version) // bindgen MSRV is 1.70, so this is enough
.derive_copy(true)
.derive_debug(true)
.derive_default(true)
.derive_eq(false)
.derive_partialeq(false)
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
})
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.generate_comments(true)
.fit_macro_constants(false)
.size_t_is_usize(true)
.layout_tests(config.env.debug.is_some())
.prepend_enum_name(true)
.blocklist_type("max_align_t") // Not supported by bindgen on all targets, not used by BoringSSL
.clang_args(get_extra_clang_args_for_bindgen(config))
.clang_arg("-I")
.clang_arg(include_path.display().to_string());
if let Some(sysroot) = &config.env.sysroot {
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 must_have_headers = [
"aes.h",
"asn1_mac.h",
"asn1t.h",
"blake2.h",
"blowfish.h",
"cast.h",
"chacha.h",
"cmac.h",
"cpu.h",
"curve25519.h",
"des.h",
"dtls1.h",
"err.h",
"hkdf.h",
"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",
"rand.h",
"rc4.h",
"ripemd.h",
"siphash.h",
"srtp.h",
"trust_token.h",
];
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");
let mut source_code = Vec::new();
bindings
.write(Box::new(&mut source_code))
.expect("Couldn't serialize bindings!");
ensure_err_lib_enum_is_named(&mut source_code);
fs::write(config.out_dir.join("bindings.rs"), source_code).expect("Couldn't write bindings!");
}
/// err.h has anonymous `enum { ERR_LIB_NONE = 1 }`, which makes a dodgy `_bindgen_ty_1` name
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))
.unwrap_or("_bindgen_ty_1");
source_code.extend_from_slice(
format!("\n/// Newtype for [`ERR_LIB_SSL`] constants\npub use {enum_type} as ErrLib;\n")
.as_bytes(),
);
}

@ -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

@ -0,0 +1,79 @@
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
---
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) {
- if (!OPENSSL_isalnum(*p) && *p != '-') {
+ if (!OPENSSL_isalnum(*p) && *p != '-' &&
+ !(*p == '_' &&
+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) {
return 0;
}
}
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,
},
+
+ // Underscores in DNS names are forbidden by default.
+ {
+ /*cert_subject=*/{},
+ /*cert_dns_names=*/{"*.example.com"},
+ /*cert_emails=*/{},
+ /*valid_dns_names=*/{},
+ /*invalid_dns_names=*/{"not_allowed.example.com"},
+ /*valid_emails=*/{},
+ /*invalid_emails=*/{},
+ /*flags=*/0,
+ },
+
+ // Underscores in DNS names can be allowed with the right flag.
+ {
+ /*cert_subject=*/{},
+ /*cert_dns_names=*/{"*.example.com"},
+ /*cert_emails=*/{},
+ /*valid_dns_names=*/{"now_allowed.example.com"},
+ /*invalid_dns_names=*/{},
+ /*valid_emails=*/{},
+ /*invalid_emails=*/{},
+ /*flags=*/X509_CHECK_FLAG_UNDERSCORE_WILDCARDS,
+ },
+
};
size_t i = 0;
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
+// 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

@ -10,16 +10,31 @@
non_upper_case_globals,
unused_imports
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::convert::TryInto;
use std::ffi::c_void;
use std::os::raw::{c_char, c_int, c_uint, c_ulong};
#[allow(clippy::useless_transmute, clippy::derive_partial_eq_without_eq)]
#[allow(
clippy::useless_transmute,
clippy::derive_partial_eq_without_eq,
clippy::ptr_offset_with_cast,
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")]
@ -27,57 +42,28 @@ pub type BN_ULONG = u64;
#[cfg(target_pointer_width = "32")]
pub type BN_ULONG = u32;
#[cfg(const_fn)]
macro_rules! const_fn {
($(pub const fn $name:ident($($arg:ident: $t:ty),*) -> $ret:ty $b:block)*) => {
$(
pub const fn $name($($arg: $t),*) -> $ret $b
)*
}
#[must_use]
pub const fn ERR_PACK(l: c_int, f: c_int, r: c_int) -> c_ulong {
((l as c_ulong & 0x0FF) << 24) | ((f as c_ulong & 0xFFF) << 12) | (r as c_ulong & 0xFFF)
}
#[cfg(not(const_fn))]
macro_rules! const_fn {
($(pub const fn $name:ident($($arg:ident: $t:ty),*) -> $ret:ty $b:block)*) => {
$(
pub fn $name($($arg: $t),*) -> $ret $b
)*
}
#[must_use]
pub const fn ERR_GET_LIB(l: c_uint) -> c_int {
((l >> 24) & 0x0FF) as c_int
}
const_fn! {
pub const fn ERR_PACK(l: c_int, f: c_int, r: c_int) -> c_ulong {
((l as c_ulong & 0x0FF) << 24) |
((f as c_ulong & 0xFFF) << 12) |
(r as c_ulong & 0xFFF)
}
#[must_use]
pub const fn ERR_GET_FUNC(l: c_uint) -> c_int {
((l >> 12) & 0xFFF) as c_int
}
pub const fn ERR_GET_LIB(l: c_uint) -> c_int {
((l >> 24) & 0x0FF) as c_int
}
pub const fn ERR_GET_FUNC(l: c_uint) -> c_int {
((l >> 12) & 0xFFF) as c_int
}
pub const fn ERR_GET_REASON(l: c_uint) -> c_int {
(l & 0xFFF) as c_int
}
#[must_use]
pub const fn ERR_GET_REASON(l: c_uint) -> c_int {
(l & 0xFFF) as c_int
}
pub fn init() {
use std::ptr;
use std::sync::Once;
// explicitly initialize to work around https://github.com/openssl/openssl/issues/3505
static INIT: Once = Once::new();
let init_options = OPENSSL_INIT_LOAD_SSL_STRINGS;
INIT.call_once(|| {
assert_eq!(
unsafe { OPENSSL_init_ssl(init_options.try_into().unwrap(), ptr::null_mut()) },
1
)
});
unsafe {
CRYPTO_library_init();
}
}

View File

@ -7,70 +7,60 @@ 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 = { workspace = true }
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental"]
features = ["rpk", "underscore-wildcards"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
# Controlling the build
default = []
# Use a FIPS-validated version of boringssl.
# Use a FIPS-validated version of BoringSSL.
fips = ["boring-sys/fips"]
# Link with precompiled FIPS-validated `bcm.o` module.
fips-link-precompiled = ["boring-sys/fips-link-precompiled"]
# **DO NOT USE** This will be removed without warning in future releases.
legacy-compat-deprecated = []
# **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 = []
# 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)
rpk = ["boring-sys/rpk"]
# Applies a patch to the boringSSL source code that enables support for PQ key
# exchange. This feature is necessary in order to compile the bindings for the
# 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_SOURCE_PATH`.
pq-experimental = ["boring-sys/pq-experimental"]
# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
rpk = ["credential", "boring-sys/rpk"]
# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, but
# keeps the related Rust API.
#
# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL_PATH` env variable) or
# with custom BoringSSL sources (via `BORING_BSSL_SOURCE_PATH` env variable) already containing
# required patches.
no-patches = ["boring-sys/no-patches"]
# 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 }
foreign-types = { workspace = true }
once_cell = { workspace = true }
openssl-macros = { workspace = true }
libc = { workspace = true }
boring-sys = { workspace = true }
[dev-dependencies]
hex = { workspace = true }
rusty-hook = { workspace = true }
brotli = { 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();
@ -146,8 +148,8 @@ fn real_main() -> Result<(), ErrorStack> {
// Verify that this cert was issued by this ca
match ca_cert.issued(&cert) {
Ok(()) => println!("Certificate verified!"),
Err(ver_err) => println!("Failed to verify certificate: {}", ver_err),
};
Err(ver_err) => println!("Failed to verify certificate: {ver_err}"),
}
Ok(())
}
@ -155,6 +157,6 @@ fn real_main() -> Result<(), ErrorStack> {
fn main() {
match real_main() {
Ok(()) => println!("Finished."),
Err(e) => println!("Error: {}", e),
};
Err(e) => println!("Error: {e}"),
}
}

View File

@ -38,7 +38,8 @@
//! ```
//!
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;
@ -55,15 +56,15 @@ impl AesKey {
/// # Failure
///
/// Returns an error if the key is not 128, 192, or 256 bits.
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[corresponds(AES_set_encrypt_key)]
pub fn new_encrypt(key: &[u8]) -> Result<AesKey, KeyError> {
unsafe {
assert!(key.len() <= c_int::max_value() as usize / 8);
assert!(key.len() <= c_int::MAX as usize / 8);
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 {
@ -79,15 +80,15 @@ impl AesKey {
/// # Failure
///
/// Returns an error if the key is not 128, 192, or 256 bits.
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
#[corresponds(AES_set_decrypt_key)]
pub fn new_decrypt(key: &[u8]) -> Result<AesKey, KeyError> {
unsafe {
assert!(key.len() <= c_int::max_value() as usize / 8);
assert!(key.len() <= c_int::MAX as usize / 8);
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(),
);
@ -113,6 +114,7 @@ impl AesKey {
///
/// Panics if either `out` or `in_` do not have sizes that are a multiple of 8, or if
/// `out` is not 8 bytes longer than `in_`
#[corresponds(AES_wrap_key)]
pub fn wrap_key(
key: &AesKey,
iv: Option<[u8; 8]>,
@ -123,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(()))
@ -151,6 +152,7 @@ pub fn wrap_key(
///
/// Panics if either `out` or `in_` do not have sizes that are a multiple of 8, or
/// if `in_` is not 8 bytes longer than `out`
#[corresponds(AES_unwrap_key)]
pub fn unwrap_key(
key: &AesKey,
iv: Option<[u8; 8]>,
@ -161,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;
@ -41,6 +41,7 @@ use crate::nid::Nid;
use crate::stack::Stackable;
use crate::string::OpensslString;
use crate::{cvt, cvt_p};
use openssl_macros::corresponds;
foreign_type_and_impl_send_sync! {
type CType = ffi::ASN1_GENERALIZEDTIME;
@ -62,20 +63,19 @@ foreign_type_and_impl_send_sync! {
impl fmt::Display for Asn1GeneralizedTimeRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
unsafe {
let mem_bio = match MemBio::new() {
Err(_) => return f.write_str("error"),
Ok(m) => m,
};
let print_result = cvt(ffi::ASN1_GENERALIZEDTIME_print(
mem_bio.as_ptr(),
self.as_ptr(),
));
match print_result {
Err(_) => f.write_str("error"),
Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())),
}
}
let bio = MemBio::new().ok();
let msg = bio
.as_ref()
.and_then(|mem_bio| unsafe {
cvt(ffi::ASN1_GENERALIZEDTIME_print(
mem_bio.as_ptr(),
self.as_ptr(),
))
.ok()?;
str::from_utf8(mem_bio.get_buf()).ok()
})
.unwrap_or("error");
f.write_str(msg)
}
}
@ -142,11 +142,13 @@ impl Asn1Type {
pub const BMPSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_BMPSTRING);
/// Constructs an `Asn1Type` from a raw OpenSSL value.
#[must_use]
pub fn from_raw(value: c_int) -> Self {
Asn1Type(value)
}
/// Returns the raw OpenSSL value represented by this type.
#[must_use]
pub fn as_raw(&self) -> c_int {
self.0
}
@ -187,10 +189,7 @@ foreign_type_and_impl_send_sync! {
impl Asn1TimeRef {
/// Find difference between two times
///
/// This corresponds to [`ASN1_TIME_diff`].
///
/// [`ASN1_TIME_diff`]: https://www.openssl.org/docs/man1.1.0/crypto/ASN1_TIME_diff.html
#[corresponds(ASN1_TIME_diff)]
pub fn diff(&self, compare: &Self) -> Result<TimeDiff, ErrorStack> {
let mut days = 0;
let mut secs = 0;
@ -205,12 +204,7 @@ impl Asn1TimeRef {
}
/// Compare two times
///
/// This corresponds to [`ASN1_TIME_compare`] but is implemented using [`diff`] so that it is
/// also supported on older versions of OpenSSL.
///
/// [`ASN1_TIME_compare`]: https://www.openssl.org/docs/man1.1.1/man3/ASN1_TIME_compare.html
/// [`diff`]: struct.Asn1TimeRef.html#method.diff
#[corresponds(ASN1_TIME_compare)]
pub fn compare(&self, other: &Self) -> Result<Ordering, ErrorStack> {
let d = self.diff(other)?;
if d.days > 0 || d.secs > 0 {
@ -240,7 +234,7 @@ impl PartialEq<Asn1Time> for Asn1TimeRef {
}
}
impl<'a> PartialEq<Asn1Time> for &'a Asn1TimeRef {
impl PartialEq<Asn1Time> for &Asn1TimeRef {
fn eq(&self, other: &Asn1Time) -> bool {
self.diff(other)
.map(|t| t.days == 0 && t.secs == 0)
@ -260,7 +254,7 @@ impl PartialOrd<Asn1Time> for Asn1TimeRef {
}
}
impl<'a> PartialOrd<Asn1Time> for &'a Asn1TimeRef {
impl PartialOrd<Asn1Time> for &Asn1TimeRef {
fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> {
self.compare(other).ok()
}
@ -289,6 +283,7 @@ impl fmt::Debug for Asn1TimeRef {
}
impl Asn1Time {
#[corresponds(ASN1_TIME_new)]
fn new() -> Result<Asn1Time, ErrorStack> {
ffi::init();
@ -298,6 +293,7 @@ impl Asn1Time {
}
}
#[corresponds(X509_gmtime_adj)]
fn from_period(period: c_long) -> Result<Asn1Time, ErrorStack> {
ffi::init();
@ -309,28 +305,30 @@ impl Asn1Time {
/// Creates a new time on specified interval in days from now
pub fn days_from_now(days: u32) -> Result<Asn1Time, ErrorStack> {
Asn1Time::from_period(days as c_long * 60 * 60 * 24)
// the type varies between platforms, so both into() and try_into() trigger Clippy lints
Self::from_period((days * 60 * 60 * 24) as _)
}
/// Creates a new time from the specified `time_t` value
#[corresponds(ASN1_TIME_set)]
pub fn from_unix(time: time_t) -> Result<Asn1Time, ErrorStack> {
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))
}
}
/// Creates a new time corresponding to the specified ASN1 time string.
///
/// This corresponds to [`ASN1_TIME_set_string`].
///
/// [`ASN1_TIME_set_string`]: https://www.openssl.org/docs/manmaster/man3/ASN1_TIME_set_string.html
#[corresponds(ASN1_TIME_set_string)]
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Asn1Time, ErrorStack> {
unsafe {
let s = CString::new(s).unwrap();
let s = CString::new(s).map_err(ErrorStack::internal_error)?;
let time = Asn1Time::new()?;
cvt(ffi::ASN1_TIME_set_string(time.as_ptr(), s.as_ptr()))?;
@ -401,6 +399,7 @@ impl Asn1StringRef {
/// ASN.1 strings may utilize UTF-16, ASCII, BMP, or UTF8. This is important to
/// consume the string in a meaningful way without knowing the underlying
/// format.
#[corresponds(ASN1_STRING_to_UTF8)]
pub fn as_utf8(&self) -> Result<OpensslString, ErrorStack> {
unsafe {
let mut ptr = ptr::null_mut();
@ -409,7 +408,7 @@ impl Asn1StringRef {
return Err(ErrorStack::get());
}
Ok(OpensslString::from_ptr(ptr as *mut c_char))
Ok(OpensslString::from_ptr(ptr.cast()))
}
}
@ -419,16 +418,21 @@ impl Asn1StringRef {
/// strings in rust, it is preferable to use [`as_utf8`]
///
/// [`as_utf8`]: struct.Asn1String.html#method.as_utf8
#[corresponds(ASN1_STRING_get0_data)]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr()), self.len()) }
}
/// Returns the number of bytes in the string.
#[corresponds(ASN1_STRING_length)]
#[must_use]
pub fn len(&self) -> usize {
unsafe { ffi::ASN1_STRING_length(self.as_ptr()) as usize }
}
/// Determines if the string is empty.
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
@ -476,15 +480,13 @@ impl Asn1IntegerRef {
#[allow(clippy::unnecessary_cast)]
#[allow(missing_docs)]
#[deprecated(since = "0.10.6", note = "use to_bn instead")]
#[must_use]
pub fn get(&self) -> i64 {
unsafe { crate::ffi::ASN1_INTEGER_get(self.as_ptr()) as i64 }
}
/// Converts the integer to a `BigNum`.
///
/// This corresponds to [`ASN1_INTEGER_to_BN`].
///
/// [`ASN1_INTEGER_to_BN`]: https://www.openssl.org/docs/man1.1.0/crypto/ASN1_INTEGER_get.html
#[corresponds(ASN1_INTEGER_to_BN)]
pub fn to_bn(&self) -> Result<BigNum, ErrorStack> {
unsafe {
cvt_p(crate::ffi::ASN1_INTEGER_to_BN(
@ -498,12 +500,15 @@ impl Asn1IntegerRef {
/// Sets the ASN.1 value to the value of a signed 32-bit integer, for larger numbers
/// see [`bn`].
///
/// OpenSSL documentation at [`ASN1_INTEGER_set`]
///
/// [`bn`]: ../bn/struct.BigNumRef.html#method.to_asn1_integer
/// [`ASN1_INTEGER_set`]: https://www.openssl.org/docs/man1.1.0/crypto/ASN1_INTEGER_set.html
#[corresponds(ASN1_INTEGER_set)]
pub fn set(&mut self, value: i32) -> Result<(), ErrorStack> {
unsafe { cvt(crate::ffi::ASN1_INTEGER_set(self.as_ptr(), value as c_long)).map(|_| ()) }
unsafe {
cvt(crate::ffi::ASN1_INTEGER_set(
self.as_ptr(),
c_long::from(value),
))
}
}
}
@ -521,16 +526,34 @@ foreign_type_and_impl_send_sync! {
impl Asn1BitStringRef {
/// Returns the Asn1BitString as a slice.
#[corresponds(ASN1_STRING_get0_data)]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr() as *mut _), self.len()) }
unsafe {
let ptr = ASN1_STRING_get0_data(self.as_ptr().cast());
if ptr.is_null() {
return &[];
}
slice::from_raw_parts(ptr, self.len())
}
}
/// Returns the Asn1BitString as a str, if possible.
#[corresponds(ASN1_STRING_get0_data)]
#[must_use]
pub fn to_str(&self) -> Option<&str> {
str::from_utf8(self.as_slice()).ok()
}
/// Returns the number of bytes in the string.
#[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.
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
@ -561,18 +584,14 @@ impl Stackable for Asn1Object {
}
impl Asn1Object {
/// Constructs an ASN.1 Object Identifier from a string representation of
/// the OID.
///
/// This corresponds to [`OBJ_txt2obj`].
///
/// [`OBJ_txt2obj`]: https://www.openssl.org/docs/man1.1.0/man3/OBJ_txt2obj.html
/// Constructs an ASN.1 Object Identifier from a string representation of the OID.
#[corresponds(OBJ_txt2obj)]
#[allow(clippy::should_implement_trait)]
pub fn from_str(txt: &str) -> Result<Asn1Object, ErrorStack> {
unsafe {
ffi::init();
let txt = CString::new(txt).unwrap();
let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr() as *const _, 0))?;
let txt = CString::new(txt).map_err(ErrorStack::internal_error)?;
let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr(), 0))?;
Ok(Asn1Object::from_ptr(obj))
}
}
@ -580,26 +599,38 @@ impl Asn1Object {
impl Asn1ObjectRef {
/// Returns the NID associated with this OID.
#[must_use]
pub fn nid(&self) -> Nid {
unsafe { Nid::from_raw(ffi::OBJ_obj2nid(self.as_ptr())) }
}
/// Returns the numerical string OID of this object.
///
/// This corresponds to [`OBJ_obj2txt`] with `no_name = 1`.
///
/// [`OBJ_obj2txt`]: https://www.openssl.org/docs/man1.1.1/man3/OBJ_obj2txt.html
pub fn oid_string(&self) -> String {
self.to_text(true)
}
// 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 = [0u8; 80];
let len = ffi::OBJ_obj2txt(
buf.as_mut_ptr().cast(),
buf.len() as c_int,
self.as_ptr(),
no_name as c_int,
);
String::from_utf8_lossy(&buf[..len as usize]).into_owned()
}
}
}
impl fmt::Display for Asn1ObjectRef {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
unsafe {
let mut buf = [0; 80];
let len = ffi::OBJ_obj2txt(
buf.as_mut_ptr() as *mut _,
buf.len() as c_int,
self.as_ptr(),
0,
);
match str::from_utf8(&buf[..len as usize]) {
Err(_) => fmt.write_str("error"),
Ok(s) => fmt.write_str(s),
}
}
fmt.write_str(&self.to_text(false))
}
}
@ -702,4 +733,13 @@ mod tests {
.map(|object| object.to_string())
.expect_err("parsing invalid OID should fail");
}
#[test]
fn object_to_text() {
let oid = "2.16.840.1.101.3.4.2.1";
let object = Asn1Object::from_str(oid).unwrap();
assert_eq!(object.to_text(false), Nid::SHA256.long_name().unwrap());
assert_eq!(object.to_text(true), oid.to_string());
assert_eq!(object.oid_string(), oid.to_string());
}
}

View File

@ -3,18 +3,17 @@ use crate::cvt_n;
use crate::error::ErrorStack;
use crate::ffi;
use libc::c_int;
use openssl_macros::corresponds;
/// Encodes a slice of bytes to a base64 string.
///
/// This corresponds to [`EVP_EncodeBlock`].
///
/// # Panics
///
/// Panics if the input length or computed output length overflow a signed C integer.
///
/// [`EVP_EncodeBlock`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DecodeBlock.html
#[corresponds(EVP_EncodeBlock)]
#[must_use]
pub fn encode_block(src: &[u8]) -> String {
assert!(src.len() <= c_int::max_value() as usize);
assert!(src.len() <= c_int::MAX as usize);
let src_len = src.len();
let len = encoded_len(src_len).unwrap();
@ -33,13 +32,10 @@ pub fn encode_block(src: &[u8]) -> String {
/// Decodes a base64-encoded string to bytes.
///
/// This corresponds to [`EVP_DecodeBlock`].
///
/// # Panics
///
/// Panics if the input length or computed output length overflow a signed C integer.
///
/// [`EVP_DecodeBlock`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DecodeBlock.html
#[corresponds(EVP_DecodeBlock)]
pub fn decode_block(src: &str) -> Result<Vec<u8>, ErrorStack> {
let src = src.trim();
@ -48,7 +44,7 @@ pub fn decode_block(src: &str) -> Result<Vec<u8>, ErrorStack> {
return Ok(vec![]);
}
assert!(src.len() <= c_int::max_value() as usize);
assert!(src.len() <= c_int::MAX as usize);
let src_len = src.len();
let len = decoded_len(src_len).unwrap();
@ -106,7 +102,7 @@ mod tests {
#[test]
fn test_encode_block() {
assert_eq!("".to_string(), encode_block(b""));
assert_eq!(String::new(), encode_block(b""));
assert_eq!("Zg==".to_string(), encode_block(b"f"));
assert_eq!("Zm8=".to_string(), encode_block(b"fo"));
assert_eq!("Zm9v".to_string(), encode_block(b"foo"));

View File

@ -1,15 +1,16 @@
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]>);
impl<'a> Drop for MemBioSlice<'a> {
impl Drop for MemBioSlice<'_> {
fn drop(&mut self) {
unsafe {
ffi::BIO_free_all(self.0);
@ -19,20 +20,9 @@ impl<'a> Drop for MemBioSlice<'a> {
impl<'a> MemBioSlice<'a> {
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
#[cfg(not(any(feature = "fips", feature = "fips-link-precompiled")))]
type BufLen = isize;
#[cfg(any(feature = "fips", feature = "fips-link-precompiled"))]
type BufLen = libc::c_int;
ffi::init();
assert!(buf.len() <= BufLen::max_value() 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,10 @@ impl MemBio {
unsafe {
let mut ptr = ptr::null_mut();
let len = ffi::BIO_get_mem_data(self.0, &mut ptr);
slice::from_raw_parts(ptr as *const _ as *const _, len as usize)
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};
@ -35,6 +35,7 @@ use crate::error::ErrorStack;
use crate::ffi::BN_is_negative;
use crate::string::OpensslString;
use crate::{cvt, cvt_n, cvt_p};
use openssl_macros::corresponds;
/// Options for the most significant bits of a randomly generated `BigNum`.
pub struct MsbOption(c_int);
@ -69,10 +70,7 @@ foreign_type_and_impl_send_sync! {
impl BigNumContext {
/// Returns a new `BigNumContext`.
///
/// See OpenSSL documentation at [`BN_CTX_new`].
///
/// [`BN_CTX_new`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_CTX_new.html
#[corresponds(BN_CTX_new)]
pub fn new() -> Result<BigNumContext, ErrorStack> {
unsafe {
ffi::init();
@ -91,7 +89,7 @@ foreign_type_and_impl_send_sync! {
/// with [`new`]. Perform standard mathematics on large numbers using
/// methods from [`Dref<Target = BigNumRef>`]
///
/// OpenSSL documenation at [`BN_new`].
/// OpenSSL documentation at [`BN_new`].
///
/// [`new`]: struct.BigNum.html#method.new
/// [`Dref<Target = BigNumRef>`]: struct.BigNum.html#deref-methods
@ -115,51 +113,36 @@ impl BigNumRef {
/// Erases the memory used by this `BigNum`, resetting its value to 0.
///
/// This can be used to destroy sensitive data such as keys when they are no longer needed.
///
/// OpenSSL documentation at [`BN_clear`]
///
/// [`BN_clear`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_clear.html
#[corresponds(BN_clear)]
pub fn clear(&mut self) {
unsafe { ffi::BN_clear(self.as_ptr()) }
}
/// Adds a `u32` to `self`.
///
/// OpenSSL documentation at [`BN_add_word`]
///
/// [`BN_add_word`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_add_word.html
#[corresponds(BN_add_word)]
pub fn add_word(&mut self, w: u32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_add_word(self.as_ptr(), w as ffi::BN_ULONG)).map(|_| ()) }
unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
}
/// Subtracts a `u32` from `self`.
///
/// OpenSSL documentation at [`BN_sub_word`]
///
/// [`BN_sub_word`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_sub_word.html
#[corresponds(BN_sub_word)]
pub fn sub_word(&mut self, w: u32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), w as ffi::BN_ULONG)).map(|_| ()) }
unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
}
/// Multiplies a `u32` by `self`.
///
/// OpenSSL documentation at [`BN_mul_word`]
///
/// [`BN_mul_word`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mul_word.html
#[corresponds(BN_mul_word)]
pub fn mul_word(&mut self, w: u32) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), w as ffi::BN_ULONG)).map(|_| ()) }
unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
}
/// Divides `self` by a `u32`, returning the remainder.
///
/// OpenSSL documentation at [`BN_div_word`]
///
/// [`BN_div_word`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_div_word.html
#[corresponds(BN_div_word)]
#[allow(clippy::useless_conversion)]
pub fn div_word(&mut self, w: u32) -> Result<u64, ErrorStack> {
unsafe {
let r = ffi::BN_div_word(self.as_ptr(), w.into());
if r == ffi::BN_ULONG::max_value() {
if r == ffi::BN_ULONG::MAX {
Err(ErrorStack::get())
} else {
Ok(r.into())
@ -168,15 +151,12 @@ impl BigNumRef {
}
/// Returns the result of `self` modulo `w`.
///
/// OpenSSL documentation at [`BN_mod_word`]
///
/// [`BN_mod_word`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mod_word.html
#[corresponds(BN_mod_word)]
#[allow(clippy::useless_conversion)]
pub fn mod_word(&self, w: u32) -> Result<u64, ErrorStack> {
unsafe {
let r = ffi::BN_mod_word(self.as_ptr(), w.into());
if r == ffi::BN_ULONG::max_value() {
if r == ffi::BN_ULONG::MAX {
Err(ErrorStack::get())
} else {
Ok(r.into())
@ -186,53 +166,39 @@ impl BigNumRef {
/// Places a cryptographically-secure pseudo-random nonnegative
/// number less than `self` in `rnd`.
///
/// OpenSSL documentation at [`BN_rand_range`]
///
/// [`BN_rand_range`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_rand_range.html
#[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`.
///
/// OpenSSL documentation at [`BN_pseudo_rand_range`]
///
/// [`BN_pseudo_rand_range`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_pseudo_rand_range.html
#[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)`.
///
/// When setting a bit outside of `self`, it is expanded.
///
/// OpenSSL documentation at [`BN_set_bit`]
///
/// [`BN_set_bit`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_set_bit.html
#[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)`.
///
/// When clearing a bit outside of `self`, an error is returned.
///
/// OpenSSL documentation at [`BN_clear_bit`]
///
/// [`BN_clear_bit`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_clear_bit.html
#[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.
///
/// OpenSSL documentation at [`BN_is_bit_set`]
///
/// [`BN_is_bit_set`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_is_bit_set.html
#[corresponds(BN_is_bit_set)]
#[allow(clippy::useless_conversion)]
#[must_use]
pub fn is_bit_set(&self, n: i32) -> bool {
unsafe { ffi::BN_is_bit_set(self.as_ptr(), n.into()) == 1 }
}
@ -240,94 +206,69 @@ impl BigNumRef {
/// Truncates `self` to the lowest `n` bits.
///
/// An error occurs if `self` is already shorter than `n` bits.
///
/// OpenSSL documentation at [`BN_mask_bits`]
///
/// [`BN_mask_bits`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mask_bits.html
#[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`.
///
/// OpenSSL documentation at [`BN_lshift1`]
///
/// [`BN_lshift1`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_lshift1.html
#[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`.
///
/// OpenSSL documentation at [`BN_rshift1`]
///
/// [`BN_rshift1`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_rshift1.html
#[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`.
///
/// OpenSSL documentation at [`BN_add`]
///
/// [`core::ops::Add`]: struct.BigNumRef.html#method.add
/// [`BN_add`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_add.html
#[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`.
///
/// OpenSSL documentation at [`BN_sub`]
///
/// [`core::ops::Sub`]: struct.BigNumRef.html#method.sub
/// [`BN_sub`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_sub.html
#[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`.
///
/// OpenSSL documentation at [`BN_lshift`]
///
/// [`BN_lshift`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_lshift.html
#[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`.
///
/// OpenSSL documentation at [`BN_rshift`]
///
/// [`BN_rshift`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_rshift.html
#[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.
///
/// OpenSSL documentation at [`BN_dup`]
///
/// [`BN_dup`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_dup.html
#[corresponds(BN_dup)]
pub fn to_owned(&self) -> Result<BigNum, ErrorStack> {
unsafe { cvt_p(ffi::BN_dup(self.as_ptr())).map(|b| BigNum::from_ptr(b)) }
}
/// Sets the sign of `self`. Pass true to set `self` to a negative. False sets
/// `self` positive.
#[corresponds(BN_set_negative)]
pub fn set_negative(&mut self, negative: bool) {
unsafe { ffi::BN_set_negative(self.as_ptr(), negative as c_int) }
unsafe { ffi::BN_set_negative(self.as_ptr(), c_int::from(negative)) }
}
/// Compare the absolute values of `self` and `oth`.
///
/// OpenSSL documentation at [`BN_ucmp`]
///
/// [`BN_ucmp`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_ucmp.html
///
/// # Examples
///
/// ```
@ -338,25 +279,28 @@ impl BigNumRef {
///
/// assert_eq!(s.ucmp(&o), Ordering::Equal);
/// ```
#[corresponds(BN_ucmp)]
#[must_use]
pub fn ucmp(&self, oth: &BigNumRef) -> Ordering {
unsafe { ffi::BN_ucmp(self.as_ptr(), oth.as_ptr()).cmp(&0) }
}
/// Returns `true` if `self` is negative.
#[corresponds(BN_is_negative)]
#[must_use]
pub fn is_negative(&self) -> bool {
unsafe { BN_is_negative(self.as_ptr()) == 1 }
}
/// Returns the number of significant bits in `self`.
///
/// OpenSSL documentation at [`BN_num_bits`]
///
/// [`BN_num_bits`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_num_bits.html
#[corresponds(BN_num_bits)]
#[must_use]
pub fn num_bits(&self) -> i32 {
unsafe { ffi::BN_num_bits(self.as_ptr()) as i32 }
}
/// Returns the size of `self` in bytes. Implemented natively.
#[must_use]
pub fn num_bytes(&self) -> i32 {
(self.num_bits() + 7) / 8
}
@ -384,10 +328,8 @@ impl BigNumRef {
/// }
/// ```
///
/// OpenSSL documentation at [`BN_rand`]
///
/// [`constants`]: index.html#constants
/// [`BN_rand`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_rand.html
#[corresponds(BN_rand)]
#[allow(clippy::useless_conversion)]
pub fn rand(&mut self, bits: i32, msb: MsbOption, odd: bool) -> Result<(), ErrorStack> {
unsafe {
@ -395,17 +337,13 @@ impl BigNumRef {
self.as_ptr(),
bits.into(),
msb.0,
odd as c_int,
c_int::from(odd),
))
.map(|_| ())
}
}
/// The cryptographically weak counterpart to `rand`. Not suitable for key generation.
///
/// OpenSSL documentation at [`BN_pseudo_rand`]
///
/// [`BN_pseudo_rand`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_pseudo_rand.html
#[corresponds(BN_pseudo_rand)]
#[allow(clippy::useless_conversion)]
pub fn pseudo_rand(&mut self, bits: i32, msb: MsbOption, odd: bool) -> Result<(), ErrorStack> {
unsafe {
@ -413,9 +351,8 @@ impl BigNumRef {
self.as_ptr(),
bits.into(),
msb.0,
odd as c_int,
c_int::from(odd),
))
.map(|_| ())
}
}
@ -442,10 +379,7 @@ impl BigNumRef {
/// Ok((big))
/// }
/// ```
///
/// OpenSSL documentation at [`BN_generate_prime_ex`]
///
/// [`BN_generate_prime_ex`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_generate_prime_ex.html
#[corresponds(BN_generate_prime_ex)]
pub fn generate_prime(
&mut self,
bits: i32,
@ -456,23 +390,20 @@ impl BigNumRef {
unsafe {
cvt(ffi::BN_generate_prime_ex(
self.as_ptr(),
bits as c_int,
safe 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(|_| ())
}
}
/// Places the result of `a * b` in `self`.
/// [`core::ops::Mul`] is also implemented for `BigNumRef`.
///
/// OpenSSL documentation at [`BN_mul`]
///
/// [`core::ops::Mul`]: struct.BigNumRef.html#method.mul
/// [`BN_mul`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mul.html
#[corresponds(BN_mul)]
pub fn checked_mul(
&mut self,
a: &BigNumRef,
@ -486,17 +417,14 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a / b` in `self`. The remainder is discarded.
/// [`core::ops::Div`] is also implemented for `BigNumRef`.
///
/// OpenSSL documentation at [`BN_div`]
///
/// [`core::ops::Div`]: struct.BigNumRef.html#method.div
/// [`BN_div`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_div.html
#[corresponds(BN_div)]
pub fn checked_div(
&mut self,
a: &BigNumRef,
@ -511,15 +439,11 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a % b` in `self`.
///
/// OpenSSL documentation at [`BN_div`]
///
/// [`BN_div`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_div.html
#[corresponds(BN_div)]
pub fn checked_rem(
&mut self,
a: &BigNumRef,
@ -534,15 +458,11 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a / b` in `self` and `a % b` in `rem`.
///
/// OpenSSL documentation at [`BN_div`]
///
/// [`BN_div`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_div.html
#[corresponds(BN_div)]
pub fn div_rem(
&mut self,
rem: &mut BigNumRef,
@ -558,25 +478,18 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a²` in `self`.
///
/// OpenSSL documentation at [`BN_sqr`]
///
/// [`BN_sqr`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_sqr.html
#[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`
/// the result is non-negative.
///
/// OpenSSL documentation at [`BN_nnmod`]
///
/// [`BN_nnmod`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_nnmod.html
#[corresponds(BN_nnmod)]
pub fn nnmod(
&mut self,
a: &BigNumRef,
@ -590,15 +503,11 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `(a + b) mod m` in `self`.
///
/// OpenSSL documentation at [`BN_mod_add`]
///
/// [`BN_mod_add`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mod_add.html
#[corresponds(BN_mod_add)]
pub fn mod_add(
&mut self,
a: &BigNumRef,
@ -614,15 +523,11 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `(a - b) mod m` in `self`.
///
/// OpenSSL documentation at [`BN_mod_sub`]
///
/// [`BN_mod_sub`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mod_sub.html
#[corresponds(BN_mod_sub)]
pub fn mod_sub(
&mut self,
a: &BigNumRef,
@ -638,15 +543,11 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `(a * b) mod m` in `self`.
///
/// OpenSSL documentation at [`BN_mod_mul`]
///
/// [`BN_mod_mul`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mod_mul.html
#[corresponds(BN_mod_mul)]
pub fn mod_mul(
&mut self,
a: &BigNumRef,
@ -662,15 +563,11 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a² mod m` in `self`.
///
/// OpenSSL documentation at [`BN_mod_sqr`]
///
/// [`BN_mod_sqr`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mod_sqr.html
#[corresponds(BN_mod_sqr)]
pub fn mod_sqr(
&mut self,
a: &BigNumRef,
@ -684,15 +581,11 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a^p` in `self`.
///
/// OpenSSL documentation at [`BN_exp`]
///
/// [`BN_exp`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_exp.html
#[corresponds(BN_exp)]
pub fn exp(
&mut self,
a: &BigNumRef,
@ -706,15 +599,11 @@ impl BigNumRef {
p.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the result of `a^p mod m` in `self`.
///
/// OpenSSL documentation at [`BN_mod_exp`]
///
/// [`BN_mod_exp`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_mod_exp.html
#[corresponds(BN_mod_exp)]
pub fn mod_exp(
&mut self,
a: &BigNumRef,
@ -730,11 +619,11 @@ impl BigNumRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the inverse of `a` modulo `n` in `self`.
#[corresponds(BN_mod_inverse)]
pub fn mod_inverse(
&mut self,
a: &BigNumRef,
@ -753,10 +642,7 @@ impl BigNumRef {
}
/// Places the greatest common denominator of `a` and `b` in `self`.
///
/// OpenSSL documentation at [`BN_gcd`]
///
/// [`BN_gcd`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_gcd.html
#[corresponds(BN_gcd)]
pub fn gcd(
&mut self,
a: &BigNumRef,
@ -770,7 +656,6 @@ impl BigNumRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -778,13 +663,10 @@ impl BigNumRef {
///
/// Performs a Miller-Rabin probabilistic primality test with `checks` iterations.
///
/// OpenSSL documentation at [`BN_is_prime_ex`]
///
/// [`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_is_prime_ex.html
///
/// # Return Value
///
/// Returns `true` if `self` is prime with an error probability of less than `0.25 ^ checks`.
#[corresponds(BN_is_prime_ex)]
#[allow(clippy::useless_conversion)]
pub fn is_prime(&self, checks: i32, ctx: &mut BigNumContextRef) -> Result<bool, ErrorStack> {
unsafe {
@ -804,13 +686,10 @@ impl BigNumRef {
/// Then, like `is_prime`, performs a Miller-Rabin probabilistic primality test with `checks`
/// iterations.
///
/// OpenSSL documentation at [`BN_is_prime_fasttest_ex`]
///
/// [`BN_is_prime_fasttest_ex`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_is_prime_fasttest_ex.html
///
/// # Return Value
///
/// Returns `true` if `self` is prime with an error probability of less than `0.25 ^ checks`.
#[corresponds(BN_is_prime_fasttest_ex)]
#[allow(clippy::useless_conversion)]
pub fn is_prime_fasttest(
&self,
@ -823,7 +702,7 @@ impl BigNumRef {
self.as_ptr(),
checks.into(),
ctx.as_ptr(),
do_trial_division as c_int,
c_int::from(do_trial_division),
ptr::null_mut(),
))
.map(|r| r != 0)
@ -842,6 +721,8 @@ impl BigNumRef {
/// let s_vec = s.to_vec();
/// assert_eq!(BigNum::from_slice(&s_vec).unwrap(), r);
/// ```
#[corresponds(BN_bn2bin)]
#[must_use]
pub fn to_vec(&self) -> Vec<u8> {
let size = self.num_bytes() as usize;
let mut v = Vec::with_capacity(size);
@ -890,6 +771,7 @@ impl BigNumRef {
///
/// assert_eq!(&**s.to_dec_str().unwrap(), "-12345");
/// ```
#[corresponds(BN_bn2dec)]
pub fn to_dec_str(&self) -> Result<OpensslString, ErrorStack> {
unsafe {
let buf = cvt_p(ffi::BN_bn2dec(self.as_ptr()))?;
@ -905,6 +787,7 @@ impl BigNumRef {
///
/// assert_eq!(&**s.to_hex_str().unwrap(), "-99ff");
/// ```
#[corresponds(BN_bn2hex)]
pub fn to_hex_str(&self) -> Result<OpensslString, ErrorStack> {
unsafe {
let buf = cvt_p(ffi::BN_bn2hex(self.as_ptr()))?;
@ -913,6 +796,7 @@ impl BigNumRef {
}
/// Returns an `Asn1Integer` containing the value of `self`.
#[corresponds(BN_to_ASN1_INTEGER)]
pub fn to_asn1_integer(&self) -> Result<Asn1Integer, ErrorStack> {
unsafe {
cvt_p(ffi::BN_to_ASN1_INTEGER(self.as_ptr(), ptr::null_mut()))
@ -923,6 +807,7 @@ impl BigNumRef {
impl BigNum {
/// Creates a new `BigNum` with the value 0.
#[corresponds(BN_new)]
pub fn new() -> Result<BigNum, ErrorStack> {
unsafe {
ffi::init();
@ -932,42 +817,33 @@ impl BigNum {
}
/// Creates a new `BigNum` with the given value.
///
/// OpenSSL documentation at [`BN_set_word`]
///
/// [`BN_set_word`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_set_word.html
#[corresponds(BN_set_word)]
pub fn from_u32(n: u32) -> Result<BigNum, ErrorStack> {
BigNum::new().and_then(|v| unsafe {
cvt(ffi::BN_set_word(v.as_ptr(), n as ffi::BN_ULONG)).map(|_| v)
cvt(ffi::BN_set_word(v.as_ptr(), ffi::BN_ULONG::from(n))).map(|_| v)
})
}
/// Creates a `BigNum` from a decimal string.
///
/// OpenSSL documentation at [`BN_dec2bn`]
///
/// [`BN_dec2bn`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_dec2bn.html
#[corresponds(BN_dec2bn)]
pub fn from_dec_str(s: &str) -> Result<BigNum, ErrorStack> {
unsafe {
ffi::init();
let c_str = CString::new(s.as_bytes()).unwrap();
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))
}
}
/// Creates a `BigNum` from a hexadecimal string.
///
/// OpenSSL documentation at [`BN_hex2bn`]
///
/// [`BN_hex2bn`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_hex2bn.html
#[corresponds(BN_hex2bn)]
pub fn from_hex_str(s: &str) -> Result<BigNum, ErrorStack> {
unsafe {
ffi::init();
let c_str = CString::new(s.as_bytes()).unwrap();
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))
}
}
@ -984,16 +860,12 @@ impl BigNum {
///
/// assert_eq!(bignum, BigNum::from_u32(0x120034).unwrap());
/// ```
#[corresponds(BN_bin2bn)]
pub fn from_slice(n: &[u8]) -> Result<BigNum, ErrorStack> {
unsafe {
ffi::init();
assert!(n.len() <= c_int::max_value() as usize);
cvt_p(ffi::BN_bin2bn(
n.as_ptr(),
n.len() as size_t,
ptr::null_mut(),
))
.map(|p| BigNum::from_ptr(p))
assert!(n.len() <= c_int::MAX as usize);
cvt_p(ffi::BN_bin2bn(n.as_ptr(), n.len(), ptr::null_mut())).map(|p| BigNum::from_ptr(p))
}
}
}
@ -1042,7 +914,7 @@ impl PartialEq<BigNumRef> for BigNumRef {
impl PartialEq<BigNum> for BigNumRef {
fn eq(&self, oth: &BigNum) -> bool {
self.eq(oth.deref())
self.eq(&**oth)
}
}
@ -1070,7 +942,7 @@ impl PartialOrd<BigNumRef> for BigNumRef {
impl PartialOrd<BigNum> for BigNumRef {
fn partial_cmp(&self, oth: &BigNum) -> Option<Ordering> {
Some(self.cmp(oth.deref()))
Some(self.cmp(&**oth))
}
}
@ -1094,7 +966,7 @@ impl PartialOrd<BigNumRef> for BigNum {
impl Ord for BigNum {
fn cmp(&self, oth: &BigNum) -> Ordering {
self.deref().cmp(oth.deref())
self.deref().cmp(&**oth)
}
}
@ -1126,7 +998,7 @@ macro_rules! delegate {
};
}
impl<'a, 'b> Add<&'b BigNumRef> for &'a BigNumRef {
impl Add<&BigNumRef> for &BigNumRef {
type Output = BigNum;
fn add(self, oth: &BigNumRef) -> BigNum {
@ -1138,7 +1010,7 @@ impl<'a, 'b> Add<&'b BigNumRef> for &'a BigNumRef {
delegate!(Add, add);
impl<'a, 'b> Sub<&'b BigNumRef> for &'a BigNumRef {
impl Sub<&BigNumRef> for &BigNumRef {
type Output = BigNum;
fn sub(self, oth: &BigNumRef) -> BigNum {
@ -1150,7 +1022,7 @@ impl<'a, 'b> Sub<&'b BigNumRef> for &'a BigNumRef {
delegate!(Sub, sub);
impl<'a, 'b> Mul<&'b BigNumRef> for &'a BigNumRef {
impl Mul<&BigNumRef> for &BigNumRef {
type Output = BigNum;
fn mul(self, oth: &BigNumRef) -> BigNum {
@ -1163,7 +1035,7 @@ impl<'a, 'b> Mul<&'b BigNumRef> for &'a BigNumRef {
delegate!(Mul, mul);
impl<'a, 'b> Div<&'b BigNumRef> for &'a BigNumRef {
impl<'b> Div<&'b BigNumRef> for &BigNumRef {
type Output = BigNum;
fn div(self, oth: &'b BigNumRef) -> BigNum {
@ -1176,7 +1048,7 @@ impl<'a, 'b> Div<&'b BigNumRef> for &'a BigNumRef {
delegate!(Div, div);
impl<'a, 'b> Rem<&'b BigNumRef> for &'a BigNumRef {
impl<'b> Rem<&'b BigNumRef> for &BigNumRef {
type Output = BigNum;
fn rem(self, oth: &'b BigNumRef) -> BigNum {
@ -1189,7 +1061,7 @@ impl<'a, 'b> Rem<&'b BigNumRef> for &'a BigNumRef {
delegate!(Rem, rem);
impl<'a> Shl<i32> for &'a BigNumRef {
impl Shl<i32> for &BigNumRef {
type Output = BigNum;
fn shl(self, n: i32) -> BigNum {
@ -1199,7 +1071,7 @@ impl<'a> Shl<i32> for &'a BigNumRef {
}
}
impl<'a> Shl<i32> for &'a BigNum {
impl Shl<i32> for &BigNum {
type Output = BigNum;
fn shl(self, n: i32) -> BigNum {
@ -1207,7 +1079,7 @@ impl<'a> Shl<i32> for &'a BigNum {
}
}
impl<'a> Shr<i32> for &'a BigNumRef {
impl Shr<i32> for &BigNumRef {
type Output = BigNum;
fn shr(self, n: i32) -> BigNum {
@ -1217,7 +1089,7 @@ impl<'a> Shr<i32> for &'a BigNumRef {
}
}
impl<'a> Shr<i32> for &'a BigNum {
impl Shr<i32> for &BigNum {
type Output = BigNum;
fn shr(self, n: i32) -> BigNum {
@ -1225,7 +1097,7 @@ impl<'a> Shr<i32> for &'a BigNum {
}
}
impl<'a> Neg for &'a BigNumRef {
impl Neg for &BigNumRef {
type Output = BigNum;
fn neg(self) -> BigNum {
@ -1233,7 +1105,7 @@ impl<'a> Neg for &'a BigNumRef {
}
}
impl<'a> Neg for &'a BigNum {
impl Neg for &BigNum {
type Output = BigNum;
fn neg(self) -> BigNum {

View File

@ -19,6 +19,7 @@ impl ConfMethod {
}
/// Convert to raw pointer.
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.0
}

View File

@ -1,6 +1,7 @@
//! Shared secret derivation.
use crate::ffi;
use foreign_types::ForeignTypeRef;
use openssl_macros::corresponds;
use std::marker::PhantomData;
use std::ptr;
@ -11,10 +12,10 @@ use crate::{cvt, cvt_p};
/// A type used to derive a shared secret between two keys.
pub struct Deriver<'a>(*mut ffi::EVP_PKEY_CTX, PhantomData<&'a ()>);
unsafe impl<'a> Sync for Deriver<'a> {}
unsafe impl<'a> Send for Deriver<'a> {}
unsafe impl Sync for Deriver<'_> {}
unsafe impl Send for Deriver<'_> {}
impl<'a> Drop for Deriver<'a> {
impl Drop for Deriver<'_> {
fn drop(&mut self) {
unsafe {
ffi::EVP_PKEY_CTX_free(self.0);
@ -25,10 +26,7 @@ impl<'a> Drop for Deriver<'a> {
#[allow(clippy::len_without_is_empty)]
impl<'a> Deriver<'a> {
/// Creates a new `Deriver` using the provided private key.
///
/// This corresponds to [`EVP_PKEY_derive_init`].
///
/// [`EVP_PKEY_derive_init`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_derive_init.html
#[corresponds(EVP_PKEY_derive_init)]
pub fn new<T>(key: &'a PKeyRef<T>) -> Result<Deriver<'a>, ErrorStack>
where
T: HasPrivate,
@ -41,25 +39,18 @@ impl<'a> Deriver<'a> {
}
/// Sets the peer key used for secret derivation.
///
/// This corresponds to [`EVP_PKEY_derive_set_peer`]:
///
/// [`EVP_PKEY_derive_set_peer`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_derive_init.html
#[corresponds(EVP_PKEY_derive_set_peer)]
pub fn set_peer<T>(&mut self, key: &'a PKeyRef<T>) -> Result<(), ErrorStack>
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.
///
/// It can be used to size the buffer passed to [`Deriver::derive`].
///
/// This corresponds to [`EVP_PKEY_derive`].
///
/// [`Deriver::derive`]: #method.derive
/// [`EVP_PKEY_derive`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_derive_init.html
#[corresponds(EVP_PKEY_derive)]
pub fn len(&mut self) -> Result<usize, ErrorStack> {
unsafe {
let mut len = 0;
@ -70,20 +61,10 @@ impl<'a> Deriver<'a> {
/// Derives a shared secret between the two keys, writing it into the buffer.
///
/// Returns the number of bytes written.
///
/// This corresponds to [`EVP_PKEY_derive`].
///
/// [`EVP_PKEY_derive`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_derive_init.html
#[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

@ -1,6 +1,7 @@
use crate::error::ErrorStack;
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl_macros::corresponds;
use std::mem;
use std::ptr;
@ -25,20 +26,14 @@ where
/// Serializes the parameters into a PEM-encoded PKCS#3 DHparameter structure.
///
/// The output will have a header of `-----BEGIN DH PARAMETERS-----`.
///
/// This corresponds to [`PEM_write_bio_DHparams`].
///
/// [`PEM_write_bio_DHparams`]: https://www.openssl.org/docs/manmaster/man3/PEM_write_bio_DHparams.html
#[corresponds(PEM_write_bio_DHparams)]
params_to_pem,
ffi::PEM_write_bio_DHparams
}
to_der! {
/// Serializes the parameters into a DER-encoded PKCS#3 DHparameter structure.
///
/// This corresponds to [`i2d_DHparams`].
///
/// [`i2d_DHparams`]: https://www.openssl.org/docs/man1.1.0/crypto/i2d_DHparams.html
/// Serializes the parameters into a DER-encoded PKCS#3 `DHparameter` structure.
#[corresponds(i2d_DHparams)]
params_to_der,
ffi::i2d_DHparams
}
@ -58,10 +53,7 @@ impl Dh<Params> {
/// Deserializes a PEM-encoded PKCS#3 DHpararameters structure.
///
/// The input should have a header of `-----BEGIN DH PARAMETERS-----`.
///
/// This corresponds to [`PEM_read_bio_DHparams`].
///
/// [`PEM_read_bio_DHparams`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_read_bio_DHparams.html
#[corresponds(PEM_read_bio_DHparams)]
params_from_pem,
Dh<Params>,
ffi::PEM_read_bio_DHparams
@ -69,10 +61,7 @@ impl Dh<Params> {
from_der! {
/// Deserializes a DER-encoded PKCS#3 DHparameters structure.
///
/// This corresponds to [`d2i_DHparams`].
///
/// [`d2i_DHparams`]: https://www.openssl.org/docs/man1.1.0/crypto/d2i_DHparams.html
#[corresponds(d2i_DHparams)]
params_from_der,
Dh<Params>,
ffi::d2i_DHparams,

View File

@ -5,16 +5,18 @@
//! 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;
use std::fmt;
use std::mem;
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! {
@ -84,30 +86,25 @@ where
/// Serialies the public key into a PEM-encoded SubjectPublicKeyInfo structure.
///
/// The output will have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_write_bio_DSA_PUBKEY`].
///
/// [`PEM_write_bio_DSA_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_DSA_PUBKEY.html
#[corresponds(PEM_write_bio_DSA_PUBKEY)]
public_key_to_pem,
ffi::PEM_write_bio_DSA_PUBKEY
}
to_der! {
/// Serializes the public key into a DER-encoded SubjectPublicKeyInfo structure.
///
/// This corresponds to [`i2d_DSA_PUBKEY`].
///
/// [`i2d_DSA_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/i2d_DSA_PUBKEY.html
#[corresponds(i2d_DSA_PUBKEY)]
public_key_to_der,
ffi::i2d_DSA_PUBKEY
}
/// Returns a reference to the public key component of `self`.
#[must_use]
pub fn pub_key(&self) -> &BigNumRef {
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())
}
}
}
@ -120,28 +117,23 @@ where
/// Serializes the private key to a PEM-encoded DSAPrivateKey structure.
///
/// The output will have a header of `-----BEGIN DSA PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_DSAPrivateKey`].
///
/// [`PEM_write_bio_DSAPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_DSAPrivateKey.html
#[corresponds(PEM_write_bio_DSAPrivateKey)]
private_key_to_pem,
/// Serializes the private key to a PEM-encoded encrypted DSAPrivateKey structure.
///
/// The output will have a header of `-----BEGIN DSA PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_DSAPrivateKey`].
///
/// [`PEM_write_bio_DSAPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_DSAPrivateKey.html
#[corresponds(PEM_write_bio_DSAPrivateKey)]
private_key_to_pem_passphrase,
ffi::PEM_write_bio_DSAPrivateKey
}
/// Returns a reference to the private key component of `self`.
#[must_use]
pub fn priv_key(&self) -> &BigNumRef {
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())
}
}
}
@ -151,38 +143,39 @@ where
T: HasParams,
{
/// Returns the maximum size of the signature output by `self` in bytes.
///
/// OpenSSL documentation at [`DSA_size`]
///
/// [`DSA_size`]: https://www.openssl.org/docs/man1.1.0/crypto/DSA_size.html
#[corresponds(DSA_size)]
#[must_use]
pub fn size(&self) -> u32 {
unsafe { ffi::DSA_size(self.as_ptr()) as u32 }
}
/// Returns the DSA prime parameter of `self`.
#[must_use]
pub fn p(&self) -> &BigNumRef {
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())
}
}
/// Returns the DSA sub-prime parameter of `self`.
#[must_use]
pub fn q(&self) -> &BigNumRef {
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())
}
}
/// Returns the DSA base parameter of `self`.
#[must_use]
pub fn g(&self) -> &BigNumRef {
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())
}
}
}
@ -203,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(),
@ -244,10 +237,7 @@ impl Dsa<Public> {
/// Decodes a PEM-encoded SubjectPublicKeyInfo structure containing a DSA key.
///
/// The input should have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_read_bio_DSA_PUBKEY`].
///
/// [`PEM_read_bio_DSA_PUBKEY`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_read_bio_DSA_PUBKEY.html
#[corresponds(PEM_read_bio_DSA_PUBKEY)]
public_key_from_pem,
Dsa<Public>,
ffi::PEM_read_bio_DSA_PUBKEY
@ -255,10 +245,7 @@ impl Dsa<Public> {
from_der! {
/// Decodes a DER-encoded SubjectPublicKeyInfo structure containing a DSA key.
///
/// This corresponds to [`d2i_DSA_PUBKEY`].
///
/// [`d2i_DSA_PUBKEY`]: https://www.openssl.org/docs/man1.0.2/crypto/d2i_DSA_PUBKEY.html
#[corresponds(d2i_DSA_PUBKEY)]
public_key_from_der,
Dsa<Public>,
ffi::d2i_DSA_PUBKEY,
@ -280,8 +267,7 @@ impl Dsa<Public> {
let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?);
cvt(DSA_set0_pqg(dsa.0, p.as_ptr(), q.as_ptr(), g.as_ptr()))?;
mem::forget((p, q, g));
cvt(DSA_set0_key(dsa.0, pub_key.as_ptr(), ptr::null_mut()))?;
mem::forget(pub_key);
cvt(DSA_set0_key(dsa.0, pub_key.into_ptr(), ptr::null_mut()))?;
Ok(dsa)
}
}
@ -315,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,16 +15,18 @@
//! [`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;
use std::fmt;
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
@ -102,7 +104,7 @@ foreign_type_and_impl_send_sync! {
/// Prime fields use the formula `y^2 mod p = x^3 + ax + b mod p`. Binary
/// fields use the formula `y^2 + xy = x^3 + ax^2 + b`. Named curves have
/// assured security. To prevent accidental vulnerabilities, they should
/// be prefered.
/// be preferred.
///
/// [wiki]: https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations
/// [`Nid`]: ../nid/index.html
@ -111,10 +113,7 @@ foreign_type_and_impl_send_sync! {
impl EcGroup {
/// Returns the group of a standard named curve.
///
/// OpenSSL documentation at [`EC_GROUP_new`].
///
/// [`EC_GROUP_new`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_new.html
#[corresponds(EC_GROUP_new)]
pub fn from_curve_name(nid: Nid) -> Result<EcGroup, ErrorStack> {
unsafe {
init();
@ -145,15 +144,11 @@ impl EcGroupRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Places the cofactor of the group in the provided `BigNum`.
///
/// OpenSSL documentation at [`EC_GROUP_get_cofactor`]
///
/// [`EC_GROUP_get_cofactor`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_get_cofactor.html
#[corresponds(EC_GROUP_get_cofactor)]
pub fn cofactor(
&self,
cofactor: &mut BigNumRef,
@ -165,46 +160,36 @@ impl EcGroupRef {
cofactor.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Returns the degree of the curve.
///
/// OpenSSL documentation at [`EC_GROUP_get_degree`]
///
/// [`EC_GROUP_get_degree`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_get_degree.html
#[corresponds(EC_GROUP_get_degree)]
#[allow(clippy::unnecessary_cast)]
#[must_use]
pub fn degree(&self) -> u32 {
unsafe { ffi::EC_GROUP_get_degree(self.as_ptr()) as u32 }
}
/// Returns the number of bits in the group order.
///
/// OpenSSL documentation at [`EC_GROUP_order_bits`]
///
/// [`EC_GROUP_order_bits`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_order_bits.html
#[corresponds(EC_GROUP_order_bits)]
#[must_use]
pub fn order_bits(&self) -> u32 {
unsafe { ffi::EC_GROUP_order_bits(self.as_ptr()) as u32 }
}
/// Returns the generator for the given curve as a [`EcPoint`].
///
/// OpenSSL documentation at [`EC_GROUP_get0_generator`]
///
/// [`EC_GROUP_get0_generator`]: https://www.openssl.org/docs/man1.1.0/man3/EC_GROUP_get0_generator.html
#[corresponds(EC_GROUP_get0_generator)]
#[must_use]
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())
}
}
/// Places the order of the curve in the provided `BigNum`.
///
/// OpenSSL documentation at [`EC_GROUP_get_order`]
///
/// [`EC_GROUP_get_order`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_get_order.html
#[corresponds(EC_GROUP_get_order)]
pub fn order(
&self,
order: &mut BigNumRef,
@ -216,7 +201,6 @@ impl EcGroupRef {
order.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -232,10 +216,8 @@ impl EcGroupRef {
}
/// Returns the name of the curve, if a name is associated.
///
/// OpenSSL documentation at [`EC_GROUP_get_curve_name`]
///
/// [`EC_GROUP_get_curve_name`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_get_curve_name.html
#[corresponds(EC_GROUP_get_curve_name)]
#[must_use]
pub fn curve_name(&self) -> Option<Nid> {
let nid = unsafe { ffi::EC_GROUP_get_curve_name(self.as_ptr()) };
if nid > 0 {
@ -260,10 +242,7 @@ foreign_type_and_impl_send_sync! {
impl EcPointRef {
/// Computes `a + b`, storing the result in `self`.
///
/// OpenSSL documentation at [`EC_POINT_add`]
///
/// [`EC_POINT_add`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_add.html
#[corresponds(EC_POINT_add)]
pub fn add(
&mut self,
group: &EcGroupRef,
@ -279,22 +258,17 @@ impl EcPointRef {
b.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Computes `q * m`, storing the result in `self`.
///
/// OpenSSL documentation at [`EC_POINT_mul`]
///
/// [`EC_POINT_mul`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_mul.html
#[corresponds(EC_POINT_mul)]
pub fn mul(
&mut self,
group: &EcGroupRef,
q: &EcPointRef,
m: &BigNumRef,
// FIXME should be &mut
ctx: &BigNumContextRef,
ctx: &mut BigNumContextRef,
) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EC_POINT_mul(
@ -305,7 +279,6 @@ impl EcPointRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -314,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(
@ -326,7 +298,6 @@ impl EcPointRef {
ptr::null(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
@ -348,15 +319,11 @@ impl EcPointRef {
m.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Inverts `self`.
///
/// OpenSSL documentation at [`EC_POINT_invert`]
///
/// [`EC_POINT_invert`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_invert.html
#[corresponds(EC_POINT_invert)]
pub fn invert(&mut self, group: &EcGroupRef, ctx: &BigNumContextRef) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EC_POINT_invert(
@ -364,15 +331,11 @@ impl EcPointRef {
self.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
/// Serializes the point to a binary representation.
///
/// OpenSSL documentation at [`EC_POINT_point2oct`]
///
/// [`EC_POINT_point2oct`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_point2oct.html
#[corresponds(EC_POINT_point2oct)]
pub fn to_bytes(
&self,
group: &EcGroupRef,
@ -409,10 +372,7 @@ impl EcPointRef {
}
/// Creates a new point on the specified curve with the same value.
///
/// OpenSSL documentation at [`EC_POINT_dup`]
///
/// [`EC_POINT_dup`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_dup.html
#[corresponds(EC_POINT_dup)]
pub fn to_owned(&self, group: &EcGroupRef) -> Result<EcPoint, ErrorStack> {
unsafe {
cvt_p(ffi::EC_POINT_dup(self.as_ptr(), group.as_ptr())).map(|p| EcPoint::from_ptr(p))
@ -443,10 +403,7 @@ impl EcPointRef {
/// Place affine coordinates of a curve over a prime field in the provided
/// `x` and `y` `BigNum`s
///
/// OpenSSL documentation at [`EC_POINT_get_affine_coordinates_GFp`]
///
/// [`EC_POINT_get_affine_coordinates_GFp`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_get_affine_coordinates_GFp.html
#[corresponds(EC_POINT_get_affine_coordinates_GFp)]
pub fn affine_coordinates_gfp(
&self,
group: &EcGroupRef,
@ -462,26 +419,19 @@ impl EcPointRef {
y.as_ptr(),
ctx.as_ptr(),
))
.map(|_| ())
}
}
}
impl EcPoint {
/// Creates a new point on the specified curve.
///
/// OpenSSL documentation at [`EC_POINT_new`]
///
/// [`EC_POINT_new`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_new.html
#[corresponds(EC_POINT_new)]
pub fn new(group: &EcGroupRef) -> Result<EcPoint, ErrorStack> {
unsafe { cvt_p(ffi::EC_POINT_new(group.as_ptr())).map(|p| EcPoint::from_ptr(p)) }
}
/// Creates point from a binary representation
///
/// OpenSSL documentation at [`EC_POINT_oct2point`]
///
/// [`EC_POINT_oct2point`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_POINT_oct2point.html
#[corresponds(EC_POINT_oct2point)]
pub fn from_bytes(
group: &EcGroupRef,
buf: &[u8],
@ -507,9 +457,6 @@ generic_foreign_type_and_impl_send_sync! {
/// Public and optional Private key on the given curve
///
/// OpenSSL documentation at [`EC_KEY_new`]
///
/// [`EC_KEY_new`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_KEY_new.html
pub struct EcKey<T>;
/// Reference to [`EcKey`]
@ -526,41 +473,30 @@ where
/// Serializes the private key to a PEM-encoded ECPrivateKey structure.
///
/// The output will have a header of `-----BEGIN EC PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_ECPrivateKey`].
///
/// [`PEM_write_bio_ECPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_ECPrivateKey.html
#[corresponds(PEM_write_bio_ECPrivateKey)]
private_key_to_pem,
/// Serializes the private key to a PEM-encoded encrypted ECPrivateKey structure.
///
/// The output will have a header of `-----BEGIN EC PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_ECPrivateKey`].
///
/// [`PEM_write_bio_ECPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_ECPrivateKey.html
#[corresponds(PEM_write_bio_ECPrivateKey)]
private_key_to_pem_passphrase,
ffi::PEM_write_bio_ECPrivateKey
}
to_der! {
/// Serializes the private key into a DER-encoded ECPrivateKey structure.
///
/// This corresponds to [`i2d_ECPrivateKey`].
///
/// [`i2d_ECPrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/d2i_ECPrivate_key.html
#[corresponds(i2d_ECPrivateKey)]
private_key_to_der,
ffi::i2d_ECPrivateKey
}
/// Return [`EcPoint`] associated with the private key
///
/// OpenSSL documentation at [`EC_KEY_get0_private_key`]
///
/// [`EC_KEY_get0_private_key`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_KEY_get0_private_key.html
#[corresponds(EC_KEY_get0_private_key)]
#[must_use]
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())
}
}
}
@ -570,14 +506,12 @@ where
T: HasPublic,
{
/// Returns the public key.
///
/// OpenSSL documentation at [`EC_KEY_get0_public_key`]
///
/// [`EC_KEY_get0_public_key`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_KEY_get0_public_key.html
#[corresponds(EC_KEY_get0_public_key)]
#[must_use]
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())
}
}
@ -585,20 +519,14 @@ where
/// Serialies the public key into a PEM-encoded SubjectPublicKeyInfo structure.
///
/// The output will have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_write_bio_EC_PUBKEY`].
///
/// [`PEM_write_bio_EC_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_EC_PUBKEY.html
#[corresponds(PEM_write_bio_EC_PUBKEY)]
public_key_to_pem,
ffi::PEM_write_bio_EC_PUBKEY
}
to_der! {
/// Serializes the public key into a DER-encoded SubjectPublicKeyInfo structure.
///
/// This corresponds to [`i2d_EC_PUBKEY`].
///
/// [`i2d_EC_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/i2d_EC_PUBKEY.html
#[corresponds(i2d_EC_PUBKEY)]
public_key_to_der,
ffi::i2d_EC_PUBKEY
}
@ -609,24 +537,19 @@ where
T: HasParams,
{
/// Return [`EcGroup`] of the `EcKey`
///
/// OpenSSL documentation at [`EC_KEY_get0_group`]
///
/// [`EC_KEY_get0_group`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_KEY_get0_group.html
#[corresponds(EC_KEY_get0_group)]
#[must_use]
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.
///
/// OpenSSL documenation at [`EC_KEY_check_key`]
///
/// [`EC_KEY_check_key`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_KEY_check_key.html
#[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())) }
}
}
@ -647,10 +570,7 @@ impl EcKey<Params> {
///
/// It will not have an associated public or private key. This kind of key is primarily useful
/// to be provided to the `set_tmp_ecdh` methods on `Ssl` and `SslContextBuilder`.
///
/// OpenSSL documenation at [`EC_KEY_new_by_curve_name`]
///
/// [`EC_KEY_new_by_curve_name`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_KEY_new_by_curve_name.html
#[corresponds(EC_KEY_new_by_curve_name)]
pub fn from_curve_name(nid: Nid) -> Result<EcKey<Params>, ErrorStack> {
unsafe {
init();
@ -659,10 +579,7 @@ impl EcKey<Params> {
}
/// Constructs an `EcKey` corresponding to a curve.
///
/// This corresponds to [`EC_KEY_set_group`].
///
/// [`EC_KEY_set_group`]: https://www.openssl.org/docs/man1.1.0/crypto/EC_KEY_new.html
#[corresponds(EC_KEY_set_group)]
pub fn from_group(group: &EcGroupRef) -> Result<EcKey<Params>, ErrorStack> {
unsafe {
cvt_p(ffi::EC_KEY_new())
@ -743,10 +660,7 @@ impl EcKey<Public> {
/// Decodes a PEM-encoded SubjectPublicKeyInfo structure containing a EC key.
///
/// The input should have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_read_bio_EC_PUBKEY`].
///
/// [`PEM_read_bio_EC_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_EC_PUBKEY.html
#[corresponds(PEM_read_bio_EC_PUBKEY)]
public_key_from_pem,
EcKey<Public>,
ffi::PEM_read_bio_EC_PUBKEY
@ -754,10 +668,7 @@ impl EcKey<Public> {
from_der! {
/// Decodes a DER-encoded SubjectPublicKeyInfo structure containing a EC key.
///
/// This corresponds to [`d2i_EC_PUBKEY`].
///
/// [`d2i_EC_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/d2i_EC_PUBKEY.html
#[corresponds(d2i_EC_PUBKEY)]
public_key_from_der,
EcKey<Public>,
ffi::d2i_EC_PUBKEY,
@ -811,15 +722,13 @@ impl EcKey<Private> {
/// Deserializes a private key from a PEM-encoded ECPrivateKey structure.
///
/// The input should have a header of `-----BEGIN EC PRIVATE KEY-----`.
///
/// This corresponds to `PEM_read_bio_ECPrivateKey`.
#[corresponds(PEM_read_bio_ECPrivateKey)]
private_key_from_pem,
/// Deserializes a private key from a PEM-encoded encrypted ECPrivateKey structure.
///
/// The input should have a header of `-----BEGIN EC PRIVATE KEY-----`.
///
/// This corresponds to `PEM_read_bio_ECPrivateKey`.
#[corresponds(PEM_read_bio_ECPrivateKey)]
private_key_from_pem_passphrase,
/// Deserializes a private key from a PEM-encoded encrypted ECPrivateKey structure.
@ -827,8 +736,7 @@ impl EcKey<Private> {
/// The callback should fill the password into the provided buffer and return its length.
///
/// The input should have a header of `-----BEGIN EC PRIVATE KEY-----`.
///
/// This corresponds to `PEM_read_bio_ECPrivateKey`.
#[corresponds(PEM_read_bio_ECPrivateKey)]
private_key_from_pem_callback,
EcKey<Private>,
ffi::PEM_read_bio_ECPrivateKey
@ -836,10 +744,7 @@ impl EcKey<Private> {
from_der! {
/// Decodes a DER-encoded elliptic curve private key structure.
///
/// This corresponds to [`d2i_ECPrivateKey`].
///
/// [`d2i_ECPrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/d2i_ECPrivate_key.html
#[corresponds(d2i_ECPrivateKey)]
private_key_from_der,
EcKey<Private>,
ffi::d2i_ECPrivateKey,
@ -932,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());
}
@ -944,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,8 @@
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;
@ -26,46 +27,37 @@ foreign_type_and_impl_send_sync! {
impl EcdsaSig {
/// Computes a digital signature of the hash value `data` using the private EC key eckey.
///
/// OpenSSL documentation at [`ECDSA_do_sign`]
///
/// [`ECDSA_do_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/ECDSA_do_sign.html
#[corresponds(ECDSA_do_sign)]
pub fn sign<T>(data: &[u8], eckey: &EcKeyRef<T>) -> Result<EcdsaSig, ErrorStack>
where
T: HasPrivate,
{
unsafe {
assert!(data.len() <= c_int::max_value() as usize);
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))
}
}
/// Returns a new `EcdsaSig` by setting the `r` and `s` values associated with a
/// ECDSA signature.
///
/// OpenSSL documentation at [`ECDSA_SIG_set0`]
///
/// [`ECDSA_SIG_set0`]: https://www.openssl.org/docs/man1.1.0/crypto/ECDSA_SIG_set0.html
#[corresponds(ECDSA_SIG_set0)]
pub fn from_private_components(r: BigNum, s: BigNum) -> Result<EcdsaSig, ErrorStack> {
unsafe {
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))
}
}
from_der! {
/// Decodes a DER-encoded ECDSA signature.
///
/// This corresponds to [`d2i_ECDSA_SIG`].
///
/// [`d2i_ECDSA_SIG`]: https://www.openssl.org/docs/man1.1.0/crypto/d2i_ECDSA_SIG.html
#[corresponds(d2i_ECDSA_SIG)]
from_der,
EcdsaSig,
ffi::d2i_ECDSA_SIG,
@ -76,28 +68,22 @@ impl EcdsaSig {
impl EcdsaSigRef {
to_der! {
/// Serializes the ECDSA signature into a DER-encoded ECDSASignature structure.
///
/// This corresponds to [`i2d_ECDSA_SIG`].
///
/// [`i2d_ECDSA_SIG`]: https://www.openssl.org/docs/man1.1.0/crypto/i2d_ECDSA_SIG.html
#[corresponds(i2d_ECDSA_SIG)]
to_der,
ffi::i2d_ECDSA_SIG
}
/// Verifies if the signature is a valid ECDSA signature using the given public key.
///
/// OpenSSL documentation at [`ECDSA_do_verify`]
///
/// [`ECDSA_do_verify`]: https://www.openssl.org/docs/man1.1.0/crypto/ECDSA_do_verify.html
#[corresponds(ECDSA_do_verify)]
pub fn verify<T>(&self, data: &[u8], eckey: &EcKeyRef<T>) -> Result<bool, ErrorStack>
where
T: HasPublic,
{
unsafe {
assert!(data.len() <= c_int::max_value() as usize);
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(),
))
@ -106,28 +92,24 @@ impl EcdsaSigRef {
}
/// Returns internal component: `r` of an `EcdsaSig`. (See X9.62 or FIPS 186-2)
///
/// OpenSSL documentation at [`ECDSA_SIG_get0`]
///
/// [`ECDSA_SIG_get0`]: https://www.openssl.org/docs/man1.1.0/crypto/ECDSA_SIG_get0.html
#[corresponds(ECDSA_SIG_get0)]
#[must_use]
pub fn r(&self) -> &BigNumRef {
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())
}
}
/// Returns internal components: `s` of an `EcdsaSig`. (See X9.62 or FIPS 186-2)
///
/// OpenSSL documentation at [`ECDSA_SIG_get0`]
///
/// [`ECDSA_SIG_get0`]: https://www.openssl.org/docs/man1.1.0/crypto/ECDSA_SIG_get0.html
#[corresponds(ECDSA_SIG_get0)]
#[must_use]
pub fn s(&self) -> &BigNumRef {
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

@ -15,10 +15,12 @@
//! Err(e) => println!("Parsing Error: {:?}", e),
//! }
//! ```
use libc::{c_char, c_uint};
use libc::{c_char, c_int, c_uint};
use openssl_macros::corresponds;
use std::borrow::Cow;
use std::error;
use std::ffi::CStr;
use std::ffi::CString;
use std::fmt;
use std::io;
use std::ptr;
@ -26,6 +28,8 @@ use std::str;
use crate::ffi;
pub use crate::ffi::ErrLib;
/// Collection of [`Error`]s from OpenSSL.
///
/// [`Error`]: struct.Error.html
@ -33,7 +37,12 @@ use crate::ffi;
pub struct ErrorStack(Vec<Error>);
impl ErrorStack {
/// Returns the contents of the OpenSSL error stack.
/// 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 {
let mut vec = vec![];
while let Some(err) = Error::get() {
@ -43,15 +52,37 @@ impl ErrorStack {
}
/// Pushes the errors back onto the OpenSSL error stack.
#[corresponds(ERR_put_error)]
pub fn put(&self) {
for error in self.errors() {
error.put();
}
}
/// Used to report errors from the Rust crate
#[cold]
pub(crate) fn internal_error(err: impl error::Error) -> Self {
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() {
unsafe {
ffi::ERR_clear_error();
}
}
}
impl ErrorStack {
/// Returns the errors in the stack.
#[must_use]
pub fn errors(&self) -> &[Error] {
&self.0
}
@ -69,7 +100,13 @@ impl fmt::Display for ErrorStack {
fmt.write_str(" ")?;
}
first = false;
write!(fmt, "[{}]", err.reason().unwrap_or("unknown reason"))?;
write!(
fmt,
"[{}]",
err.reason()
.or_else(|| err.library())
.unwrap_or("unknown reason")
)?;
}
Ok(())
}
@ -79,7 +116,7 @@ impl error::Error for ErrorStack {}
impl From<ErrorStack> for io::Error {
fn from(e: ErrorStack) -> io::Error {
io::Error::new(io::ErrorKind::Other, e)
io::Error::other(e)
}
}
@ -89,20 +126,32 @@ impl From<ErrorStack> for fmt::Error {
}
}
/// An error reported from OpenSSL.
/// A detailed error reported as part of an [`ErrorStack`].
#[derive(Clone)]
pub struct Error {
code: c_uint,
file: *const c_char,
line: c_uint,
data: Option<Cow<'static, str>>,
data: Data,
}
#[derive(Clone)]
enum Data {
None,
CString(CString),
String(String),
Static(&'static str),
}
unsafe impl Sync for Error {}
unsafe impl Send for Error {}
static BORING_INTERNAL: &CStr = c"boring-rust";
impl Error {
/// Returns the first error on the OpenSSL error stack.
/// Pops the first error off the OpenSSL error stack.
#[must_use = "Use ErrorStack::clear() to drop the error stack"]
#[corresponds(ERR_get_error_line_data)]
pub fn get() -> Option<Error> {
unsafe {
ffi::init();
@ -117,12 +166,9 @@ impl Error {
// The memory referenced by data is only valid until that slot is overwritten
// in the error stack, so we'll need to copy it off if it's dynamic
let data = if flags & ffi::ERR_FLAG_STRING != 0 {
let bytes = CStr::from_ptr(data as *const _).to_bytes();
let data = str::from_utf8(bytes).unwrap();
let data = Cow::Owned(data.to_string());
Some(data)
Data::CString(CStr::from_ptr(data.cast()).to_owned())
} else {
None
Data::None
};
Some(Error {
code,
@ -136,6 +182,7 @@ impl Error {
}
/// Pushes the error back onto the OpenSSL error stack.
#[corresponds(ERR_put_error)]
pub fn put(&self) {
unsafe {
ffi::ERR_put_error(
@ -145,104 +192,165 @@ impl Error {
self.file,
self.line,
);
let ptr = match self.data {
Some(Cow::Borrowed(data)) => Some(data.as_ptr() as *mut c_char),
Some(Cow::Owned(ref data)) => {
let ptr = ffi::OPENSSL_malloc((data.len() + 1) as _) as *mut c_char;
if ptr.is_null() {
None
} else {
ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len());
*ptr.add(data.len()) = 0;
Some(ptr)
}
}
None => None,
};
if let Some(ptr) = ptr {
ffi::ERR_add_error_data(1, ptr);
if let Some(cstr) = self.data_cstr() {
ffi::ERR_add_error_data(1, cstr.as_ptr().cast_mut());
}
}
}
/// Returns the raw OpenSSL error code for this error.
/// Get `{lib}_R_{reason}` reason code for the given library, or `None` if the error is from a different library.
///
/// Libraries are identified by [`ERR_LIB_{name}`(ffi::ERR_LIB_SSL) constants.
#[inline]
#[must_use]
#[track_caller]
pub fn library_reason(&self, library_code: ErrLib) -> Option<c_int> {
debug_assert!(library_code.0 < ffi::ERR_NUM_LIBS.0);
(self.library_code() == library_code.0 as c_int).then_some(self.reason_code())
}
/// Returns a raw OpenSSL **packed** error code for this error, which **can't be reliably compared to any error constant**.
///
/// Use [`Error::library_code()`] and [`Error::library_reason()`] instead.
/// Packed error codes are different than [SSL error codes](crate::ssl::ErrorCode).
#[must_use]
#[deprecated(note = "use library_reason() to compare error codes")]
pub fn code(&self) -> c_uint {
self.code
}
/// Returns the name of the library reporting the error, if available.
#[must_use]
pub fn library(&self) -> Option<&'static str> {
if self.is_internal() {
return None;
}
unsafe {
let cstr = ffi::ERR_lib_error_string(self.code);
if cstr.is_null() {
return None;
}
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
Some(str::from_utf8(bytes).unwrap())
CStr::from_ptr(cstr.cast())
.to_str()
.ok()
.filter(|&msg| msg != "unknown library")
}
}
/// Returns the name of the function reporting the error.
/// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`).
///
/// Error [reason codes](Error::library_reason) are not globally unique, but scoped to each library.
#[must_use]
pub fn library_code(&self) -> c_int {
ffi::ERR_GET_LIB(self.code)
}
/// Returns `None`. Boring doesn't use function codes.
pub fn function(&self) -> Option<&'static str> {
unsafe {
let cstr = ffi::ERR_func_error_string(self.code);
if cstr.is_null() {
return None;
}
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
Some(str::from_utf8(bytes).unwrap())
}
None
}
/// Returns the reason for the error.
pub fn reason(&self) -> Option<&'static str> {
#[must_use]
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() {
return None;
}
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
Some(str::from_utf8(bytes).unwrap())
CStr::from_ptr(cstr.cast()).to_str().ok()
}
}
/// Returns [library-specific](Error::library_code) reason code corresponding to some of the `{lib}_R_{reason}` constants.
///
/// Reason codes are ambiguous, and different libraries reuse the same numeric values for different errors.
/// Use [`Error::library_reason`] to compare error codes.
///
/// For `ERR_LIB_SYS` the reason code is `errno`. `ERR_LIB_USER` can use any values.
/// Other libraries may use [`ERR_R_*`](ffi::ERR_R_FATAL) or their own codes.
#[must_use]
pub fn reason_code(&self) -> c_int {
ffi::ERR_GET_REASON(self.code)
}
/// Returns the name of the source file which encountered the error.
#[must_use]
pub fn file(&self) -> &'static str {
unsafe {
assert!(!self.file.is_null());
let bytes = CStr::from_ptr(self.file as *const _).to_bytes();
str::from_utf8(bytes).unwrap()
if self.file.is_null() {
return "";
}
CStr::from_ptr(self.file.cast())
.to_str()
.unwrap_or_default()
}
}
/// Returns the line in the source file which encountered the error.
///
/// 0 if unknown
#[allow(clippy::unnecessary_cast)]
#[must_use]
pub fn line(&self) -> u32 {
self.line as u32
}
/// Returns additional data describing the error.
#[allow(clippy::option_as_ref_deref)]
#[must_use]
pub fn data(&self) -> Option<&str> {
self.data.as_ref().map(|s| &**s)
match &self.data {
Data::None => None,
Data::CString(cstring) => cstring.to_str().ok(),
Data::String(s) => Some(s),
Data::Static(s) => Some(s),
}
}
#[must_use]
fn data_cstr(&self) -> Option<Cow<'_, CStr>> {
let s = match &self.data {
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)
}
fn new_internal(msg: Data) -> Self {
Self {
code: ffi::ERR_PACK(ffi::ERR_LIB_NONE.0 as _, 0, 0) as _,
file: BORING_INTERNAL.as_ptr(),
line: 0,
data: msg,
}
}
fn is_internal(&self) -> bool {
std::ptr::eq(self.file, BORING_INTERNAL.as_ptr())
}
}
impl fmt::Debug for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let mut builder = fmt.debug_struct("Error");
builder.field("code", &self.code());
if let Some(library) = self.library() {
builder.field("library", &library);
builder.field("code", &self.code);
if !self.is_internal() {
if let Some(library) = self.library() {
builder.field("library", &library);
}
builder.field("library_code", &self.library_code());
if let Some(reason) = self.reason() {
builder.field("reason", &reason);
}
builder.field("reason_code", &self.reason_code());
builder.field("file", &self.file());
builder.field("line", &self.line());
}
if let Some(function) = self.function() {
builder.field("function", &function);
}
if let Some(reason) = self.reason() {
builder.field("reason", &reason);
}
builder.field("file", &self.file());
builder.field("line", &self.line());
if let Some(data) = self.data() {
builder.field("data", &data);
}
@ -256,7 +364,7 @@ impl fmt::Display for Error {
fmt,
"{}\n\nCode: {:08X}\nLoc: {}:{}",
self.reason().unwrap_or("unknown TLS error"),
self.code(),
&self.code,
self.file(),
self.line()
)
@ -264,3 +372,14 @@ impl fmt::Display for Error {
}
impl error::Error for Error {}
#[test]
fn internal_err() {
let e = ErrorStack::internal_error(io::Error::other("hello, boring"));
assert_eq!(1, e.errors().len());
assert!(e.to_string().contains("hello, boring"), "{e} {e:?}");
e.put();
let e = ErrorStack::get();
assert!(e.to_string().contains("hello, boring"), "{e} {e:?}");
}

View File

@ -21,11 +21,13 @@ impl<T, U> Index<T, U> {
/// # Safety
///
/// The caller must ensure that the index correctly maps to a `U` value stored in a `T`.
#[must_use]
pub unsafe fn from_raw(idx: c_int) -> Index<T, U> {
Index(idx, PhantomData)
}
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_raw(&self) -> c_int {
self.0
}

View File

@ -4,18 +4,19 @@
//!
//! [OpenSSL's documentation]: https://www.openssl.org/docs/fips/UserGuide-2.0.pdf
use crate::ffi;
use openssl_macros::corresponds;
/// Determines if the library is running in the FIPS 140-2 mode of operation.
///
/// This corresponds to `FIPS_mode`.
#[corresponds(FIPS_mode)]
#[must_use]
pub fn enabled() -> bool {
unsafe { ffi::FIPS_mode() != 0 }
}
#[test]
fn is_enabled() {
#[cfg(any(feature = "fips", feature = "fips-link-precompiled"))]
#[cfg(feature = "fips")]
assert!(enabled());
#[cfg(not(any(feature = "fips", feature = "fips-link-precompiled")))]
#[cfg(not(feature = "fips"))]
assert!(!enabled());
}

View File

@ -1,5 +1,5 @@
use crate::ffi;
use std::convert::TryInto;
use openssl_macros::corresponds;
use std::ffi::c_uint;
use std::fmt;
use std::io;
use std::io::prelude::*;
@ -7,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)]
@ -20,15 +22,14 @@ impl MessageDigest {
/// # Safety
///
/// The caller must ensure the pointer is valid.
#[must_use]
pub unsafe fn from_ptr(x: *const ffi::EVP_MD) -> Self {
MessageDigest(x)
}
/// Returns the `MessageDigest` corresponding to an `Nid`.
///
/// This corresponds to [`EVP_get_digestbynid`].
///
/// [`EVP_get_digestbynid`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_DigestInit.html
#[corresponds(EVP_get_digestbynid)]
#[must_use]
pub fn from_nid(type_: Nid) -> Option<MessageDigest> {
unsafe {
let ptr = ffi::EVP_get_digestbynid(type_.as_raw());
@ -40,47 +41,57 @@ impl MessageDigest {
}
}
#[must_use]
pub fn md5() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_md5()) }
}
#[must_use]
pub fn sha1() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_sha1()) }
}
#[must_use]
pub fn sha224() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_sha224()) }
}
#[must_use]
pub fn sha256() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_sha256()) }
}
#[must_use]
pub fn sha384() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_sha384()) }
}
#[must_use]
pub fn sha512() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_sha512()) }
}
#[must_use]
pub fn sha512_256() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_sha512_256()) }
}
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_ptr(&self) -> *const ffi::EVP_MD {
self.0
}
/// The size of the digest in bytes.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn size(&self) -> usize {
unsafe { ffi::EVP_MD_size(self.0) }
}
/// The name of the digest.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn type_(&self) -> Nid {
Nid::from_raw(unsafe { ffi::EVP_MD_type(self.0) })
}
@ -185,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(),
))?;
}
@ -199,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,
@ -209,7 +220,7 @@ impl Hasher {
self.state = Finalized;
Ok(DigestBytes {
buf,
len: len as usize,
len: try_int(len)?,
})
}
}
@ -309,7 +320,7 @@ impl DerefMut for DigestBytes {
impl AsRef<[u8]> for DigestBytes {
#[inline]
fn as_ref(&self) -> &[u8] {
self.deref()
self
}
}
@ -333,6 +344,46 @@ pub fn hash_xof(t: MessageDigest, data: &[u8], buf: &mut [u8]) -> Result<(), Err
h.finish_xof(buf)
}
/// Computes HMAC with SHA-256 digest.
pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; 32], ErrorStack> {
hmac(MessageDigest::sha256(), key, data)
}
/// Computes HMAC with SHA-512 digest.
pub fn hmac_sha512(key: &[u8], data: &[u8]) -> Result<[u8; 64], ErrorStack> {
hmac(MessageDigest::sha512(), key, data)
}
/// Computes HMAC with SHA-1 digest.
pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<[u8; 20], ErrorStack> {
hmac(MessageDigest::sha1(), key, data)
}
pub(crate) fn hmac<const N: usize>(
digest: MessageDigest,
key: &[u8],
data: &[u8],
) -> Result<[u8; N], ErrorStack> {
let mut out = [0u8; N];
let mut out_len: c_uint = 0;
cvt_p(unsafe {
ffi::HMAC(
digest.as_ptr(),
key.as_ptr().cast(),
key.len(),
data.as_ptr(),
data.len(),
out.as_mut_ptr(),
&mut out_len,
)
})?;
assert_eq!(out_len as usize, N);
Ok(out)
}
#[cfg(test)]
mod tests {
use hex::{self, FromHex};
@ -371,9 +422,52 @@ mod tests {
),
];
#[test]
fn test_hmac_sha256() {
let hmac = hmac_sha256(b"That's a secret".as_slice(), b"Hello world!".as_slice()).unwrap();
assert_eq!(
hmac,
[
0x50, 0xbb, 0x7d, 0xd2, 0xb8, 0xd2, 0x51, 0x5d, 0xb4, 0x2b, 0x70, 0xc3, 0x0b, 0xfd,
0xf5, 0x4c, 0x38, 0xa7, 0xae, 0x99, 0x07, 0xe5, 0x80, 0x0f, 0x8b, 0xe8, 0x34, 0x83,
0x55, 0x5f, 0xd0, 0xd4
]
);
}
#[test]
fn test_hmac_sha512() {
let hmac = hmac_sha512(b"That's a secret".as_slice(), b"Hello world!".as_slice()).unwrap();
assert_eq!(
hmac,
[
0xc2, 0x7a, 0x7f, 0x7c, 0x17, 0x4c, 0x87, 0x70, 0x7f, 0x8c, 0xb7, 0x90, 0x01, 0xba,
0x23, 0x0e, 0xb7, 0xd6, 0x1a, 0xfd, 0x50, 0xea, 0x40, 0x43, 0x5f, 0x03, 0x25, 0x5a,
0x22, 0xb7, 0x8d, 0x0e, 0xba, 0x0d, 0x47, 0xb8, 0xef, 0xaa, 0xbf, 0xb1, 0xe7, 0xad,
0xc5, 0xd1, 0xe5, 0xba, 0x4d, 0xa5, 0xd1, 0xbb, 0x5e, 0xe3, 0xc7, 0x27, 0x0c, 0x57,
0x76, 0xd4, 0x2f, 0xb6, 0x5c, 0x21, 0xb7, 0x3a
]
);
}
#[test]
fn test_hmac_sha1() {
let hmac = hmac_sha1(b"That's a secret".as_slice(), b"Hello world!".as_slice()).unwrap();
assert_eq!(
hmac,
[
0xe1, 0x06, 0x76, 0x46, 0x3b, 0x82, 0x67, 0xa1, 0xae, 0xe5, 0x1c, 0xfa, 0xee, 0x36,
0x1d, 0x4b, 0xd4, 0x41, 0x6e, 0x37
]
);
}
#[test]
fn test_md5() {
for test in MD5_TESTS.iter() {
for test in &MD5_TESTS {
hash_test(MessageDigest::md5(), test);
}
}
@ -381,7 +475,7 @@ mod tests {
#[test]
fn test_md5_recycle() {
let mut h = Hasher::new(MessageDigest::md5()).unwrap();
for test in MD5_TESTS.iter() {
for test in &MD5_TESTS {
hash_recycle_test(&mut h, test);
}
}
@ -432,7 +526,7 @@ mod tests {
fn test_sha1() {
let tests = [("616263", "a9993e364706816aba3e25717850c26c9cd0d89d")];
for test in tests.iter() {
for test in &tests {
hash_test(MessageDigest::sha1(), test);
}
}
@ -444,7 +538,7 @@ mod tests {
"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7",
)];
for test in tests.iter() {
for test in &tests {
hash_test(MessageDigest::sha224(), test);
}
}
@ -456,7 +550,7 @@ mod tests {
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
)];
for test in tests.iter() {
for test in &tests {
hash_test(MessageDigest::sha256(), test);
}
}
@ -469,7 +563,7 @@ mod tests {
192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
)];
for test in tests.iter() {
for test in &tests {
hash_test(MessageDigest::sha512(), test);
}
}
@ -481,7 +575,7 @@ mod tests {
"53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23",
)];
for test in tests.iter() {
for test in &tests {
hash_test(MessageDigest::sha512_256(), test);
}
}

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());
}
}

31
boring/src/hpke.rs Normal file
View File

@ -0,0 +1,31 @@
use crate::error::ErrorStack;
use crate::{cvt_0i, cvt_p, ffi};
use foreign_types::ForeignType;
foreign_type_and_impl_send_sync! {
type CType = ffi::EVP_HPKE_KEY;
fn drop = ffi::EVP_HPKE_KEY_free;
pub struct HpkeKey;
}
impl HpkeKey {
/// Allocates and initializes a key with the `EVP_HPKE_KEY` type using the
/// `EVP_hpke_x25519_hkdf_sha256` KEM algorithm.
pub fn dhkem_p256_sha256(pkey: &[u8]) -> Result<HpkeKey, ErrorStack> {
unsafe {
ffi::init();
let hpke = cvt_p(ffi::EVP_HPKE_KEY_new()).map(|p| HpkeKey::from_ptr(p))?;
cvt_0i(ffi::EVP_HPKE_KEY_init(
hpke.as_ptr(),
ffi::EVP_hpke_x25519_hkdf_sha256(),
pkey.as_ptr(),
pkey.len(),
))?;
Ok(hpke)
}
}
}

View File

@ -18,18 +18,26 @@
//!
//! # Compilation and linking options
//!
//! ## Environment variables
//!
//! This crate uses various environment variables to tweak how boring is built. The variables
//! are all prefixed by `BORING_BSSL_` for non-FIPS builds, and by `BORING_BSSL_FIPS_` for FIPS builds.
//!
//! ## Support for pre-built binaries or custom source
//!
//! While this crate can build BoringSSL on its own, you may want to provide pre-built binaries instead.
//! To do so, specify the environment variable `BORING_BSSL_PATH` with the path to the binaries.
//! To do so, specify the environment variable `BORING_BSSL{,_FIPS}_PATH` with the path to the binaries.
//!
//! You can also provide specific headers by setting `BORING_BSSL_INCLUDE_PATH`.
//! You can also provide specific headers by setting `BORING_BSSL{,_FIPS}_INCLUDE_PATH`.
//!
//! _Notes_: The crate will look for headers in the `$BORING_BSSL_INCLUDE_PATH/openssl/` folder, make sure to place your headers there.
//! _Notes_: The crate will look for headers in the`$BORING_BSSL{,_FIPS}_INCLUDE_PATH/openssl/`
//! folder, make sure to place your headers there.
//!
//! In alternative a different path for the BoringSSL source code directory can be specified by setting `BORING_BSSL_SOURCE_PATH` which will automatically be compiled during the build process.
//! In alternative a different path for the BoringSSL source code directory can be specified by setting
//! `BORING_BSSL{,_FIPS}_SOURCE_PATH` which will automatically be compiled during the build process.
//!
//! _Warning_: When providing a different version of BoringSSL make sure to use a compatible one, the crate relies on the presence of certain functions.
//! _Warning_: When providing a different version of BoringSSL make sure to use a compatible one, the
//! crate relies on the presence of certain functions.
//!
//! ## Building with a FIPS-validated module
//!
@ -44,11 +52,22 @@
//! ```
//!
//! ## Linking current BoringSSL version with precompiled FIPS-validated module (`bcm.o`)
//!
//! It's possible to link latest supported version of BoringSSL with FIPS-validated crypto module
//! (`bcm.o`). To enable this compilation option one should enable `fips-link-precompiled`
//! compilation feature and provide a `BORING_SSL_PRECOMPILED_BCM_O` env variable with a path to the
//! compilation feature and provide a `BORING_BSSL_FIPS_PRECOMPILED_BCM_O` env variable with a path to the
//! precompiled FIPS-validated `bcm.o` module.
//!
//! Note that `BORING_BSSL_PRECOMPILED_BCM_O` is never used, as linking BoringSSL with precompiled non-FIPS
//! module is not supported.
//!
//! ## Linking with a C++ standard library
//!
//! Recent versions of boringssl require some C++ standard library features, so boring needs to link
//! with a STL implementation. This can be controlled using the BORING_BSSL_RUST_CPPLIB variable. If
//! no library is specified, libc++ is used on macOS and iOS whereas libstdc++ is used on other Unix
//! systems.
//!
//! # Optional patches
//!
//! ## Raw Public Key
@ -62,21 +81,21 @@
//! support by turning on `post-quantum` compilation feature.
//!
//! Upstream BoringSSL support the post-quantum hybrid key agreement `X25519Kyber768Draft00`. Most
//! users should stick to that one. Enabling this feature, adds a few other post-quantum key
//! users should stick to that one for now. Enabling this feature, adds a few other post-quantum key
//! agreements:
//!
//! - `X25519MLKEM768` is the successor of `X25519Kyber768Draft00`. We expect servers to switch
//! before the end of 2024.
//! - `X25519Kyber768Draft00Old` is the same as `X25519Kyber768Draft00`, but under its old codepoint.
//! -`X25519Kyber512Draft00`. Similar to `X25519Kyber768Draft00`, but uses level 1 parameter set for
//! Kyber. Not recommended. It's useful to test whether the shorter ClientHello upsets fewer middle
//! boxes.
//! - `X25519Kyber512Draft00`. Similar to `X25519Kyber768Draft00`, but uses level 1 parameter set for
//! Kyber. Not recommended. It's useful to test whether the shorter ClientHello upsets fewer middle
//! boxes.
//! - `P256Kyber768Draft00`. Similar again to `X25519Kyber768Draft00`, but uses P256 as classical
//! part. It uses a non-standard codepoint. Not recommended.
//! part. It uses a non-standard codepoint. Not recommended.
//!
//! Presently all these key agreements are deployed by Cloudflare, but we do not guarantee continued
//! support for them.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[macro_use]
extern crate bitflags;
#[macro_use]
@ -87,11 +106,12 @@ extern crate libc;
#[cfg(test)]
extern crate hex;
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]
@ -114,7 +134,11 @@ pub mod error;
pub mod ex_data;
pub mod fips;
pub mod hash;
pub mod hmac;
pub mod hpke;
pub mod memcmp;
#[cfg(feature = "mlkem")]
pub mod mlkem;
pub mod nid;
pub mod pkcs12;
pub mod pkcs5;
@ -139,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(())
}
}
@ -155,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())
@ -170,3 +201,25 @@ fn cvt_n(r: c_int) -> Result<c_int, ErrorStack> {
Ok(r)
}
}
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,
_ad: *mut ffi::CRYPTO_EX_DATA,
_idx: c_int,
_argl: c_long,
_argp: *mut c_void,
) {
if !ptr.is_null() {
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_value() 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)
@ -108,7 +108,7 @@ macro_rules! from_der {
pub fn $n(der: &[u8]) -> Result<$t, crate::error::ErrorStack> {
unsafe {
crate::ffi::init();
let len = ::std::cmp::min(der.len(), <$len_ty>::max_value() as usize) as $len_ty;
let len = ::std::cmp::min(der.len(), <$len_ty>::MAX as usize) as $len_ty;
crate::cvt_p($f(::std::ptr::null_mut(), &mut der.as_ptr(), len))
.map(|p| ::foreign_types::ForeignType::from_ptr(p))
}

View File

@ -12,7 +12,7 @@
//!
//! # Examples
//!
//! To perform a constant-time comparision of two arrays of the same length but different
//! To perform a constant-time comparison of two arrays of the same length but different
//! values:
//!
//! ```
@ -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.
///
@ -44,7 +43,7 @@ use libc::size_t;
///
/// # Examples
///
/// To perform a constant-time comparision of two arrays of the same length but different
/// To perform a constant-time comparison of two arrays of the same length but different
/// values:
///
/// ```
@ -61,15 +60,10 @@ use libc::size_t;
/// assert!(!eq(&a, &b));
/// assert!(!eq(&a, &c));
/// ```
#[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
}
@ -87,6 +81,6 @@ mod tests {
#[test]
#[should_panic]
fn test_diff_lens() {
eq(&[], &[1]);
let _ = eq(&[], &[1]);
}
}

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,7 @@
//! 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;
use std::str;
@ -50,20 +51,22 @@ pub struct Nid(c_int);
#[allow(non_snake_case)]
impl Nid {
/// Create a `Nid` from an integer representation.
#[must_use]
pub fn from_raw(raw: c_int) -> Nid {
Nid(raw)
}
/// Return the integer representation of a `Nid`.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_raw(&self) -> c_int {
self.0
}
/// Returns the `Nid`s of the digest and public key algorithms associated with a signature ID.
///
/// This corresponds to `OBJ_find_sigid_algs`.
#[corresponds(OBJ_find_sigid_algs)]
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn signature_algorithms(&self) -> Option<SignatureAlgorithms> {
unsafe {
let mut digest = 0;
@ -80,26 +83,26 @@ impl Nid {
}
/// Return the string representation of a `Nid` (long)
/// This corresponds to [`OBJ_nid2ln`]
///
/// [`OBJ_nid2ln`]: https://www.openssl.org/docs/man1.1.0/crypto/OBJ_nid2ln.html
#[corresponds(OBJ_nid2ln)]
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn long_name(&self) -> Result<&'static str, ErrorStack> {
unsafe {
cvt_p(ffi::OBJ_nid2ln(self.0) as *mut c_char)
.map(|nameptr| str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).unwrap())
let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0).cast_mut())?;
CStr::from_ptr(nameptr)
.to_str()
.map_err(ErrorStack::internal_error)
}
}
/// Return the string representation of a `Nid` (short)
/// This corresponds to [`OBJ_nid2sn`]
///
/// [`OBJ_nid2sn`]: https://www.openssl.org/docs/man1.1.0/crypto/OBJ_nid2sn.html
#[corresponds(OBJ_nid2sn)]
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn short_name(&self) -> Result<&'static str, ErrorStack> {
unsafe {
cvt_p(ffi::OBJ_nid2sn(self.0) as *mut c_char)
.map(|nameptr| str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).unwrap())
let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0).cast_mut())?;
CStr::from_ptr(nameptr)
.to_str()
.map_err(ErrorStack::internal_error)
}
}
@ -1050,6 +1053,10 @@ impl Nid {
pub const AES_128_CBC_HMAC_SHA1: Nid = Nid(ffi::NID_aes_128_cbc_hmac_sha1);
pub const AES_192_CBC_HMAC_SHA1: Nid = Nid(ffi::NID_aes_192_cbc_hmac_sha1);
pub const AES_256_CBC_HMAC_SHA1: Nid = Nid(ffi::NID_aes_256_cbc_hmac_sha1);
pub const AUTH_RSA: Nid = Nid(ffi::NID_auth_rsa);
pub const AUTH_ECDSA: Nid = Nid(ffi::NID_auth_ecdsa);
pub const AUTH_PSK: Nid = Nid(ffi::NID_auth_psk);
pub const AUTH_ANY: Nid = Nid(ffi::NID_auth_any);
}
#[cfg(test)]

View File

@ -3,13 +3,14 @@
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::c_int;
use openssl_macros::corresponds;
use std::ffi::CString;
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};
@ -25,41 +26,48 @@ foreign_type_and_impl_send_sync! {
impl Pkcs12Ref {
to_der! {
/// Serializes the `Pkcs12` to its standard DER encoding.
///
/// This corresponds to [`i2d_PKCS12`].
///
/// [`i2d_PKCS12`]: https://www.openssl.org/docs/manmaster/man3/i2d_PKCS12.html
#[corresponds(i2d_PKCS12)]
to_der,
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()).unwrap();
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 })
}
}
}
@ -67,10 +75,7 @@ impl Pkcs12Ref {
impl Pkcs12 {
from_der! {
/// Deserializes a DER-encoded PKCS#12 archive.
///
/// This corresponds to [`d2i_PKCS12`].
///
/// [`d2i_PKCS12`]: https://www.openssl.org/docs/man1.1.0/crypto/d2i_PKCS12.html
#[corresponds(d2i_PKCS12)]
from_der,
Pkcs12,
ffi::d2i_PKCS12,
@ -85,6 +90,7 @@ impl Pkcs12 {
/// * `nid_cert` - `nid::PBE_WITHSHA1AND40BITRC2_CBC`
/// * `iter` - `2048`
/// * `mac_iter` - `2048`
#[must_use]
pub fn builder() -> Pkcs12Builder {
ffi::init();
@ -104,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,
@ -166,8 +185,8 @@ impl Pkcs12Builder {
T: HasPrivate,
{
unsafe {
let pass = CString::new(password).unwrap();
let friendly_name = CString::new(friendly_name).unwrap();
let pass = CString::new(password).map_err(ErrorStack::internal_error)?;
let friendly_name = CString::new(friendly_name).map_err(ErrorStack::internal_error)?;
let pkey = pkey.as_ptr();
let cert = cert.as_ptr();
let ca = self
@ -184,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,
@ -264,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 {
@ -32,7 +32,7 @@ pub fn bytes_to_key(
count: u32,
) -> Result<KeyIvPair, ErrorStack> {
unsafe {
assert!(data.len() <= c_int::max_value() as usize);
assert!(data.len() <= c_int::MAX as usize);
let salt_ptr = match salt {
Some(salt) => {
pub const PKCS5_SALT_LEN: c_int = 8;
@ -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_value() as usize);
assert!(salt.len() <= c_int::max_value() as usize);
assert!(key.len() <= c_int::max_value() 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,9 +40,9 @@
//! 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;
use std::ffi::CString;
use std::fmt;
use std::mem;
@ -53,9 +53,11 @@ 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_p};
use crate::{cvt, cvt_0i, cvt_p};
/// A tag type indicating that a key only has parameters.
pub enum Params {}
@ -72,6 +74,7 @@ pub struct Id(c_int);
impl Id {
pub const RSA: Id = Id(ffi::EVP_PKEY_RSA);
pub const RSAPSS: Id = Id(ffi::EVP_PKEY_RSA_PSS);
pub const DSA: Id = Id(ffi::EVP_PKEY_DSA);
pub const DH: Id = Id(ffi::EVP_PKEY_DH);
pub const EC: Id = Id(ffi::EVP_PKEY_EC);
@ -81,12 +84,14 @@ impl Id {
pub const X448: Id = Id(ffi::EVP_PKEY_X448);
/// Creates a `Id` from an integer representation.
#[must_use]
pub fn from_raw(value: c_int) -> Id {
Id(value)
}
/// Returns the integer representation of the `Id`.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_raw(&self) -> c_int {
self.0
}
@ -137,10 +142,7 @@ impl<T> ToOwned for PKeyRef<T> {
impl<T> PKeyRef<T> {
/// Returns a copy of the internal RSA key.
///
/// This corresponds to [`EVP_PKEY_get1_RSA`].
///
/// [`EVP_PKEY_get1_RSA`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_get1_RSA.html
#[corresponds(EVP_PKEY_get1_RSA)]
pub fn rsa(&self) -> Result<Rsa<T>, ErrorStack> {
unsafe {
let rsa = cvt_p(ffi::EVP_PKEY_get1_RSA(self.as_ptr()))?;
@ -149,10 +151,7 @@ impl<T> PKeyRef<T> {
}
/// Returns a copy of the internal DSA key.
///
/// This corresponds to [`EVP_PKEY_get1_DSA`].
///
/// [`EVP_PKEY_get1_DSA`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_get1_DSA.html
#[corresponds(EVP_PKEY_get1_DSA)]
pub fn dsa(&self) -> Result<Dsa<T>, ErrorStack> {
unsafe {
let dsa = cvt_p(ffi::EVP_PKEY_get1_DSA(self.as_ptr()))?;
@ -161,10 +160,7 @@ impl<T> PKeyRef<T> {
}
/// Returns a copy of the internal DH key.
///
/// This corresponds to [`EVP_PKEY_get1_DH`].
///
/// [`EVP_PKEY_get1_DH`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_get1_DH.html
#[corresponds(EVP_PKEY_get1_DH)]
pub fn dh(&self) -> Result<Dh<T>, ErrorStack> {
unsafe {
let dh = cvt_p(ffi::EVP_PKEY_get1_DH(self.as_ptr()))?;
@ -173,10 +169,7 @@ impl<T> PKeyRef<T> {
}
/// Returns a copy of the internal elliptic curve key.
///
/// This corresponds to [`EVP_PKEY_get1_EC_KEY`].
///
/// [`EVP_PKEY_get1_EC_KEY`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_get1_EC_KEY.html
#[corresponds(EVP_PKEY_get1_EC_KEY)]
pub fn ec_key(&self) -> Result<EcKey<T>, ErrorStack> {
unsafe {
let ec_key = cvt_p(ffi::EVP_PKEY_get1_EC_KEY(self.as_ptr()))?;
@ -185,19 +178,15 @@ impl<T> PKeyRef<T> {
}
/// Returns the `Id` that represents the type of this key.
///
/// This corresponds to [`EVP_PKEY_id`].
///
/// [`EVP_PKEY_id`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_id.html
#[corresponds(EVP_PKEY_id)]
#[must_use]
pub fn id(&self) -> Id {
unsafe { Id::from_raw(ffi::EVP_PKEY_id(self.as_ptr())) }
}
/// Returns the maximum size of a signature in bytes.
///
/// This corresponds to [`EVP_PKEY_size`].
///
/// [`EVP_PKEY_size`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_PKEY_size.html
#[corresponds(EVP_PKEY_size)]
#[must_use]
pub fn size(&self) -> usize {
unsafe { ffi::EVP_PKEY_size(self.as_ptr()) as usize }
}
@ -211,20 +200,14 @@ where
/// Serializes the public key into a PEM-encoded SubjectPublicKeyInfo structure.
///
/// The output will have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_write_bio_PUBKEY`].
///
/// [`PEM_write_bio_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_PUBKEY.html
#[corresponds(PEM_write_bio_PUBKEY)]
public_key_to_pem,
ffi::PEM_write_bio_PUBKEY
}
to_der! {
/// Serializes the public key into a DER-encoded SubjectPublicKeyInfo structure.
///
/// This corresponds to [`i2d_PUBKEY`].
///
/// [`i2d_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/i2d_PUBKEY.html
#[corresponds(i2d_PUBKEY)]
public_key_to_der,
ffi::i2d_PUBKEY
}
@ -233,17 +216,49 @@ where
///
/// This corresponds to the bit length of the modulus of an RSA key, and the bit length of the
/// group order for an elliptic curve key, for example.
#[must_use]
pub fn bits(&self) -> u32 {
unsafe { ffi::EVP_PKEY_bits(self.as_ptr()) as u32 }
}
/// Compares the public component of this key with another.
#[must_use]
pub fn public_eq<U>(&self, other: &PKeyRef<U>) -> bool
where
U: HasPublic,
{
unsafe { ffi::EVP_PKEY_cmp(self.as_ptr(), other.as_ptr()) == 1 }
}
/// Returns the length of the "raw" form of the public key. Only supported for certain key types.
#[corresponds(EVP_PKEY_get_raw_public_key)]
pub fn raw_public_key_len(&self) -> Result<usize, ErrorStack> {
unsafe {
let mut size = 0;
_ = cvt_0i(ffi::EVP_PKEY_get_raw_public_key(
self.as_ptr(),
std::ptr::null_mut(),
&mut size,
))?;
Ok(size)
}
}
/// Outputs a copy of the "raw" form of the public key. Only supported for certain key types.
///
/// Returns the used portion of `out`.
#[corresponds(EVP_PKEY_get_raw_public_key)]
pub fn raw_public_key<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], ErrorStack> {
unsafe {
let mut size = out.len();
_ = cvt_0i(ffi::EVP_PKEY_get_raw_public_key(
self.as_ptr(),
out.as_mut_ptr(),
&mut size,
))?;
Ok(&out[..size])
}
}
}
impl<T> PKeyRef<T>
@ -254,28 +269,19 @@ where
/// Serializes the private key to a PEM-encoded PKCS#8 PrivateKeyInfo structure.
///
/// The output will have a header of `-----BEGIN PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_PKCS8PrivateKey`].
///
/// [`PEM_write_bio_PKCS8PrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_write_bio_PKCS8PrivateKey.html
#[corresponds(PEM_write_bio_PKCS8PrivateKey)]
private_key_to_pem_pkcs8,
/// Serializes the private key to a PEM-encoded PKCS#8 EncryptedPrivateKeyInfo structure.
///
/// The output will have a header of `-----BEGIN ENCRYPTED PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_PKCS8PrivateKey`].
///
/// [`PEM_write_bio_PKCS8PrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_write_bio_PKCS8PrivateKey.html
#[corresponds(PEM_write_bio_PKCS8PrivateKey)]
private_key_to_pem_pkcs8_passphrase,
ffi::PEM_write_bio_PKCS8PrivateKey
}
to_der! {
/// Serializes the private key to a DER-encoded key type specific format.
///
/// This corresponds to [`i2d_PrivateKey`].
///
/// [`i2d_PrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/i2d_PrivateKey.html
#[corresponds(i2d_PrivateKey)]
private_key_to_der,
ffi::i2d_PrivateKey
}
@ -284,25 +290,50 @@ where
// "identical to the corresponding PEM function", and it's declared in pem.h.
private_key_to_pem! {
/// Serializes the private key to a DER-encoded PKCS#8 PrivateKeyInfo structure.
///
/// This corresponds to [`i2d_PKCS8PrivateKey_bio`].
///
/// [`i2d_PKCS8PrivateKey_bio`]: https://www.openssl.org/docs/man1.1.1/man3/i2d_PKCS8PrivateKey_bio.html
#[corresponds(i2d_PKCS8PrivateKey_bio)]
private_key_to_der_pkcs8,
/// Serializes the private key to a DER-encoded PKCS#8 EncryptedPrivateKeyInfo structure.
///
/// This corresponds to [`i2d_PKCS8PrivateKey_bio`].
///
/// [`i2d_PKCS8PrivateKey_bio`]: https://www.openssl.org/docs/man1.1.1/man3/i2d_PKCS8PrivateKey_bio.html
#[corresponds(i2d_PKCS8PrivateKey_bio)]
private_key_to_der_pkcs8_passphrase,
ffi::i2d_PKCS8PrivateKey_bio
}
/// Returns the length of the "raw" form of the private key. Only supported for certain key types.
#[corresponds(EVP_PKEY_get_raw_private_key)]
pub fn raw_private_key_len(&self) -> Result<usize, ErrorStack> {
unsafe {
let mut size = 0;
_ = cvt_0i(ffi::EVP_PKEY_get_raw_private_key(
self.as_ptr(),
std::ptr::null_mut(),
&mut size,
))?;
Ok(size)
}
}
/// Outputs a copy of the "raw" form of the private key. Only supported for certain key types.
///
/// Returns the used portion of `out`.
#[corresponds(EVP_PKEY_get_raw_private_key)]
pub fn raw_private_key<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], ErrorStack> {
unsafe {
let mut size = out.len();
_ = cvt_0i(ffi::EVP_PKEY_get_raw_private_key(
self.as_ptr(),
out.as_mut_ptr(),
&mut size,
))?;
Ok(&out[..size])
}
}
}
impl<T> fmt::Debug for PKey<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let alg = match self.id() {
Id::RSA => "RSA",
Id::RSAPSS => "RSAPSS",
Id::DSA => "DSA",
Id::DH => "DH",
Id::EC => "EC",
@ -323,10 +354,7 @@ impl<T> Clone for PKey<T> {
impl<T> PKey<T> {
/// Creates a new `PKey` containing an RSA key.
///
/// This corresponds to [`EVP_PKEY_assign_RSA`].
///
/// [`EVP_PKEY_assign_RSA`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_assign_RSA.html
#[corresponds(EVP_PKEY_assign_RSA)]
pub fn from_rsa(rsa: Rsa<T>) -> Result<PKey<T>, ErrorStack> {
unsafe {
let evp = cvt_p(ffi::EVP_PKEY_new())?;
@ -334,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)
@ -342,10 +370,7 @@ impl<T> PKey<T> {
}
/// Creates a new `PKey` containing an elliptic curve key.
///
/// This corresponds to [`EVP_PKEY_assign_EC_KEY`].
///
/// [`EVP_PKEY_assign_EC_KEY`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_assign_EC_KEY.html
#[corresponds(EVP_PKEY_assign_EC_KEY)]
pub fn from_ec_key(ec_key: EcKey<T>) -> Result<PKey<T>, ErrorStack> {
unsafe {
let evp = cvt_p(ffi::EVP_PKEY_new())?;
@ -353,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)
@ -364,26 +389,17 @@ impl<T> PKey<T> {
impl PKey<Private> {
private_key_from_pem! {
/// Deserializes a private key from a PEM-encoded key type specific format.
///
/// This corresponds to [`PEM_read_bio_PrivateKey`].
///
/// [`PEM_read_bio_PrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_PrivateKey.html
#[corresponds(PEM_read_bio_PrivateKey)]
private_key_from_pem,
/// Deserializes a private key from a PEM-encoded encrypted key type specific format.
///
/// This corresponds to [`PEM_read_bio_PrivateKey`].
///
/// [`PEM_read_bio_PrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_PrivateKey.html
#[corresponds(PEM_read_bio_PrivateKey)]
private_key_from_pem_passphrase,
/// Deserializes a private key from a PEM-encoded encrypted key type specific format.
///
/// The callback should fill the password into the provided buffer and return its length.
///
/// This corresponds to [`PEM_read_bio_PrivateKey`].
///
/// [`PEM_read_bio_PrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_PrivateKey.html
#[corresponds(PEM_read_bio_PrivateKey)]
private_key_from_pem_callback,
PKey<Private>,
ffi::PEM_read_bio_PrivateKey
@ -395,10 +411,7 @@ impl PKey<Private> {
/// This function will automatically attempt to detect the underlying key format, and
/// supports the unencrypted PKCS#8 PrivateKeyInfo structures as well as key type specific
/// formats.
///
/// This corresponds to [`d2i_AutoPrivateKey`].
///
/// [`d2i_AutoPrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/d2i_AutoPrivateKey.html
#[corresponds(d2i_AutoPrivateKey)]
private_key_from_der,
PKey<Private>,
ffi::d2i_AutoPrivateKey,
@ -411,7 +424,7 @@ impl PKey<Private> {
pub fn private_key_from_pkcs8(der: &[u8]) -> Result<PKey<Private>, ErrorStack> {
unsafe {
ffi::init();
let len = der.len().min(c_long::max_value() as usize) as c_long;
let len = der.len().min(c_long::MAX as usize) as c_long;
let p8inf = cvt_p(ffi::d2i_PKCS8_PRIV_KEY_INFO(
ptr::null_mut(),
&mut der.as_ptr(),
@ -424,7 +437,7 @@ impl PKey<Private> {
}
/// Deserializes a DER-formatted PKCS#8 private key, using a callback to retrieve the password
/// if the key is encrpyted.
/// if the key is encrypted.
///
/// The callback should copy the password into the provided buffer and return the number of
/// bytes written.
@ -443,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))
}
@ -462,12 +475,12 @@ impl PKey<Private> {
unsafe {
ffi::init();
let bio = MemBioSlice::new(der)?;
let passphrase = CString::new(passphrase).unwrap();
let passphrase = CString::new(passphrase).map_err(ErrorStack::internal_error)?;
cvt_p(ffi::d2i_PKCS8PrivateKey_bio(
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))
}
@ -479,10 +492,7 @@ impl PKey<Public> {
/// Decodes a PEM-encoded SubjectPublicKeyInfo structure.
///
/// The input should have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_read_bio_PUBKEY`].
///
/// [`PEM_read_bio_PUBKEY`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_read_bio_PUBKEY.html
#[corresponds(PEM_read_bio_PUBKEY)]
public_key_from_pem,
PKey<Public>,
ffi::PEM_read_bio_PUBKEY
@ -490,10 +500,7 @@ impl PKey<Public> {
from_der! {
/// Decodes a DER-encoded SubjectPublicKeyInfo structure.
///
/// This corresponds to [`d2i_PUBKEY`].
///
/// [`d2i_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/d2i_PUBKEY.html
#[corresponds(d2i_PUBKEY)]
public_key_from_der,
PKey<Public>,
ffi::d2i_PUBKEY,
@ -505,6 +512,8 @@ use crate::ffi::EVP_PKEY_up_ref;
#[cfg(test)]
mod tests {
use hex::FromHex as _;
use crate::ec::EcKey;
use crate::nid::Nid;
use crate::rsa::Rsa;
@ -615,4 +624,34 @@ mod tests {
assert_eq!(pkey.id(), Id::EC);
assert!(pkey.rsa().is_err());
}
#[test]
fn test_raw_accessors() {
const ED25519_PRIVATE_KEY_DER: &str = concat!(
"302e020100300506032b6570042204207c8c6497f9960d5595d7815f550569e5",
"f77764ac97e63e339aaa68cc1512b683"
);
let pkey =
PKey::private_key_from_der(&Vec::from_hex(ED25519_PRIVATE_KEY_DER).unwrap()).unwrap();
assert_eq!(pkey.id(), Id::ED25519);
let priv_len = pkey.raw_private_key_len().unwrap();
assert_eq!(priv_len, 32);
let mut raw_private_key_buf = [0; 40];
let raw_private_key = pkey.raw_private_key(&mut raw_private_key_buf).unwrap();
assert_eq!(raw_private_key.len(), 32);
assert_ne!(raw_private_key, [0; 32]);
pkey.raw_private_key(&mut [0; 5])
.expect_err("buffer too small");
let pub_len = pkey.raw_public_key_len().unwrap();
assert_eq!(pub_len, 32);
let mut raw_public_key_buf = [0; 40];
let raw_public_key = pkey.raw_public_key(&mut raw_public_key_buf).unwrap();
assert_eq!(raw_public_key.len(), 32);
assert_ne!(raw_public_key, [0; 32]);
assert_ne!(raw_public_key, raw_private_key);
pkey.raw_public_key(&mut [0; 5])
.expect_err("buffer too small");
}
}

View File

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

View File

@ -23,16 +23,18 @@
//! 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;
use std::fmt;
use std::mem;
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;
@ -66,12 +68,14 @@ impl Padding {
pub const PKCS1_PSS: Padding = Padding(ffi::RSA_PKCS1_PSS_PADDING);
/// Creates a `Padding` from an integer representation.
#[must_use]
pub fn from_raw(value: c_int) -> Padding {
Padding(value)
}
/// Returns the integer representation of `Padding`.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_raw(&self) -> c_int {
self.0
}
@ -113,28 +117,19 @@ where
/// Serializes the private key to a PEM-encoded PKCS#1 RSAPrivateKey structure.
///
/// The output will have a header of `-----BEGIN RSA PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_RSAPrivateKey`].
///
/// [`PEM_write_bio_RSAPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_RSAPrivateKey.html
#[corresponds(PEM_write_bio_RSAPrivateKey)]
private_key_to_pem,
/// Serializes the private key to a PEM-encoded encrypted PKCS#1 RSAPrivateKey structure.
///
/// The output will have a header of `-----BEGIN RSA PRIVATE KEY-----`.
///
/// This corresponds to [`PEM_write_bio_RSAPrivateKey`].
///
/// [`PEM_write_bio_RSAPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_RSAPrivateKey.html
#[corresponds(PEM_write_bio_RSAPrivateKey)]
private_key_to_pem_passphrase,
ffi::PEM_write_bio_RSAPrivateKey
}
to_der! {
/// Serializes the private key to a DER-encoded PKCS#1 RSAPrivateKey structure.
///
/// This corresponds to [`i2d_RSAPrivateKey`].
///
/// [`i2d_RSAPrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/i2d_RSAPrivateKey.html
#[corresponds(i2d_RSAPrivateKey)]
private_key_to_der,
ffi::i2d_RSAPrivateKey
}
@ -151,7 +146,7 @@ where
to: &mut [u8],
padding: Padding,
) -> Result<usize, ErrorStack> {
assert!(from.len() <= i32::max_value() as usize);
assert!(i32::try_from(from.len()).is_ok());
assert!(to.len() >= self.size() as usize);
unsafe {
@ -178,7 +173,7 @@ where
to: &mut [u8],
padding: Padding,
) -> Result<usize, ErrorStack> {
assert!(from.len() <= i32::max_value() as usize);
assert!(i32::try_from(from.len()).is_ok());
assert!(to.len() >= self.size() as usize);
unsafe {
@ -194,23 +189,19 @@ where
}
/// Returns a reference to the private exponent of the key.
///
/// This corresponds to [`RSA_get0_key`].
///
/// [`RSA_get0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_key)]
#[must_use]
pub fn d(&self) -> &BigNumRef {
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())
}
}
/// Returns a reference to the first factor of the exponent of the key.
///
/// This corresponds to [`RSA_get0_factors`].
///
/// [`RSA_get0_factors`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_factors)]
#[must_use]
pub fn p(&self) -> Option<&BigNumRef> {
unsafe {
let mut p = ptr::null();
@ -218,16 +209,14 @@ where
if p.is_null() {
None
} else {
Some(BigNumRef::from_ptr(p as *mut _))
Some(BigNumRef::from_ptr(p.cast_mut()))
}
}
}
/// Returns a reference to the second factor of the exponent of the key.
///
/// This corresponds to [`RSA_get0_factors`].
///
/// [`RSA_get0_factors`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_factors)]
#[must_use]
pub fn q(&self) -> Option<&BigNumRef> {
unsafe {
let mut q = ptr::null();
@ -235,16 +224,14 @@ where
if q.is_null() {
None
} else {
Some(BigNumRef::from_ptr(q as *mut _))
Some(BigNumRef::from_ptr(q.cast_mut()))
}
}
}
/// Returns a reference to the first exponent used for CRT calculations.
///
/// This corresponds to [`RSA_get0_crt_params`].
///
/// [`RSA_get0_crt_params`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_crt_params)]
#[must_use]
pub fn dmp1(&self) -> Option<&BigNumRef> {
unsafe {
let mut dp = ptr::null();
@ -252,16 +239,14 @@ where
if dp.is_null() {
None
} else {
Some(BigNumRef::from_ptr(dp as *mut _))
Some(BigNumRef::from_ptr(dp.cast_mut()))
}
}
}
/// Returns a reference to the second exponent used for CRT calculations.
///
/// This corresponds to [`RSA_get0_crt_params`].
///
/// [`RSA_get0_crt_params`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_crt_params)]
#[must_use]
pub fn dmq1(&self) -> Option<&BigNumRef> {
unsafe {
let mut dq = ptr::null();
@ -269,16 +254,14 @@ where
if dq.is_null() {
None
} else {
Some(BigNumRef::from_ptr(dq as *mut _))
Some(BigNumRef::from_ptr(dq.cast_mut()))
}
}
}
/// Returns a reference to the coefficient used for CRT calculations.
///
/// This corresponds to [`RSA_get0_crt_params`].
///
/// [`RSA_get0_crt_params`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_crt_params)]
#[must_use]
pub fn iqmp(&self) -> Option<&BigNumRef> {
unsafe {
let mut qi = ptr::null();
@ -286,16 +269,13 @@ where
if qi.is_null() {
None
} else {
Some(BigNumRef::from_ptr(qi as *mut _))
Some(BigNumRef::from_ptr(qi.cast_mut()))
}
}
}
/// Validates RSA parameters for correctness
///
/// This corresponds to [`RSA_check_key`].
///
/// [`RSA_check_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_check_key.html
#[corresponds(RSA_check_key)]
#[allow(clippy::unnecessary_cast)]
pub fn check_key(&self) -> Result<bool, ErrorStack> {
unsafe {
@ -317,20 +297,14 @@ where
/// Serializes the public key into a PEM-encoded SubjectPublicKeyInfo structure.
///
/// The output will have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_write_bio_RSA_PUBKEY`].
///
/// [`PEM_write_bio_RSA_PUBKEY`]: https://www.openssl.org/docs/man1.0.2/crypto/pem.html
#[corresponds(PEM_write_bio_RSA_PUBKEY)]
public_key_to_pem,
ffi::PEM_write_bio_RSA_PUBKEY
}
to_der! {
/// Serializes the public key into a DER-encoded SubjectPublicKeyInfo structure.
///
/// This corresponds to [`i2d_RSA_PUBKEY`].
///
/// [`i2d_RSA_PUBKEY`]: https://www.openssl.org/docs/man1.1.0/crypto/i2d_RSA_PUBKEY.html
#[corresponds(i2d_RSA_PUBKEY)]
public_key_to_der,
ffi::i2d_RSA_PUBKEY
}
@ -339,30 +313,22 @@ where
/// Serializes the public key into a PEM-encoded PKCS#1 RSAPublicKey structure.
///
/// The output will have a header of `-----BEGIN RSA PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_write_bio_RSAPublicKey`].
///
/// [`PEM_write_bio_RSAPublicKey`]: https://www.openssl.org/docs/man1.0.2/crypto/pem.html
#[corresponds(PEM_write_bio_RSAPublicKey)]
public_key_to_pem_pkcs1,
ffi::PEM_write_bio_RSAPublicKey
}
to_der! {
/// Serializes the public key into a DER-encoded PKCS#1 RSAPublicKey structure.
///
/// This corresponds to [`i2d_RSAPublicKey`].
///
/// [`i2d_RSAPublicKey`]: https://www.openssl.org/docs/man1.0.2/crypto/i2d_RSAPublicKey.html
#[corresponds(i2d_RSAPublicKey)]
public_key_to_der_pkcs1,
ffi::i2d_RSAPublicKey
}
/// Returns the size of the modulus in bytes.
///
/// This corresponds to [`RSA_size`].
///
/// [`RSA_size`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_size.html
#[corresponds(RSA_size)]
#[allow(clippy::unnecessary_cast)]
#[must_use]
pub fn size(&self) -> u32 {
unsafe { ffi::RSA_size(self.as_ptr()) as u32 }
}
@ -378,7 +344,7 @@ where
to: &mut [u8],
padding: Padding,
) -> Result<usize, ErrorStack> {
assert!(from.len() <= i32::max_value() as usize);
assert!(i32::try_from(from.len()).is_ok());
assert!(to.len() >= self.size() as usize);
unsafe {
@ -404,7 +370,7 @@ where
to: &mut [u8],
padding: Padding,
) -> Result<usize, ErrorStack> {
assert!(from.len() <= i32::max_value() as usize);
assert!(i32::try_from(from.len()).is_ok());
assert!(to.len() >= self.size() as usize);
unsafe {
@ -420,28 +386,24 @@ where
}
/// Returns a reference to the modulus of the key.
///
/// This corresponds to [`RSA_get0_key`].
///
/// [`RSA_get0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_key)]
#[must_use]
pub fn n(&self) -> &BigNumRef {
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())
}
}
/// Returns a reference to the public exponent of the key.
///
/// This corresponds to [`RSA_get0_key`].
///
/// [`RSA_get0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
#[corresponds(RSA_get0_key)]
#[must_use]
pub fn e(&self) -> &BigNumRef {
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())
}
}
}
@ -451,15 +413,11 @@ impl Rsa<Public> {
///
/// `n` is the modulus common to both public and private key.
/// `e` is the public exponent.
///
/// This corresponds to [`RSA_new`] and uses [`RSA_set0_key`].
///
/// [`RSA_new`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_new.html
/// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html
#[corresponds(RSA_new)]
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))
}
@ -469,10 +427,7 @@ impl Rsa<Public> {
/// Decodes a PEM-encoded SubjectPublicKeyInfo structure containing an RSA key.
///
/// The input should have a header of `-----BEGIN PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_read_bio_RSA_PUBKEY`].
///
/// [`PEM_read_bio_RSA_PUBKEY`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_read_bio_RSA_PUBKEY.html
#[corresponds(PEM_read_bio_RSA_PUBKEY)]
public_key_from_pem,
Rsa<Public>,
ffi::PEM_read_bio_RSA_PUBKEY
@ -482,10 +437,7 @@ impl Rsa<Public> {
/// Decodes a PEM-encoded PKCS#1 RSAPublicKey structure.
///
/// The input should have a header of `-----BEGIN RSA PUBLIC KEY-----`.
///
/// This corresponds to [`PEM_read_bio_RSAPublicKey`].
///
/// [`PEM_read_bio_RSAPublicKey`]: https://www.openssl.org/docs/man1.0.2/crypto/PEM_read_bio_RSAPublicKey.html
#[corresponds(PEM_read_bio_RSAPublicKey)]
public_key_from_pem_pkcs1,
Rsa<Public>,
ffi::PEM_read_bio_RSAPublicKey
@ -493,10 +445,7 @@ impl Rsa<Public> {
from_der! {
/// Decodes a DER-encoded SubjectPublicKeyInfo structure containing an RSA key.
///
/// This corresponds to [`d2i_RSA_PUBKEY`].
///
/// [`d2i_RSA_PUBKEY`]: https://www.openssl.org/docs/man1.0.2/crypto/d2i_RSA_PUBKEY.html
#[corresponds(d2i_RSA_PUBKEY)]
public_key_from_der,
Rsa<Public>,
ffi::d2i_RSA_PUBKEY,
@ -505,10 +454,7 @@ impl Rsa<Public> {
from_der! {
/// Decodes a DER-encoded PKCS#1 RSAPublicKey structure.
///
/// This corresponds to [`d2i_RSAPublicKey`].
///
/// [`d2i_RSAPublicKey`]: https://www.openssl.org/docs/man1.0.2/crypto/d2i_RSA_PUBKEY.html
#[corresponds(d2i_RSAPublicKey)]
public_key_from_der_pkcs1,
Rsa<Public>,
ffi::d2i_RSAPublicKey,
@ -525,15 +471,11 @@ impl RsaPrivateKeyBuilder {
///
/// `n` is the modulus common to both public and private key.
/// `e` is the public exponent and `d` is the private exponent.
///
/// This corresponds to [`RSA_new`] and uses [`RSA_set0_key`].
///
/// [`RSA_new`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_new.html
/// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html
#[corresponds(RSA_new)]
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),
@ -544,14 +486,10 @@ impl RsaPrivateKeyBuilder {
/// Sets the factors of the Rsa key.
///
/// `p` and `q` are the first and second factors of `n`.
///
/// This correspond to [`RSA_set0_factors`].
///
/// [`RSA_set0_factors`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_factors.html
// 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)
@ -561,11 +499,7 @@ impl RsaPrivateKeyBuilder {
///
/// `dmp1`, `dmq1`, and `iqmp` are the exponents and coefficient for
/// CRT calculations which is used to speed up RSA operations.
///
/// This correspond to [`RSA_set0_crt_params`].
///
/// [`RSA_set0_crt_params`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_crt_params.html
// FIXME should be infallible
#[corresponds(RSA_set0_crt_params)]
pub fn set_crt_params(
self,
dmp1: BigNum,
@ -573,18 +507,19 @@ 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)
}
/// Returns the Rsa key.
#[must_use]
pub fn build(self) -> Rsa<Private> {
self.rsa
}
@ -615,10 +550,7 @@ impl Rsa<Private> {
/// Generates a public/private key pair with the specified size.
///
/// The public exponent will be 65537.
///
/// This corresponds to [`RSA_generate_key_ex`].
///
/// [`RSA_generate_key_ex`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_generate_key_ex.html
#[corresponds(RSA_generate_key_ex)]
pub fn generate(bits: u32) -> Result<Rsa<Private>, ErrorStack> {
let e = BigNum::from_u32(ffi::RSA_F4 as u32)?;
Rsa::generate_with_e(bits, &e)
@ -627,10 +559,7 @@ impl Rsa<Private> {
/// Generates a public/private key pair with the specified size and a custom exponent.
///
/// Unless you have specific needs and know what you're doing, use `Rsa::generate` instead.
///
/// This corresponds to [`RSA_generate_key_ex`].
///
/// [`RSA_generate_key_ex`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_generate_key_ex.html
#[corresponds(RSA_generate_key_ex)]
pub fn generate_with_e(bits: u32, e: &BigNumRef) -> Result<Rsa<Private>, ErrorStack> {
unsafe {
let rsa = Rsa::from_ptr(cvt_p(ffi::RSA_new())?);
@ -647,26 +576,17 @@ impl Rsa<Private> {
// FIXME these need to identify input formats
private_key_from_pem! {
/// Deserializes a private key from a PEM-encoded PKCS#1 RSAPrivateKey structure.
///
/// This corresponds to [`PEM_read_bio_RSAPrivateKey`].
///
/// [`PEM_read_bio_RSAPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_RSAPrivateKey.html
#[corresponds(PEM_read_bio_RSAPrivateKey)]
private_key_from_pem,
/// Deserializes a private key from a PEM-encoded encrypted PKCS#1 RSAPrivateKey structure.
///
/// This corresponds to [`PEM_read_bio_RSAPrivateKey`].
///
/// [`PEM_read_bio_RSAPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_RSAPrivateKey.html
#[corresponds(PEM_read_bio_RSAPrivateKey)]
private_key_from_pem_passphrase,
/// Deserializes a private key from a PEM-encoded encrypted PKCS#1 RSAPrivateKey structure.
///
/// The callback should fill the password into the provided buffer and return its length.
///
/// This corresponds to [`PEM_read_bio_RSAPrivateKey`].
///
/// [`PEM_read_bio_RSAPrivateKey`]: https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_RSAPrivateKey.html
#[corresponds(PEM_read_bio_RSAPrivateKey)]
private_key_from_pem_callback,
Rsa<Private>,
ffi::PEM_read_bio_RSAPrivateKey
@ -674,10 +594,7 @@ impl Rsa<Private> {
from_der! {
/// Decodes a DER-encoded PKCS#1 RSAPrivateKey structure.
///
/// This corresponds to [`d2i_RSAPrivateKey`].
///
/// [`d2i_RSAPrivateKey`]: https://www.openssl.org/docs/man1.0.2/crypto/d2i_RSA_PUBKEY.html
#[corresponds(d2i_RSAPrivateKey)]
private_key_from_der,
Rsa<Private>,
ffi::d2i_RSAPrivateKey,

View File

@ -54,7 +54,7 @@ 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 {
let mut hash: MaybeUninit<[u8; 20]> = MaybeUninit::uninit();
@ -65,7 +65,7 @@ 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 {
let mut hash: MaybeUninit<[u8; 28]> = MaybeUninit::uninit();
@ -76,7 +76,7 @@ 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 {
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();
@ -87,7 +87,7 @@ 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 {
let mut hash: MaybeUninit<[u8; 48]> = MaybeUninit::uninit();
@ -98,7 +98,7 @@ 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 {
let mut hash: MaybeUninit<[u8; 64]> = MaybeUninit::uninit();
@ -109,7 +109,7 @@ 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 {
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();
@ -137,7 +137,7 @@ 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 {
let mut ctx = MaybeUninit::uninit();
@ -152,13 +152,13 @@ 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 {
let mut hash: MaybeUninit<[u8; 20]> = MaybeUninit::uninit();
@ -182,7 +182,7 @@ 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 {
let mut ctx = MaybeUninit::uninit();
@ -197,13 +197,13 @@ 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 {
let mut hash: MaybeUninit<[u8; 28]> = MaybeUninit::uninit();
@ -227,7 +227,7 @@ 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 {
let mut ctx = MaybeUninit::uninit();
@ -242,13 +242,13 @@ 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 {
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();
@ -272,7 +272,7 @@ 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 {
let mut ctx = MaybeUninit::uninit();
@ -287,13 +287,13 @@ 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 {
let mut hash: MaybeUninit<[u8; 48]> = MaybeUninit::uninit();
@ -317,7 +317,7 @@ 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 {
let mut ctx = MaybeUninit::uninit();
@ -332,13 +332,13 @@ 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 {
let mut hash: MaybeUninit<[u8; 64]> = MaybeUninit::uninit();
@ -362,7 +362,7 @@ 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 {
let mut ctx = MaybeUninit::uninit();
@ -377,13 +377,13 @@ 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 {
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();

View File

@ -37,6 +37,7 @@
use crate::ffi;
use foreign_types::ForeignTypeRef;
use libc::c_int;
use openssl_macros::corresponds;
use std::io::{self, Write};
use std::marker::PhantomData;
use std::ptr;
@ -59,6 +60,7 @@ impl RsaPssSaltlen {
}
/// Sets the salt length to the given value.
#[must_use]
pub fn custom(val: c_int) -> RsaPssSaltlen {
RsaPssSaltlen(val)
}
@ -78,10 +80,10 @@ pub struct Signer<'a> {
_p: PhantomData<&'a ()>,
}
unsafe impl<'a> Sync for Signer<'a> {}
unsafe impl<'a> Send for Signer<'a> {}
unsafe impl Sync for Signer<'_> {}
unsafe impl Send for Signer<'_> {}
impl<'a> Drop for Signer<'a> {
impl Drop for Signer<'_> {
fn drop(&mut self) {
// pkey_ctx is owned by the md_ctx, so no need to explicitly free it.
unsafe {
@ -96,10 +98,7 @@ impl<'a> Signer<'a> {
///
/// This cannot be used with Ed25519 or Ed448 keys. Please refer to
/// `new_without_digest`.
///
/// OpenSSL documentation at [`EVP_DigestSignInit`].
///
/// [`EVP_DigestSignInit`]: https://www.openssl.org/docs/manmaster/man3/EVP_DigestSignInit.html
#[corresponds(EVP_DigestSignInit)]
pub fn new<T>(type_: MessageDigest, pkey: &'a PKeyRef<T>) -> Result<Signer<'a>, ErrorStack>
where
T: HasPrivate,
@ -111,10 +110,7 @@ impl<'a> Signer<'a> {
///
/// This is the only way to create a `Verifier` for Ed25519 or Ed448 keys.
/// It can also be used to create a CMAC.
///
/// OpenSSL documentation at [`EVP_DigestSignInit`].
///
/// [`EVP_DigestSignInit`]: https://www.openssl.org/docs/manmaster/man3/EVP_DigestSignInit.html
#[corresponds(EVP_DigestSignInit)]
pub fn new_without_digest<T>(pkey: &'a PKeyRef<T>) -> Result<Signer<'a>, ErrorStack>
where
T: HasPrivate,
@ -159,8 +155,7 @@ impl<'a> Signer<'a> {
/// Returns the RSA padding mode in use.
///
/// This is only useful for RSA keys.
///
/// This corresponds to `EVP_PKEY_CTX_get_rsa_padding`.
#[corresponds(EVP_PKEY_CTX_get_rsa_padding)]
pub fn rsa_padding(&self) -> Result<Padding, ErrorStack> {
unsafe {
let mut pad = 0;
@ -172,51 +167,39 @@ impl<'a> Signer<'a> {
/// Sets the RSA padding mode.
///
/// This is only useful for RSA keys.
///
/// This corresponds to [`EVP_PKEY_CTX_set_rsa_padding`].
///
/// [`EVP_PKEY_CTX_set_rsa_padding`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_CTX_set_rsa_padding.html
#[corresponds(EVP_PKEY_CTX_set_rsa_padding)]
pub fn set_rsa_padding(&mut self, padding: Padding) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_padding(
self.pctx,
padding.as_raw(),
))
.map(|_| ())
}
}
/// Sets the RSA PSS salt length.
///
/// This is only useful for RSA keys.
///
/// This corresponds to [`EVP_PKEY_CTX_set_rsa_pss_saltlen`].
///
/// [`EVP_PKEY_CTX_set_rsa_pss_saltlen`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_CTX_set_rsa_pss_saltlen.html
#[corresponds(EVP_PKEY_CTX_set_rsa_pss_saltlen)]
pub fn set_rsa_pss_saltlen(&mut self, len: RsaPssSaltlen) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_pss_saltlen(
self.pctx,
len.as_raw(),
))
.map(|_| ())
}
}
/// Sets the RSA MGF1 algorithm.
///
/// This is only useful for RSA keys.
///
/// This corresponds to [`EVP_PKEY_CTX_set_rsa_mgf1_md`].
///
/// [`EVP_PKEY_CTX_set_rsa_mgf1_md`]: https://www.openssl.org/docs/manmaster/man7/RSA-PSS.html
#[corresponds(EVP_PKEY_CTX_set_rsa_mgf1_md)]
pub fn set_rsa_mgf1_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md(
self.pctx,
md.as_ptr() as *mut _,
md.as_ptr().cast_mut(),
))
.map(|_| ())
}
}
@ -224,18 +207,14 @@ impl<'a> Signer<'a> {
///
/// Please note that PureEdDSA (Ed25519 and Ed448 keys) do not support streaming.
/// Use `sign_oneshot` instead.
///
/// OpenSSL documentation at [`EVP_DigestUpdate`].
///
/// [`EVP_DigestUpdate`]: https://www.openssl.org/docs/manmaster/man3/EVP_DigestInit.html
#[corresponds(EVP_DigestUpdate)]
pub fn update(&mut self, buf: &[u8]) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_DigestUpdate(
self.md_ctx,
buf.as_ptr() as *const _,
buf.as_ptr().cast(),
buf.len(),
))
.map(|_| ())
}
}
@ -243,10 +222,7 @@ impl<'a> Signer<'a> {
///
/// The actual signature may be shorter than this value. Check the return value of
/// `sign` to get the exact length.
///
/// OpenSSL documentation at [`EVP_DigestSignFinal`].
///
/// [`EVP_DigestSignFinal`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_DigestSignFinal.html
#[corresponds(EVP_DigestSignFinal)]
pub fn len(&self) -> Result<usize, ErrorStack> {
self.len_intern()
}
@ -269,16 +245,13 @@ impl<'a> Signer<'a> {
///
/// This method will fail if the buffer is not large enough for the signature. Use the `len`
/// method to get an upper bound on the required size.
///
/// OpenSSL documentation at [`EVP_DigestSignFinal`].
///
/// [`EVP_DigestSignFinal`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_DigestSignFinal.html
#[corresponds(EVP_DigestSignFinal)]
pub fn sign(&self, buf: &mut [u8]) -> Result<usize, ErrorStack> {
unsafe {
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)
@ -303,10 +276,7 @@ impl<'a> Signer<'a> {
///
/// This method will fail if the buffer is not large enough for the signature. Use the `len`
/// method to get an upper bound on the required size.
///
/// OpenSSL documentation at [`EVP_DigestSign`].
///
/// [`EVP_DigestSign`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestSign.html
#[corresponds(EVP_DigestSign)]
pub fn sign_oneshot(
&mut self,
sig_buf: &mut [u8],
@ -316,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)
@ -337,7 +307,7 @@ impl<'a> Signer<'a> {
}
}
impl<'a> Write for Signer<'a> {
impl Write for Signer<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.update(buf)?;
Ok(buf.len())
@ -354,10 +324,10 @@ pub struct Verifier<'a> {
pkey_pd: PhantomData<&'a ()>,
}
unsafe impl<'a> Sync for Verifier<'a> {}
unsafe impl<'a> Send for Verifier<'a> {}
unsafe impl Sync for Verifier<'_> {}
unsafe impl Send for Verifier<'_> {}
impl<'a> Drop for Verifier<'a> {
impl Drop for Verifier<'_> {
fn drop(&mut self) {
// pkey_ctx is owned by the md_ctx, so no need to explicitly free it.
unsafe {
@ -372,10 +342,7 @@ impl<'a> Verifier<'a> {
///
/// This cannot be used with Ed25519 or Ed448 keys. Please refer to
/// `new_without_digest`.
///
/// OpenSSL documentation at [`EVP_DigestVerifyInit`].
///
/// [`EVP_DigestVerifyInit`]: https://www.openssl.org/docs/manmaster/man3/EVP_DigestVerifyInit.html
#[corresponds(EVP_DigestVerifyInit)]
pub fn new<T>(type_: MessageDigest, pkey: &'a PKeyRef<T>) -> Result<Verifier<'a>, ErrorStack>
where
T: HasPublic,
@ -386,10 +353,7 @@ impl<'a> Verifier<'a> {
/// Creates a new `Verifier` without a digest.
///
/// This is the only way to create a `Verifier` for Ed25519 or Ed448 keys.
///
/// OpenSSL documentation at [`EVP_DigestVerifyInit`].
///
/// [`EVP_DigestVerifyInit`]: https://www.openssl.org/docs/manmaster/man3/EVP_DigestVerifyInit.html
#[corresponds(EVP_DigestVerifyInit)]
pub fn new_without_digest<T>(pkey: &'a PKeyRef<T>) -> Result<Verifier<'a>, ErrorStack>
where
T: HasPublic,
@ -434,8 +398,7 @@ impl<'a> Verifier<'a> {
/// Returns the RSA padding mode in use.
///
/// This is only useful for RSA keys.
///
/// This corresponds to `EVP_PKEY_CTX_get_rsa_padding`.
#[corresponds(EVP_PKEY_CTX_get_rsa_padding)]
pub fn rsa_padding(&self) -> Result<Padding, ErrorStack> {
unsafe {
let mut pad = 0;
@ -447,51 +410,39 @@ impl<'a> Verifier<'a> {
/// Sets the RSA padding mode.
///
/// This is only useful for RSA keys.
///
/// This corresponds to [`EVP_PKEY_CTX_set_rsa_padding`].
///
/// [`EVP_PKEY_CTX_set_rsa_padding`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_CTX_set_rsa_padding.html
#[corresponds(EVP_PKEY_CTX_set_rsa_padding)]
pub fn set_rsa_padding(&mut self, padding: Padding) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_padding(
self.pctx,
padding.as_raw(),
))
.map(|_| ())
}
}
/// Sets the RSA PSS salt length.
///
/// This is only useful for RSA keys.
///
/// This corresponds to [`EVP_PKEY_CTX_set_rsa_pss_saltlen`].
///
/// [`EVP_PKEY_CTX_set_rsa_pss_saltlen`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_CTX_set_rsa_pss_saltlen.html
#[corresponds(EVP_PKEY_CTX_set_rsa_pss_saltlen)]
pub fn set_rsa_pss_saltlen(&mut self, len: RsaPssSaltlen) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_pss_saltlen(
self.pctx,
len.as_raw(),
))
.map(|_| ())
}
}
/// Sets the RSA MGF1 algorithm.
///
/// This is only useful for RSA keys.
///
/// This corresponds to [`EVP_PKEY_CTX_set_rsa_mgf1_md`].
///
/// [`EVP_PKEY_CTX_set_rsa_mgf1_md`]: https://www.openssl.org/docs/manmaster/man7/RSA-PSS.html
#[corresponds(EVP_PKEY_CTX_set_rsa_mgf1_md)]
pub fn set_rsa_mgf1_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md(
self.pctx,
md.as_ptr() as *mut _,
md.as_ptr().cast_mut(),
))
.map(|_| ())
}
}
@ -499,34 +450,27 @@ impl<'a> Verifier<'a> {
///
/// Please note that PureEdDSA (Ed25519 and Ed448 keys) do not support streaming.
/// Use `verify_oneshot` instead.
///
/// OpenSSL documentation at [`EVP_DigestUpdate`].
///
/// [`EVP_DigestUpdate`]: https://www.openssl.org/docs/manmaster/man3/EVP_DigestInit.html
#[corresponds(EVP_DigestUpdate)]
pub fn update(&mut self, buf: &[u8]) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_DigestUpdate(
self.md_ctx,
buf.as_ptr() as *const _,
buf.as_ptr().cast(),
buf.len(),
))
.map(|_| ())
}
}
/// Determines if the data fed into the `Verifier` matches the provided signature.
///
/// OpenSSL documentation at [`EVP_DigestVerifyFinal`].
///
/// [`EVP_DigestVerifyFinal`]: https://www.openssl.org/docs/manmaster/man3/EVP_DigestVerifyFinal.html
#[corresponds(EVP_DigestVerifyFinal)]
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 => {
ErrorStack::get(); // discard error stack
ErrorStack::clear(); // discard error stack
Ok(false)
}
_ => Err(ErrorStack::get()),
@ -535,23 +479,20 @@ impl<'a> Verifier<'a> {
}
/// Determines if the data given in buf matches the provided signature.
///
/// OpenSSL documentation at [`EVP_DigestVerify`].
///
/// [`EVP_DigestVerify`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestVerify.html
#[corresponds(EVP_DigestVerify)]
pub fn verify_oneshot(&mut self, signature: &[u8], buf: &[u8]) -> Result<bool, ErrorStack> {
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 {
1 => Ok(true),
0 => {
ErrorStack::get();
ErrorStack::clear();
Ok(false)
}
_ => Err(ErrorStack::get()),
@ -560,7 +501,7 @@ impl<'a> Verifier<'a> {
}
}
impl<'a> Write for Verifier<'a> {
impl Write for Verifier<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.update(buf)?;
Ok(buf.len())

View File

@ -20,11 +20,14 @@ impl Stackable for SrtpProtectionProfile {
}
impl SrtpProtectionProfileRef {
#[must_use]
pub fn id(&self) -> SrtpProfileId {
SrtpProfileId::from_raw(unsafe { (*self.as_ptr()).id })
}
#[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")
}
@ -47,12 +50,14 @@ impl SrtpProfileId {
pub const SRTP_NULL_SHA1_32: SrtpProfileId = SrtpProfileId(ffi::SRTP_NULL_SHA1_32 as _);
/// Creates a `SrtpProfileId` from an integer representation.
#[must_use]
pub fn from_raw(value: c_ulong) -> SrtpProfileId {
SrtpProfileId(value)
}
/// Returns the integer representation of `SrtpProfileId`.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_raw(&self) -> c_ulong {
self.0
}

View File

@ -0,0 +1,392 @@
use super::mut_only::MutOnly;
use super::{
ClientHello, GetSessionPendingError, PrivateKeyMethod, PrivateKeyMethodError, SelectCertError,
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;
use std::sync::LazyLock;
use std::task::{ready, Context, Poll, Waker};
/// The type of futures to pass to [`SslContextBuilder::set_async_select_certificate_callback`].
pub type BoxSelectCertFuture = ExDataFuture<Result<BoxSelectCertFinish, AsyncSelectCertError>>;
/// The type of callbacks returned by [`BoxSelectCertFuture`] methods.
pub type BoxSelectCertFinish = Box<dyn FnOnce(ClientHello<'_>) -> Result<(), AsyncSelectCertError>>;
/// The type of futures returned by [`AsyncPrivateKeyMethod`] methods.
pub type BoxPrivateKeyMethodFuture =
ExDataFuture<Result<BoxPrivateKeyMethodFinish, AsyncPrivateKeyMethodError>>;
/// The type of callbacks returned by [`BoxPrivateKeyMethodFuture`].
pub type BoxPrivateKeyMethodFinish =
Box<dyn FnOnce(&mut SslRef, &mut [u8]) -> Result<usize, AsyncPrivateKeyMethodError>>;
/// The type of futures to pass to [`SslContextBuilder::set_async_get_session_callback`].
pub type BoxGetSessionFuture = ExDataFuture<Option<BoxGetSessionFinish>>;
/// The type of callbacks returned by [`BoxSelectCertFuture`] methods.
pub type BoxGetSessionFinish = Box<dyn FnOnce(&mut SslRef, &[u8]) -> Option<SslSession>>;
/// The type of futures to pass to [`SslContextBuilder::set_async_custom_verify_callback`].
pub type BoxCustomVerifyFuture = ExDataFuture<Result<BoxCustomVerifyFinish, SslAlert>>;
/// The type of callbacks returned by [`BoxCustomVerifyFuture`] methods.
pub type BoxCustomVerifyFinish = Box<dyn FnOnce(&mut SslRef) -> Result<(), SslAlert>>;
/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilder`] methods.
///
/// Public for documentation purposes.
pub type ExDataFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
pub(crate) static TASK_WAKER_INDEX: LazyLock<Index<Ssl, Option<Waker>>> =
LazyLock::new(|| Ssl::new_ex_index().unwrap());
pub(crate) static SELECT_CERT_FUTURE_INDEX: LazyLock<
Index<Ssl, MutOnly<Option<BoxSelectCertFuture>>>,
> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
pub(crate) static SELECT_PRIVATE_KEY_METHOD_FUTURE_INDEX: LazyLock<
Index<Ssl, MutOnly<Option<BoxPrivateKeyMethodFuture>>>,
> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
pub(crate) static SELECT_GET_SESSION_FUTURE_INDEX: LazyLock<
Index<Ssl, MutOnly<Option<BoxGetSessionFuture>>>,
> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
pub(crate) static SELECT_CUSTOM_VERIFY_FUTURE_INDEX: LazyLock<
Index<Ssl, MutOnly<Option<BoxCustomVerifyFuture>>>,
> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
impl SslContextBuilder {
/// Sets a callback that is called before most [`ClientHello`] processing
/// and before the decision whether to resume a session is made. The
/// callback may inspect the [`ClientHello`] and configure the connection.
///
/// This method uses a function that returns a future whose output is
/// itself a closure that will be passed [`ClientHello`] to configure
/// the connection based on the computations done in the future.
///
/// A task waker must be set on `Ssl` values associated with the resulting
/// `SslContext` with [`SslRef::set_task_waker`].
///
/// See [`SslContextBuilder::set_select_certificate_callback`] for the sync
/// setter of this callback.
pub fn set_async_select_certificate_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ClientHello<'_>) -> Result<BoxSelectCertFuture, AsyncSelectCertError>
+ Send
+ Sync
+ 'static,
{
self.set_select_certificate_callback(move |mut client_hello| {
let fut_poll_result = with_ex_data_future(
&mut client_hello,
*SELECT_CERT_FUTURE_INDEX,
ClientHello::ssl_mut,
&callback,
identity,
);
let fut_result = match fut_poll_result {
Poll::Ready(fut_result) => fut_result,
Poll::Pending => return Err(SelectCertError::RETRY),
};
let finish = fut_result.or(Err(SelectCertError::ERROR))?;
finish(client_hello).or(Err(SelectCertError::ERROR))
});
}
/// 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) {
self.set_private_key_method(AsyncPrivateKeyMethodBridge(Box::new(method)));
}
/// Sets a callback that is called when a client proposed to resume a session
/// but it was not found in the internal cache.
///
/// The callback is passed a reference to the session ID provided by the client.
/// It should return the session corresponding to that ID if available. This is
/// only used for servers, not clients.
///
/// A task waker must be set on `Ssl` values associated with the resulting
/// `SslContext` with [`SslRef::set_task_waker`].
///
/// See [`SslContextBuilder::set_get_session_callback`] for the sync setter
/// of this callback.
///
/// # Safety
///
/// The returned [`SslSession`] must not be associated with a different [`SslContextBuilder`].
pub unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
where
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static,
{
let async_callback = move |ssl: &mut SslRef, id: &[u8]| {
let fut_poll_result = with_ex_data_future(
&mut *ssl,
*SELECT_GET_SESSION_FUTURE_INDEX,
|ssl| ssl,
|ssl| callback(ssl, id).ok_or(()),
|option| option.ok_or(()),
);
match fut_poll_result {
Poll::Ready(Err(())) => Ok(None),
Poll::Ready(Ok(finish)) => Ok(finish(ssl, id)),
Poll::Pending => Err(GetSessionPendingError),
}
};
self.set_get_session_callback(async_callback);
}
/// Configures certificate verification.
///
/// The callback should return `Ok(())` if the certificate is valid.
/// If the certificate is invalid, the callback should return `SslVerifyError::Invalid(alert)`.
/// Some useful alerts include [`SslAlert::CERTIFICATE_EXPIRED`], [`SslAlert::CERTIFICATE_REVOKED`],
/// [`SslAlert::UNKNOWN_CA`], [`SslAlert::BAD_CERTIFICATE`], [`SslAlert::CERTIFICATE_UNKNOWN`],
/// and [`SslAlert::INTERNAL_ERROR`]. See RFC 5246 section 7.2.2 for their precise meanings.
///
/// A task waker must be set on `Ssl` values associated with the resulting
/// `SslContext` with [`SslRef::set_task_waker`].
///
/// See [`SslContextBuilder::set_custom_verify_callback`] for the sync version of this method.
///
/// # Panics
///
/// This method panics if this `Ssl` is associated with a RPK context.
pub fn set_async_custom_verify_callback<F>(&mut self, mode: SslVerifyMode, callback: F)
where
F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
{
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
}
}
#[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
F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
{
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
}
/// Sets the task waker to be used in async callbacks installed on this `Ssl`.
pub fn set_task_waker(&mut self, waker: Option<Waker>) {
self.replace_ex_data(*TASK_WAKER_INDEX, waker);
}
}
fn async_custom_verify_callback<F>(
callback: F,
) -> impl Fn(&mut SslRef) -> Result<(), SslVerifyError>
where
F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
{
move |ssl| {
let fut_poll_result = with_ex_data_future(
&mut *ssl,
*SELECT_CUSTOM_VERIFY_FUTURE_INDEX,
|ssl| ssl,
&callback,
identity,
);
match fut_poll_result {
Poll::Ready(Err(alert)) => Err(SslVerifyError::Invalid(alert)),
Poll::Ready(Ok(finish)) => Ok(finish(ssl).map_err(SslVerifyError::Invalid)?),
Poll::Pending => Err(SslVerifyError::Retry),
}
}
}
/// A fatal error to be returned from async select certificate callbacks.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AsyncSelectCertError;
/// Describes async private key hooks. This is used to off-load signing
/// operations to a custom, potentially asynchronous, backend. Metadata about the
/// key such as the type and size are parsed out of the certificate.
///
/// See [`PrivateKeyMethod`] for the sync version of those hooks.
///
/// [`ssl_private_key_method_st`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#ssl_private_key_method_st
pub trait AsyncPrivateKeyMethod: Send + Sync + 'static {
/// Signs the message `input` using the specified signature algorithm.
///
/// This method uses a function that returns a future whose output is
/// itself a closure that will be passed `ssl` and `output`
/// to finish writing the signature.
///
/// See [`PrivateKeyMethod::sign`] for the sync version of this method.
fn sign(
&self,
ssl: &mut SslRef,
input: &[u8],
signature_algorithm: SslSignatureAlgorithm,
output: &mut [u8],
) -> Result<BoxPrivateKeyMethodFuture, AsyncPrivateKeyMethodError>;
/// Decrypts `input`.
///
/// This method uses a function that returns a future whose output is
/// itself a closure that will be passed `ssl` and `output`
/// to finish decrypting the input.
///
/// See [`PrivateKeyMethod::decrypt`] for the sync version of this method.
fn decrypt(
&self,
ssl: &mut SslRef,
input: &[u8],
output: &mut [u8],
) -> Result<BoxPrivateKeyMethodFuture, AsyncPrivateKeyMethodError>;
}
/// A fatal error to be returned from async private key methods.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AsyncPrivateKeyMethodError;
struct AsyncPrivateKeyMethodBridge(Box<dyn AsyncPrivateKeyMethod>);
impl PrivateKeyMethod for AsyncPrivateKeyMethodBridge {
fn sign(
&self,
ssl: &mut SslRef,
input: &[u8],
signature_algorithm: SslSignatureAlgorithm,
output: &mut [u8],
) -> Result<usize, PrivateKeyMethodError> {
with_private_key_method(ssl, output, |ssl, output| {
<dyn AsyncPrivateKeyMethod>::sign(&*self.0, ssl, input, signature_algorithm, output)
})
}
fn decrypt(
&self,
ssl: &mut SslRef,
input: &[u8],
output: &mut [u8],
) -> Result<usize, PrivateKeyMethodError> {
with_private_key_method(ssl, output, |ssl, output| {
<dyn AsyncPrivateKeyMethod>::decrypt(&*self.0, ssl, input, output)
})
}
fn complete(
&self,
ssl: &mut SslRef,
output: &mut [u8],
) -> Result<usize, PrivateKeyMethodError> {
with_private_key_method(ssl, output, |_, _| {
// This should never be reached, if it does, that's a bug on boring's side,
// which called `complete` without having been returned to with a pending
// future from `sign` or `decrypt`.
if cfg!(debug_assertions) {
panic!("BUG: boring called complete without a pending operation");
}
Err(AsyncPrivateKeyMethodError)
})
}
}
/// Creates and drives a private key method future.
///
/// This is a convenience function for the three methods of impl `PrivateKeyMethod``
/// for `dyn AsyncPrivateKeyMethod`. It relies on [`with_ex_data_future`] to
/// drive the future and then immediately calls the final [`BoxPrivateKeyMethodFinish`]
/// when the future is ready.
fn with_private_key_method(
ssl: &mut SslRef,
output: &mut [u8],
create_fut: impl FnOnce(
&mut SslRef,
&mut [u8],
) -> Result<BoxPrivateKeyMethodFuture, AsyncPrivateKeyMethodError>,
) -> Result<usize, PrivateKeyMethodError> {
let fut_poll_result = with_ex_data_future(
ssl,
*SELECT_PRIVATE_KEY_METHOD_FUTURE_INDEX,
|ssl| ssl,
|ssl| create_fut(ssl, output),
identity,
);
let fut_result = match fut_poll_result {
Poll::Ready(fut_result) => fut_result,
Poll::Pending => return Err(PrivateKeyMethodError::RETRY),
};
let finish = fut_result.or(Err(PrivateKeyMethodError::FAILURE))?;
finish(ssl, output).or(Err(PrivateKeyMethodError::FAILURE))
}
/// Creates and drives a future stored in `ssl_handle`'s `Ssl` at ex data index `index`.
///
/// This function won't even bother storing the future in `index` if the future
/// created by `create_fut` returns `Poll::Ready(_)` on the first poll call.
fn with_ex_data_future<H, R, T, E>(
ssl_handle: &mut H,
index: Index<Ssl, MutOnly<Option<ExDataFuture<R>>>>,
get_ssl_mut: impl Fn(&mut H) -> &mut SslRef,
create_fut: impl FnOnce(&mut H) -> Result<ExDataFuture<R>, E>,
into_result: impl Fn(R) -> Result<T, E>,
) -> Poll<Result<T, E>> {
let ssl = get_ssl_mut(ssl_handle);
let waker = ssl
.ex_data(*TASK_WAKER_INDEX)
.cloned()
.flatten()
.expect("task waker should be set");
let mut ctx = Context::from_waker(&waker);
if let Some(data @ Some(_)) = ssl.ex_data_mut(index).map(MutOnly::get_mut) {
let fut_result = into_result(ready!(data.as_mut().unwrap().as_mut().poll(&mut ctx)));
*data = None;
Poll::Ready(fut_result)
} else {
let mut fut = create_fut(ssl_handle)?;
match fut.as_mut().poll(&mut ctx) {
Poll::Ready(fut_result) => Poll::Ready(into_result(fut_result)),
Poll::Pending => {
get_ssl_mut(ssl_handle).replace_ex_data(index, MutOnly::new(Some(fut)));
Poll::Pending
}
}
}
}

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());
@ -84,17 +84,14 @@ pub unsafe extern "C" fn take_stream<S>(bio: *mut BIO) -> S {
}
pub unsafe fn set_dtls_mtu_size<S>(bio: *mut BIO, mtu_size: usize) {
if mtu_size as u64 > c_long::max_value() as u64 {
panic!(
"Given MTU size {} can't be represented in a positive `c_long` range",
mtu_size
)
if mtu_size as u64 > c_long::MAX as u64 {
panic!("Given MTU size {mtu_size} can't be represented in a positive `c_long` range")
}
state::<S>(bio).dtls_mtu_size = mtu_size as c_long;
}
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());
@ -104,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,
@ -126,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,
@ -166,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
}
@ -200,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());
}
@ -219,7 +228,7 @@ struct BIO_METHOD(*mut ffi::BIO_METHOD);
impl BIO_METHOD {
fn new<S: Read + Write>() -> BIO_METHOD {
unsafe {
let ptr = ffi::BIO_meth_new(ffi::BIO_TYPE_NONE, b"rust\0".as_ptr() as *const _);
let ptr = ffi::BIO_meth_new(ffi::BIO_TYPE_NONE, c"rust".as_ptr().cast());
assert!(!ptr.is_null());
let ret = BIO_METHOD(ptr);
assert!(ffi::BIO_meth_set_write(ptr, Some(bwrite::<S>)) != 0);

View File

@ -1,19 +1,22 @@
#![forbid(unsafe_op_in_unsafe_fn)]
use super::{
AlpnError, ClientHello, PrivateKeyMethod, PrivateKeyMethodError, SelectCertError, SniError,
Ssl, SslAlert, SslContext, SslContextRef, SslRef, SslSession, SslSessionRef,
SslSignatureAlgorithm, SESSION_CTX_INDEX,
AlpnError, CertificateCompressor, ClientHello, GetSessionPendingError, PrivateKeyMethod,
PrivateKeyMethodError, SelectCertError, SniError, Ssl, SslAlert, SslContext, SslContextRef,
SslInfoCallbackAlert, SslInfoCallbackMode, SslInfoCallbackValue, SslRef, SslSession,
SslSessionRef, SslSignatureAlgorithm, SslVerifyError, SESSION_CTX_INDEX,
};
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;
use std::mem::{self, MaybeUninit};
use std::ptr;
use std::slice;
use std::str;
@ -38,9 +41,97 @@ 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) };
verify(preverify_ok != 0, ctx) as c_int
c_int::from(verify(preverify_ok != 0, ctx))
}
pub(super) unsafe extern "C" fn raw_custom_verify<F>(
ssl: *mut ffi::SSL,
out_alert: *mut u8,
) -> ffi::ssl_verify_result_t
where
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
{
let callback = |ssl: &mut SslRef| {
let custom_verify_idx = SslContext::cached_ex_index::<F>();
let ssl_context = ssl.ssl_context().to_owned();
let callback = ssl_context
.ex_data(custom_verify_idx)
.expect("BUG: custom verify callback missing");
callback(ssl)
};
unsafe { raw_custom_verify_callback(ssl, out_alert, callback) }
}
pub(super) unsafe extern "C" fn raw_cert_verify<F>(
x509_ctx: *mut ffi::X509_STORE_CTX,
_arg: *mut c_void,
) -> c_int
where
F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send,
{
// SAFETY: boring provides valid inputs.
let ctx = unsafe { X509StoreContextRef::from_ptr_mut(x509_ctx) };
let ssl_idx = X509StoreContext::ssl_idx().expect("BUG: store context ssl index missing");
let verify_idx = SslContext::cached_ex_index::<F>();
let verify = ctx
.ex_data(ssl_idx)
.expect("BUG: store context missing ssl")
.ssl_context()
.ex_data(verify_idx)
.expect("BUG: verify callback missing");
// 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 { &*std::ptr::from_ref::<F>(verify) };
c_int::from(verify(ctx))
}
pub(super) unsafe extern "C" fn ssl_raw_custom_verify<F>(
ssl: *mut ffi::SSL,
out_alert: *mut u8,
) -> ffi::ssl_verify_result_t
where
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
{
let callback = |ssl: &mut SslRef| {
let callback = ssl
.ex_data(Ssl::cached_ex_index::<Arc<F>>())
.expect("BUG: ssl verify callback missing")
.clone();
callback(ssl)
};
unsafe { raw_custom_verify_callback(ssl, out_alert, callback) }
}
unsafe fn raw_custom_verify_callback(
ssl: *mut ffi::SSL,
out_alert: *mut u8,
callback: impl FnOnce(&mut SslRef) -> Result<(), SslVerifyError>,
) -> ffi::ssl_verify_result_t {
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };
let out_alert = unsafe { &mut *out_alert };
match callback(ssl) {
Ok(()) => ffi::ssl_verify_result_t::ssl_verify_ok,
Err(SslVerifyError::Invalid(alert)) => {
*out_alert = alert.0 as u8;
ffi::ssl_verify_result_t::ssl_verify_invalid
}
Err(SslVerifyError::Retry) => ffi::ssl_verify_result_t::ssl_verify_retry,
}
}
pub(super) unsafe extern "C" fn raw_client_psk<F>(
@ -69,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();
@ -147,7 +238,7 @@ where
.expect("BUG: ssl verify callback missing")
.clone();
callback(preverify_ok != 0, ctx) as c_int
c_int::from(callback(preverify_ok != 0, ctx))
}
pub(super) unsafe extern "C" fn raw_sni<F>(
@ -181,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,
@ -205,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
@ -289,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);
@ -311,7 +464,7 @@ pub(super) unsafe extern "C" fn raw_remove_session<F>(
.ex_data(SslContext::cached_ex_index::<F>())
.expect("BUG: remove session callback missing");
callback(ctx, session)
callback(ctx, session);
}
type DataPtr = *const c_uchar;
@ -323,7 +476,10 @@ pub(super) unsafe extern "C" fn raw_get_session<F>(
copy: *mut c_int,
) -> *mut ffi::SSL_SESSION
where
F: Fn(&mut SslRef, &[u8]) -> Option<SslSession> + 'static + Sync + Send,
F: Fn(&mut SslRef, &[u8]) -> Result<Option<SslSession>, GetSessionPendingError>
+ 'static
+ Sync
+ Send,
{
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };
@ -339,16 +495,18 @@ 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) {
Some(session) => {
let p = session.as_ptr();
mem::forget(session);
Ok(Some(session)) => {
let p = session.into_ptr();
*copy = 0;
p
}
None => ptr::null_mut(),
Ok(None) => ptr::null_mut(),
Err(GetSessionPendingError) => unsafe { ffi::SSL_magic_pending_session_ptr() },
}
}
@ -357,15 +515,15 @@ where
F: Fn(&SslRef, &str) + 'static + Sync + Send,
{
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr(ssl as *mut _) };
let line = unsafe { str::from_utf8_unchecked(CStr::from_ptr(line).to_bytes()) };
let ssl = unsafe { SslRef::from_ptr(ssl.cast_mut()) };
let line = unsafe { CStr::from_ptr(line).to_string_lossy() };
let callback = ssl
.ssl_context()
.ex_data(SslContext::cached_ex_index::<F>())
.expect("BUG: get session callback missing");
callback(ssl, line);
callback(ssl, &line);
}
pub(super) unsafe extern "C" fn raw_sign<M>(
@ -457,3 +615,171 @@ where
Err(err) => err.0,
}
}
pub(super) unsafe extern "C" fn raw_info_callback<F>(
ssl: *const ffi::SSL,
mode: c_int,
value: c_int,
) where
F: Fn(&SslRef, SslInfoCallbackMode, SslInfoCallbackValue) + Send + Sync + 'static,
{
// 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.cast_mut();
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr(mut_ref) };
let ssl_context = ssl.ssl_context();
let callback = ssl_context
.ex_data(SslContext::cached_ex_index::<F>())
.expect("BUG: info callback missing");
let value = match mode {
ffi::SSL_CB_READ_ALERT | ffi::SSL_CB_WRITE_ALERT => {
SslInfoCallbackValue::Alert(SslInfoCallbackAlert(value))
}
_ => SslInfoCallbackValue::Unit,
};
callback(ssl, SslInfoCallbackMode(mode), value);
}
pub(super) unsafe extern "C" fn raw_ssl_cert_compress<C>(
ssl: *mut ffi::SSL,
out: *mut ffi::CBB,
input: *const u8,
input_len: usize,
) -> ::std::os::raw::c_int
where
C: CertificateCompressor,
{
const {
assert!(C::CAN_COMPRESS);
}
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };
let ssl_context = ssl.ssl_context();
let compressor = ssl_context
.ex_data(SslContext::cached_ex_index::<C>())
.expect("BUG: certificate compression missed");
let input_slice = unsafe { std::slice::from_raw_parts(input, input_len) };
let mut writer = CryptoByteBuilder::from_ptr(out);
if compressor.compress(input_slice, &mut writer).is_err() {
return 0;
}
1
}
pub(super) unsafe extern "C" fn raw_ssl_cert_decompress<C>(
ssl: *mut ffi::SSL,
out: *mut *mut ffi::CRYPTO_BUFFER,
uncompressed_len: usize,
input: *const u8,
input_len: usize,
) -> ::std::os::raw::c_int
where
C: CertificateCompressor,
{
const {
assert!(C::CAN_DECOMPRESS);
}
// SAFETY: boring provides valid inputs.
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };
let ssl_context = ssl.ssl_context();
let compressor = ssl_context
.ex_data(SslContext::cached_ex_index::<C>())
.expect("BUG: certificate compression missed");
let Ok(mut decompression_buffer) = CryptoBufferBuilder::with_capacity(uncompressed_len) else {
return 0;
};
let input_slice = unsafe { std::slice::from_raw_parts(input, input_len) };
if compressor
.decompress(input_slice, decompression_buffer.as_writer())
.is_err()
{
return 0;
}
let Ok(crypto_buffer) = decompression_buffer.build() else {
return 0;
};
unsafe { *out = crypto_buffer };
1
}
struct CryptoByteBuilder<'a>(*mut ffi::CBB, std::marker::PhantomData<&'a [u8]>);
impl CryptoByteBuilder<'_> {
fn from_ptr(ptr: *mut ffi::CBB) -> Self {
Self(ptr, Default::default())
}
}
impl std::io::Write for CryptoByteBuilder<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let success = unsafe { ffi::CBB_add_bytes(self.0, buf.as_ptr(), buf.len()) == 1 };
if !success {
return Err(std::io::Error::other("CBB_add_bytes failed"));
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
let success = unsafe { ffi::CBB_flush(self.0) == 1 };
if !success {
return Err(std::io::Error::other("CBB_flush failed"));
}
Ok(())
}
}
struct CryptoBufferBuilder<'a> {
buffer: *mut ffi::CRYPTO_BUFFER,
cursor: std::io::Cursor<&'a mut [u8]>,
}
impl<'a> CryptoBufferBuilder<'a> {
fn with_capacity(capacity: usize) -> Result<CryptoBufferBuilder<'a>, ErrorStack> {
let mut data: *mut u8 = std::ptr::null_mut();
let buffer = unsafe { crate::cvt_p(ffi::CRYPTO_BUFFER_alloc(&mut data, capacity))? };
Ok(CryptoBufferBuilder {
buffer,
cursor: std::io::Cursor::new(unsafe { std::slice::from_raw_parts_mut(data, capacity) }),
})
}
fn as_writer(&mut self) -> &mut (impl std::io::Write + 'a) {
&mut self.cursor
}
fn build(mut self) -> Result<*mut ffi::CRYPTO_BUFFER, ErrorStack> {
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::internal_error_str("invalid len"));
}
// Drop is no-op if the buffer is null
Ok(mem::replace(&mut self.buffer, ptr::null_mut()))
}
}
impl Drop for CryptoBufferBuilder<'_> {
fn drop(&mut self) {
if !self.buffer.is_null() {
unsafe {
boring_sys::CRYPTO_BUFFER_free(self.buffer);
}
}
}
}

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.
@ -137,11 +116,13 @@ impl SslConnector {
}
/// Consumes the `SslConnector`, returning the inner raw `SslContext`.
#[must_use]
pub fn into_context(self) -> SslContext {
self.0
}
/// Returns a shared reference to the inner raw `SslContext`.
#[must_use]
pub fn context(&self) -> &SslContextRef {
&self.0
}
@ -152,6 +133,7 @@ pub struct SslConnectorBuilder(SslContextBuilder);
impl SslConnectorBuilder {
/// Consumes the builder, returning an `SslConnector`.
#[must_use]
pub fn build(self) -> SslConnector {
SslConnector(self.0.build())
}
@ -180,6 +162,7 @@ pub struct ConnectConfiguration {
impl ConnectConfiguration {
/// A builder-style version of `set_use_server_name_indication`.
#[must_use]
pub fn use_server_name_indication(mut self, use_sni: bool) -> ConnectConfiguration {
self.set_use_server_name_indication(use_sni);
self
@ -193,6 +176,7 @@ impl ConnectConfiguration {
}
/// A builder-style version of `set_verify_hostname`.
#[must_use]
pub fn verify_hostname(mut self, verify_hostname: bool) -> ConnectConfiguration {
self.set_verify_hostname(verify_hostname);
self
@ -219,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)?;
}
@ -287,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.
///
@ -310,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)?;
@ -331,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())?;
@ -357,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,
);
@ -396,11 +359,13 @@ impl SslAcceptor {
}
/// Consumes the `SslAcceptor`, returning the inner raw `SslContext`.
#[must_use]
pub fn into_context(self) -> SslContext {
self.0
}
/// Returns a shared reference to the inner raw `SslContext`.
#[must_use]
pub fn context(&self) -> &SslContextRef {
&self.0
}
@ -411,6 +376,7 @@ pub struct SslAcceptorBuilder(SslContextBuilder);
impl SslAcceptorBuilder {
/// Consumes the builder, returning a `SslAcceptor`.
#[must_use]
pub fn build(self) -> SslAcceptor {
SslAcceptor(self.0.build())
}

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)
}

63
boring/src/ssl/ech.rs Normal file
View File

@ -0,0 +1,63 @@
use crate::ffi;
use foreign_types::ForeignType;
use libc::c_int;
use crate::error::ErrorStack;
use crate::hpke::HpkeKey;
use crate::{cvt_0i, cvt_p};
pub struct SslEchKeysBuilder {
keys: SslEchKeys,
}
impl SslEchKeysBuilder {
pub fn new() -> Result<SslEchKeysBuilder, ErrorStack> {
unsafe {
ffi::init();
let keys = cvt_p(ffi::SSL_ECH_KEYS_new())?;
Ok(SslEchKeysBuilder::from_ptr(keys))
}
}
pub unsafe fn from_ptr(keys: *mut ffi::SSL_ECH_KEYS) -> Self {
Self {
keys: SslEchKeys::from_ptr(keys),
}
}
pub fn add_key(
&mut self,
is_retry_config: bool,
ech_config: &[u8],
key: HpkeKey,
) -> Result<(), ErrorStack> {
unsafe {
cvt_0i(ffi::SSL_ECH_KEYS_add(
self.keys.as_ptr(),
c_int::from(is_retry_config),
ech_config.as_ptr(),
ech_config.len(),
key.as_ptr(),
))
.map(|_| ())
}
}
pub fn build(self) -> SslEchKeys {
self.keys
}
}
foreign_type_and_impl_send_sync! {
type CType = ffi::SSL_ECH_KEYS;
fn drop = ffi::SSL_ECH_KEYS_free;
pub struct SslEchKeys;
}
impl SslEchKeys {
pub fn builder() -> Result<SslEchKeysBuilder, ErrorStack> {
SslEchKeysBuilder::new()
}
}

View File

@ -1,18 +1,26 @@
use crate::ffi;
use crate::x509::X509VerifyError;
use libc::c_int;
use openssl_macros::corresponds;
use std::error;
use std::error::Error as StdError;
use std::ffi::CStr;
use std::fmt;
use std::io;
use crate::error::ErrorStack;
use crate::ssl::MidHandshakeSslStream;
/// An error code returned from SSL functions.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
/// `SSL_ERROR_*` error code returned from SSL functions.
///
/// This is different than [packed error codes](crate::error::Error).
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct ErrorCode(c_int);
impl ErrorCode {
/// No error.
pub const NONE: ErrorCode = ErrorCode(ffi::SSL_ERROR_NONE);
/// The SSL session has been closed.
pub const ZERO_RETURN: ErrorCode = ErrorCode(ffi::SSL_ERROR_ZERO_RETURN);
@ -32,6 +40,9 @@ impl ErrorCode {
pub const PENDING_CERTIFICATE: ErrorCode = ErrorCode(ffi::SSL_ERROR_PENDING_CERTIFICATE);
pub const WANT_CERTIFICATE_VERIFY: ErrorCode =
ErrorCode(ffi::SSL_ERROR_WANT_CERTIFICATE_VERIFY);
pub const WANT_PRIVATE_KEY_OPERATION: ErrorCode =
ErrorCode(ffi::SSL_ERROR_WANT_PRIVATE_KEY_OPERATION);
@ -43,14 +54,53 @@ impl ErrorCode {
/// An error occurred in the SSL library.
pub const SSL: ErrorCode = ErrorCode(ffi::SSL_ERROR_SSL);
/// Wrap an `SSL_ERROR_*` error code.
///
/// This is different than [packed error codes](crate::error::Error).
#[must_use]
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
pub fn from_raw(raw: c_int) -> ErrorCode {
ErrorCode(raw)
let code = ErrorCode(raw);
debug_assert!(
raw < 64 || code.description().is_some(),
"{raw} is not an SSL_ERROR_* code"
);
code
}
/// An `SSL_ERROR_*` error code.
///
/// This is different than [packed error codes](crate::error::Error).
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_raw(&self) -> c_int {
self.0
}
#[corresponds(SSL_error_description)]
#[must_use]
pub fn description(self) -> Option<&'static str> {
unsafe {
let msg = ffi::SSL_error_description(self.0);
if msg.is_null() {
return None;
}
CStr::from_ptr(msg).to_str().ok()
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.description().unwrap_or("error"), self.0)
}
}
impl fmt::Debug for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(Debug)]
@ -59,7 +109,7 @@ pub(crate) enum InnerError {
Ssl(ErrorStack),
}
/// An SSL error.
/// A general SSL error, based on [`SSL_ERROR_*` error codes](ErrorCode).
#[derive(Debug)]
pub struct Error {
pub(crate) code: ErrorCode,
@ -67,10 +117,13 @@ pub struct Error {
}
impl Error {
/// An `SSL_ERROR_*` error code.
#[must_use]
pub fn code(&self) -> ErrorCode {
self.code
}
#[must_use]
pub fn io_error(&self) -> Option<&io::Error> {
match self.cause {
Some(InnerError::Io(ref e)) => Some(e),
@ -85,6 +138,8 @@ impl Error {
}
}
/// Stack of [library-specific errors](crate::error::Error), if available.
#[must_use]
pub fn ssl_error(&self) -> Option<&ErrorStack> {
match self.cause {
Some(InnerError::Ssl(ref e)) => Some(e),
@ -92,6 +147,7 @@ impl Error {
}
}
#[must_use]
pub fn would_block(&self) -> bool {
matches!(
self.code,
@ -101,6 +157,7 @@ impl Error {
| ErrorCode::PENDING_SESSION
| ErrorCode::PENDING_CERTIFICATE
| ErrorCode::WANT_PRIVATE_KEY_OPERATION
| ErrorCode::WANT_CERTIFICATE_VERIFY
| ErrorCode::PENDING_TICKET
)
}
@ -117,26 +174,27 @@ impl From<ErrorStack> for Error {
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self.code {
ErrorCode::ZERO_RETURN => fmt.write_str("the SSL session has been shut down"),
let msg = match self.code {
ErrorCode::ZERO_RETURN => "the SSL session has been shut down",
ErrorCode::WANT_READ => match self.io_error() {
Some(_) => fmt.write_str("a nonblocking read call would have blocked"),
None => fmt.write_str("the operation should be retried"),
Some(_) => "a nonblocking read call would have blocked",
None => "the operation should be retried",
},
ErrorCode::WANT_WRITE => match self.io_error() {
Some(_) => fmt.write_str("a nonblocking write call would have blocked"),
None => fmt.write_str("the operation should be retried"),
Some(_) => "a nonblocking write call would have blocked",
None => "the operation should be retried",
},
ErrorCode::SYSCALL => match self.io_error() {
Some(err) => write!(fmt, "{}", err),
None => fmt.write_str("unexpected EOF"),
Some(err) => return err.fmt(fmt),
None => "unexpected EOF",
},
ErrorCode::SSL => match self.ssl_error() {
Some(e) => write!(fmt, "{}", e),
None => fmt.write_str("unknown BoringSSL error"),
Some(err) => return err.fmt(fmt),
None => "unknown BoringSSL error",
},
ErrorCode(code) => write!(fmt, "unknown error code {}", code),
}
ErrorCode(code) => return code.fmt(fmt),
};
fmt.write_str(msg)
}
}
@ -177,7 +235,7 @@ impl<S> fmt::Display for HandshakeError<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
HandshakeError::SetupFailure(ref e) => {
write!(f, "TLS stream setup failed {}", e)
write!(f, "TLS stream setup failed {e}")
}
HandshakeError::Failure(ref s) => fmt_mid_handshake_error(s, f, "TLS handshake failed"),
HandshakeError::WouldBlock(ref s) => {
@ -192,15 +250,16 @@ 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());
}
match s.ssl().verify_result() {
Ok(()) => write!(f, "{}", prefix)?,
Err(verify) => write!(f, "{}: cert verification failed - {}", prefix, verify)?,
// INVALID_CALL is returned if no verification took place,
// such as before a cert is sent.
Ok(()) | Err(X509VerifyError::INVALID_CALL) => write!(f, "{prefix}")?,
Err(verify) => write!(f, "{prefix}: cert verification failed - {verify}")?,
}
write!(f, " {}", s.error())

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
pub(crate) struct MutOnly<T>(T);
impl<T> MutOnly<T> {
pub(crate) fn new(value: T) -> Self {
Self(value)
}
pub(crate) fn get_mut(&mut self) -> &mut T {
&mut self.0
}
}
/// SAFETY: The type does not let anyone get a &T so Sync is irrelevant.
unsafe impl<T> Sync for MutOnly<T> {}

View File

@ -0,0 +1,102 @@
use std::io::Write as _;
use super::server::Server;
use crate::ssl::CertificateCompressor;
use crate::x509::store::X509StoreBuilder;
use crate::x509::X509;
struct BrotliCompressor {
q: u32,
lgwin: u32,
}
impl Default for BrotliCompressor {
fn default() -> Self {
Self { q: 11, lgwin: 32 }
}
}
impl CertificateCompressor for BrotliCompressor {
const ALGORITHM: crate::ssl::CertificateCompressionAlgorithm =
crate::ssl::CertificateCompressionAlgorithm(1234);
const CAN_COMPRESS: bool = true;
const CAN_DECOMPRESS: bool = true;
fn compress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
where
W: std::io::Write,
{
let mut writer = brotli::CompressorWriter::new(output, 1024, self.q, self.lgwin);
writer.write_all(input)?;
Ok(())
}
fn decompress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
where
W: std::io::Write,
{
brotli::BrotliDecompress(&mut std::io::Cursor::new(input), output)?;
Ok(())
}
}
#[test]
fn server_only_cert_compression() {
let mut server_builder = Server::builder();
server_builder
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();
let server = server_builder.build();
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(&x509).unwrap();
let client = server.client();
client.connect();
}
#[test]
fn client_only_cert_compression() {
let server_builder = Server::builder().build();
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(&x509).unwrap();
let mut client = server_builder.client();
client
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();
client.connect();
}
#[test]
fn client_and_server_cert_compression() {
let mut server = Server::builder();
server
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();
let server = server.build();
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(&x509).unwrap();
let mut client = server.client();
client
.ctx()
.add_certificate_compression_algorithm(BrotliCompressor::default())
.unwrap();
client.connect();
}

View File

@ -0,0 +1,132 @@
use crate::hash::MessageDigest;
use crate::ssl::test::Server;
use crate::ssl::SslVerifyMode;
#[test]
fn error_when_trusted_but_callback_returns_false() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_cert_verify_callback(|x509| {
// The cert is OK
assert!(x509.verify_cert().unwrap());
assert!(x509.current_cert().is_some());
assert!(x509.verify_result().is_ok());
// But we return false
false
});
client.connect_err();
}
#[test]
fn no_error_when_untrusted_but_callback_returns_true() {
let server = Server::builder().build();
let mut client = server.client();
client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_cert_verify_callback(|x509| {
// The cert is not OK
assert!(!x509.verify_cert().unwrap());
assert!(x509.current_cert().is_some());
assert!(x509.verify_result().is_err());
// But we return true
true
});
client.connect();
}
#[test]
fn no_error_when_trusted_and_callback_returns_true() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_cert_verify_callback(|x509| {
// The cert is OK
assert!(x509.verify_cert().unwrap());
assert!(x509.current_cert().is_some());
assert!(x509.verify_result().is_ok());
// And we return true
true
});
client.connect();
}
#[test]
fn callback_receives_correct_certificate() {
// Server sends the full chain (leaf + root)...
let server = Server::builder_full_chain().build();
// but client doesn't load the root as trusted.
// So we expect an error.
let mut client = server.client();
let leaf_sha1 = "59172d9313e84459bcff27f967e79e6e9217e584";
let root_sha1 = "c0cbdf7cdd03c9773e5468e1f6d2da7d5cbb1875";
client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_cert_verify_callback(move |x509| {
assert!(!x509.verify_cert().unwrap());
// This is set to the root, since that's the problematic cert.
assert!(x509.current_cert().is_some());
// This is set to the leaf, since that's the cert we're verifying.
assert!(x509.cert().is_some());
assert!(x509.verify_result().is_err());
let root = x509
.current_cert()
.unwrap()
.digest(MessageDigest::sha1())
.unwrap();
assert_eq!(hex::encode(root), root_sha1);
let leaf = x509.cert().unwrap().digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(leaf), leaf_sha1);
// Test that `untrusted` is set to the original chain.
assert_eq!(x509.untrusted().unwrap().len(), 2);
let leaf = x509
.untrusted()
.unwrap()
.get(0)
.unwrap()
.digest(MessageDigest::sha1())
.unwrap();
assert_eq!(hex::encode(leaf), leaf_sha1);
let root = x509
.untrusted()
.unwrap()
.get(1)
.unwrap()
.digest(MessageDigest::sha1())
.unwrap();
assert_eq!(hex::encode(root), root_sha1);
true
});
client.connect();
}
#[test]
fn callback_receives_correct_chain() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca();
let leaf_sha1 = "59172d9313e84459bcff27f967e79e6e9217e584";
let root_sha1 = "c0cbdf7cdd03c9773e5468e1f6d2da7d5cbb1875";
client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_cert_verify_callback(move |x509| {
assert!(x509.verify_cert().unwrap());
assert!(x509.current_cert().is_some());
assert!(x509.verify_result().is_ok());
let chain = x509.chain().unwrap();
assert!(chain.len() == 2);
let leaf_cert = chain.get(0).unwrap();
let leaf_digest = leaf_cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(leaf_digest), leaf_sha1);
let root_cert = chain.get(1).unwrap();
let root_digest = root_cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(root_digest), root_sha1);
true
});
client.connect();
}

View File

@ -0,0 +1,278 @@
use super::server::Server;
use crate::ssl::{ErrorCode, HandshakeError, SslAlert, SslVerifyMode};
use crate::x509::X509StoreContext;
use crate::{hash::MessageDigest, ssl::SslVerifyError};
use hex;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn untrusted_callback_override_bad() {
let mut server = Server::builder();
server.err_cb(|err| {
let HandshakeError::Failure(handshake) = err else {
panic!("expected failure error");
};
assert_eq!(
handshake.error().to_string(),
"[SSLV3_ALERT_CERTIFICATE_REVOKED]"
);
});
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, |_| {
Err(SslVerifyError::Invalid(SslAlert::CERTIFICATE_REVOKED))
});
client.connect_err();
}
#[test]
fn untrusted_callback_override_ok() {
let server = Server::builder().build();
let mut client = server.client();
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, |ssl| {
assert!(ssl.peer_cert_chain().is_some());
Ok(())
});
client.connect();
}
#[test]
fn untrusted_with_set_cert() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
let store = ssl.ssl_context().cert_store();
let cert = ssl.peer_certificate().unwrap();
let cert_chain = ssl.peer_cert_chain().unwrap();
assert_eq!(store.objects_len(), 0);
X509StoreContext::new()
.unwrap()
.init(store, &cert, cert_chain, |store_ctx| {
assert!(!store_ctx.verify_cert().unwrap());
assert!(store_ctx.verify_result().is_err());
Ok(())
})
.unwrap();
Err(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))
});
client.connect_err();
}
#[test]
fn trusted_with_set_cert() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
let store = ssl.ssl_context().cert_store();
let cert = ssl.peer_certificate().unwrap();
let cert_chain = ssl.peer_cert_chain().unwrap();
assert_eq!(store.objects_len(), 1);
X509StoreContext::new()
.unwrap()
.init(store, &cert, cert_chain, |store_ctx| {
assert!(store_ctx.verify_cert().unwrap());
assert_eq!(store_ctx.verify_result(), Ok(()));
Ok(())
})
.unwrap();
Ok(())
});
client.connect();
}
#[test]
fn trusted_callback_override_ok() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, |ssl| {
assert!(ssl.peer_certificate().is_some());
Ok(())
});
client.connect();
}
#[test]
fn trusted_callback_override_bad() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, |_| {
Err(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))
});
client.connect_err();
}
#[test]
fn callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, _| {
panic!("verify callback should not be called");
});
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
CALLED_BACK.store(true, Ordering::SeqCst);
let cert = ssl.peer_certificate().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), expected);
Ok(())
});
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn ssl_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client().build().builder();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
client
.ssl()
.set_verify_callback(SslVerifyMode::PEER, |_, _| {
panic!("verify callback should not be called");
});
client
.ssl()
.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
CALLED_BACK.store(true, Ordering::SeqCst);
let cert = ssl.peer_certificate().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), expected);
Ok(())
});
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn both_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client();
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, |_| {
panic!("verify callback should not be called");
});
let mut client = client.build().builder();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
client
.ssl()
.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
CALLED_BACK.store(true, Ordering::SeqCst);
let cert = ssl.peer_certificate().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), expected);
Ok(())
});
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn retry() {
let mut server = Server::builder();
server.err_cb(|err| {
let HandshakeError::Failure(handshake) = err else {
panic!("expected failure error");
};
assert_eq!(
handshake.error().to_string(),
"[SSLV3_ALERT_CERTIFICATE_REVOKED]"
);
});
let server = server.build();
let mut client = server.client();
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
client
.ctx()
.set_custom_verify_callback(SslVerifyMode::PEER, move |_| {
if !CALLED_BACK.swap(true, Ordering::SeqCst) {
return Err(SslVerifyError::Retry);
}
Err(SslVerifyError::Invalid(SslAlert::CERTIFICATE_REVOKED))
});
let HandshakeError::WouldBlock(handshake) = client.connect_err() else {
panic!("should be WouldBlock");
};
assert!(CALLED_BACK.load(Ordering::SeqCst));
assert!(handshake.error().would_block());
assert_eq!(handshake.error().code(), ErrorCode::WANT_CERTIFICATE_VERIFY);
handshake.handshake().unwrap_err();
}

View File

@ -0,0 +1,73 @@
use crate::hpke::HpkeKey;
use crate::ssl::ech::SslEchKeys;
use crate::ssl::test::server::{ClientSslBuilder, Server};
use crate::ssl::HandshakeError;
// For future reference, these configs are generated by building the bssl tool (the binary is built
// alongside boringssl) and running the following command:
//
// ./bssl generate-ech -out-ech-config-list ./list -out-ech-config ./config -out-private-key ./key
// -public-name ech.com -config-id 1
static ECH_CONFIG_LIST: &[u8] = include_bytes!("../../../test/echconfiglist");
static ECH_CONFIG: &[u8] = include_bytes!("../../../test/echconfig");
static ECH_KEY: &[u8] = include_bytes!("../../../test/echkey");
static ECH_CONFIG_2: &[u8] = include_bytes!("../../../test/echconfig-2");
static ECH_KEY_2: &[u8] = include_bytes!("../../../test/echkey-2");
fn bootstrap_ech(config: &[u8], key: &[u8], list: &[u8]) -> (Server, ClientSslBuilder) {
let server = {
let key = HpkeKey::dhkem_p256_sha256(key).unwrap();
let mut ech_keys_builder = SslEchKeys::builder().unwrap();
ech_keys_builder.add_key(true, config, key).unwrap();
let ech_keys = ech_keys_builder.build();
let mut builder = Server::builder();
builder.ctx().set_ech_keys(&ech_keys).unwrap();
builder.build()
};
let mut client = server.client_with_root_ca().build().builder();
client.ssl().set_ech_config_list(list).unwrap();
client.ssl().set_hostname("foobar.com").unwrap();
(server, client)
}
#[test]
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());
}
#[test]
fn ech_rejection() {
// Server is initialized using `ECH_CONFIG_2`, so using `ECH_CONFIG_LIST` instead of
// `ECH_CONFIG_LIST_2` should trigger rejection.
let (_server, client) = bootstrap_ech(ECH_CONFIG_2, ECH_KEY_2, ECH_CONFIG_LIST);
let HandshakeError::Failure(failed_ssl_stream) = client.connect_err() else {
panic!("wrong HandshakeError failure variant!");
};
assert_eq!(
failed_ssl_stream.ssl().get_ech_name_override(),
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());
}
#[test]
fn ech_grease() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca().build().builder();
// Verified with a pcap locally that the ECH extension gets sent due to GREASE
client.ssl().set_enable_ech_grease(true);
let ssl_stream = client.connect();
assert!(!ssl_stream.ssl().ech_accepted());
}

View File

@ -1,236 +1,46 @@
#![allow(unused_imports)]
use hex;
use std::cell::Cell;
use std::env;
use std::fs::File;
use foreign_types::{ForeignType, ForeignTypeRef};
use std::io;
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::iter;
use std::mem;
use std::net::UdpSocket;
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::net::{TcpListener, TcpStream};
use std::path::Path;
use std::process::{Child, ChildStdin, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use crate::dh::Dh;
use crate::error::ErrorStack;
use crate::hash::MessageDigest;
use crate::pkey::PKey;
use crate::srtp::SrtpProfileId;
use crate::ssl;
use crate::ssl::test::server::Server;
use crate::ssl::SslVersion;
use crate::ssl::{
Error, ExtensionType, HandshakeError, MidHandshakeSslStream, ShutdownResult, ShutdownState,
Ssl, SslAcceptor, SslAcceptorBuilder, SslConnector, SslContext, SslContextBuilder, SslFiletype,
SslMethod, SslOptions, SslSessionCacheMode, SslStream, SslStreamBuilder, SslVerifyMode,
StatusType,
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, X509StoreContext, X509};
use crate::x509::{X509Name, X509};
use super::CompliancePolicy;
mod cert_compressor;
mod cert_verify;
mod custom_verify;
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");
static CERT: &[u8] = include_bytes!("../../../test/cert.pem");
static KEY: &[u8] = include_bytes!("../../../test/key.pem");
#[test]
fn verify_untrusted() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client();
client.ctx().set_verify(SslVerifyMode::PEER);
client.connect_err();
}
#[test]
fn verify_trusted() {
let server = Server::builder().build();
let client = server.client_with_root_ca();
client.connect();
}
#[test]
fn verify_trusted_with_set_cert() {
let server = Server::builder().build();
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(ROOT_CERT).unwrap();
store.add_cert(x509).unwrap();
let mut client = server.client();
client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_verify_cert_store(store.build()).unwrap();
client.connect();
}
#[test]
fn verify_untrusted_callback_override_ok() {
let server = Server::builder().build();
let mut client = server.client();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, x509| {
assert!(x509.current_cert().is_some());
true
});
client.connect();
}
#[test]
fn verify_untrusted_callback_override_bad() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, _| false);
client.connect_err();
}
#[test]
fn verify_trusted_callback_override_ok() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, x509| {
assert!(x509.current_cert().is_some());
true
});
client.connect();
}
#[test]
fn verify_trusted_callback_override_bad() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, _| false);
client.connect_err();
}
#[test]
fn verify_callback_load_certs() {
let server = Server::builder().build();
let mut client = server.client();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, x509| {
assert!(x509.current_cert().is_some());
true
});
client.connect();
}
#[test]
fn verify_trusted_get_error_ok() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, x509| {
assert_eq!(x509.verify_result(), Ok(()));
true
});
client.connect();
}
#[test]
fn verify_trusted_get_error_err() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, x509| {
assert!(x509.verify_result().is_err());
false
});
client.connect_err();
}
#[test]
fn verify_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, move |_, x509| {
CALLED_BACK.store(true, Ordering::SeqCst);
let cert = x509.current_cert().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), expected);
true
});
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn ssl_verify_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client().build().builder();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
client
.ssl()
.set_verify_callback(SslVerifyMode::PEER, move |_, x509| {
CALLED_BACK.store(true, Ordering::SeqCst);
let cert = x509.current_cert().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), expected);
true
});
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn get_ctx_options() {
let ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.options();
let _ = ctx.options();
}
#[test]
@ -468,6 +278,13 @@ fn test_alpn_server_select_none() {
assert_eq!(None, s.ssl().selected_alpn_protocol());
}
#[test]
fn test_empty_alpn() {
assert_eq!(ssl::select_next_proto(b"", b""), None);
assert_eq!(ssl::select_next_proto(b"", b"\x08http/1.1"), None);
assert_eq!(ssl::select_next_proto(b"\x08http/1.1", b""), None);
}
#[test]
fn test_alpn_server_unilateral() {
let server = Server::builder().build();
@ -490,6 +307,52 @@ fn test_select_cert_ok() {
client.connect();
}
#[test]
fn test_mutable_store() {
#![allow(deprecated)]
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let cert2 = include_bytes!("../../../test/root-ca.pem");
let cert2 = X509::from_pem(cert2).unwrap();
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.cert_store_mut().add_cert(cert.clone()).unwrap();
assert_eq!(1, ctx.cert_store().objects_len());
ctx.set_cert_store_builder(X509StoreBuilder::new().unwrap());
assert_eq!(0, ctx.cert_store().objects_len());
ctx.cert_store_mut().add_cert(cert.clone()).unwrap();
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();
let new_store = new_store.build();
assert_eq!(2, new_store.objects_len());
ctx.set_cert_store_ref(&new_store);
assert_eq!(2, ctx.cert_store().objects_len());
assert!(std::ptr::eq(new_store.as_ptr(), ctx.cert_store().as_ptr()));
let ctx = ctx.build();
assert!(std::ptr::eq(new_store.as_ptr(), ctx.cert_store().as_ptr()));
drop(new_store);
assert_eq!(2, ctx.cert_store().objects_len());
}
#[test]
#[should_panic(expected = "mutated")]
fn shared_store_must_not_be_mutated() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
let shared = X509StoreBuilder::new().unwrap().build();
ctx.set_cert_store_ref(&shared);
ctx.cert_store_mut();
}
#[test]
fn test_select_cert_error() {
let mut server = Server::builder();
@ -554,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() {
@ -662,7 +587,7 @@ fn refcount_ssl_context() {
#[test]
#[cfg_attr(target_os = "windows", ignore)]
#[cfg_attr(all(target_os = "macos", feature = "vendored"), ignore)]
#[cfg_attr(all(target_os = "macos"), ignore)]
fn default_verify_paths() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.set_default_verify_paths().unwrap();
@ -699,12 +624,81 @@ fn verify_valid_hostname() {
client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder();
client.ssl().param_mut().set_host("foobar.com").unwrap();
client.connect();
}
#[test]
fn verify_valid_hostname_with_wildcard() {
let mut server = Server::builder();
server
.ctx()
.set_certificate_chain_file("test/cert-wildcard.pem")
.unwrap();
let server = server.build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder();
client.ssl().param_mut().set_host("yes.foobar.com").unwrap();
client.connect();
}
#[test]
fn verify_reject_underscore_hostname_with_wildcard() {
let mut server = Server::builder();
server.should_error();
server
.ctx()
.set_certificate_chain_file("test/cert-wildcard.pem")
.unwrap();
let server = server.build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder();
client
.ssl()
.param_mut()
.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS);
client.ssl().param_mut().set_host("foobar.com").unwrap();
.set_host("not_allowed.foobar.com")
.unwrap();
client.connect_err();
}
#[cfg(feature = "underscore-wildcards")]
#[test]
fn verify_allow_underscore_hostname_with_wildcard() {
let mut server = Server::builder();
server
.ctx()
.set_certificate_chain_file("test/cert-wildcard.pem")
.unwrap();
let server = server.build();
let mut client = server.client_with_root_ca();
client.ctx().set_verify(SslVerifyMode::PEER);
let mut client = client.build().builder();
client
.ssl()
.param_mut()
.set_hostflags(X509CheckFlags::UNDERSCORE_WILDCARDS);
client
.ssl()
.param_mut()
.set_host("now_allowed.foobar.com")
.unwrap();
client.connect();
}
@ -882,92 +876,6 @@ fn client_ca_list() {
ctx.set_client_ca_list(names);
}
#[test]
fn cert_store() {
let server = Server::builder().build();
let mut client = server.client();
let cert = X509::from_pem(ROOT_CERT).unwrap();
client.ctx().cert_store_mut().add_cert(cert).unwrap();
client.ctx().set_verify(SslVerifyMode::PEER);
client.connect();
}
#[test]
fn idle_session() {
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
let ssl = Ssl::new(&ctx).unwrap();
assert!(ssl.session().is_none());
}
#[test]
fn active_session() {
let server = Server::builder().build();
let s = server.client().connect();
let session = s.ssl().session().unwrap();
let len = session.master_key_len();
let mut buf = vec![0; len - 1];
let copied = session.master_key(&mut buf);
assert_eq!(copied, buf.len());
let mut buf = vec![0; len + 1];
let copied = session.master_key(&mut buf);
assert_eq!(copied, len);
}
#[test]
fn new_session_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let mut server = Server::builder();
server.ctx().set_session_id_context(b"foo").unwrap();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT | SslSessionCacheMode::NO_INTERNAL);
client
.ctx()
.set_new_session_callback(|_, _| CALLED_BACK.store(true, Ordering::SeqCst));
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn new_session_callback_swapped_ctx() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let mut server = Server::builder();
server.ctx().set_session_id_context(b"foo").unwrap();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT | SslSessionCacheMode::NO_INTERNAL);
client
.ctx()
.set_new_session_callback(|_, _| CALLED_BACK.store(true, Ordering::SeqCst));
let mut client = client.build().builder();
let ctx = SslContextBuilder::new(SslMethod::tls()).unwrap().build();
client.ssl().set_ssl_context(&ctx).unwrap();
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn keying_export() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
@ -1107,29 +1015,211 @@ fn sni_callback_swapped_ctx() {
}
#[test]
fn session_cache_size() {
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();
assert!(curve.is_some());
let curve_name = client_stream.ssl().curve_name();
assert!(curve_name.is_some());
}
#[test]
fn test_get_ciphers() {
let ctx_builder = SslContext::builder(SslMethod::tls()).unwrap();
let ctx_builder_ciphers: Vec<&str> = ctx_builder
.ciphers()
.unwrap()
.into_iter()
.map(|v| v.name())
.collect();
assert!(!(ctx_builder_ciphers.is_empty()));
let ctx = ctx_builder.build();
let ctx_ciphers: Vec<&str> = ctx
.ciphers()
.unwrap()
.into_iter()
.map(|v| v.name())
.collect();
assert!(!(ctx_ciphers.is_empty()));
assert_eq!(ctx_builder_ciphers.len(), ctx_ciphers.len());
for (ctx_builder_cipher, ctx_cipher) in ctx_builder_ciphers.into_iter().zip(ctx_ciphers) {
assert_eq!(ctx_builder_cipher, ctx_cipher);
}
}
#[test]
fn test_set_compliance() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.set_session_cache_size(1234);
let ctx = ctx.build();
assert_eq!(ctx.session_cache_size(), 1234);
ctx.set_compliance_policy(CompliancePolicy::FIPS_202205)
.unwrap();
assert_eq!(ctx.max_proto_version().unwrap(), SslVersion::TLS1_3);
assert_eq!(ctx.min_proto_version().unwrap(), SslVersion::TLS1_2);
const FIPS_CIPHERS: [&str; 4] = [
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
];
let ciphers = ctx.ciphers().unwrap();
assert_eq!(ciphers.len(), FIPS_CIPHERS.len());
for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) {
assert_eq!(cipher.0.name(), cipher.1);
}
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.set_compliance_policy(CompliancePolicy::WPA3_192_202304)
.unwrap();
assert_eq!(ctx.max_proto_version().unwrap(), SslVersion::TLS1_3);
assert_eq!(ctx.min_proto_version().unwrap(), SslVersion::TLS1_2);
const WPA3_192_CIPHERS: [&str; 2] = [
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
];
let ciphers = ctx.ciphers().unwrap();
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);
}
ctx.set_compliance_policy(CompliancePolicy::NONE)
.expect_err("Testing expect err if set compliance policy to NONE");
}
#[cfg(feature = "kx-safe-default")]
#[test]
fn client_set_default_curves_list() {
let ssl_ctx = SslContextBuilder::new(SslMethod::tls()).unwrap().build();
let mut ssl = Ssl::new(&ssl_ctx).unwrap();
fn drop_ex_data_in_context() {
let index = SslContext::new_ex_index::<&'static str>().unwrap();
let mut ctx = SslContext::builder(SslMethod::dtls()).unwrap();
// Panics if Kyber768 missing in boringSSL.
ssl.client_set_default_curves_list();
assert_eq!(ctx.replace_ex_data(index, "comté"), None);
assert_eq!(ctx.replace_ex_data(index, "camembert"), Some("comté"));
assert_eq!(ctx.replace_ex_data(index, "raclette"), Some("camembert"));
}
#[cfg(feature = "kx-safe-default")]
#[test]
fn server_set_default_curves_list() {
let ssl_ctx = SslContextBuilder::new(SslMethod::tls()).unwrap().build();
let mut ssl = Ssl::new(&ssl_ctx).unwrap();
fn drop_ex_data_in_ssl() {
let index = Ssl::new_ex_index::<&'static str>().unwrap();
let ctx = SslContext::builder(SslMethod::dtls()).unwrap().build();
let mut ssl = Ssl::new(&ctx).unwrap();
// Panics if Kyber768 missing in boringSSL.
ssl.server_set_default_curves_list();
assert_eq!(ssl.replace_ex_data(index, "comté"), None);
assert_eq!(ssl.replace_ex_data(index, "camembert"), Some("comté"));
assert_eq!(ssl.replace_ex_data(index, "raclette"), Some("camembert"));
}
#[test]
fn test_info_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client();
client.ctx().set_info_callback(move |_, _, _| {
CALLED_BACK.store(true, Ordering::Relaxed);
});
client.connect();
assert!(CALLED_BACK.load(Ordering::Relaxed));
}
#[test]
fn test_ssl_set_compliance() {
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
let mut ssl = Ssl::new(&ctx).unwrap();
ssl.set_compliance_policy(CompliancePolicy::FIPS_202205)
.unwrap();
assert_eq!(ssl.max_proto_version().unwrap(), SslVersion::TLS1_3);
assert_eq!(ssl.min_proto_version().unwrap(), SslVersion::TLS1_2);
const FIPS_CIPHERS: [&str; 4] = [
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
];
let ciphers = ssl.ciphers();
assert_eq!(ciphers.len(), FIPS_CIPHERS.len());
for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) {
assert_eq!(cipher.0.name(), cipher.1);
}
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
let mut ssl = Ssl::new(&ctx).unwrap();
ssl.set_compliance_policy(CompliancePolicy::WPA3_192_202304)
.unwrap();
assert_eq!(ssl.max_proto_version().unwrap(), SslVersion::TLS1_3);
assert_eq!(ssl.min_proto_version().unwrap(), SslVersion::TLS1_2);
const WPA3_192_CIPHERS: [&str; 2] = [
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
];
let ciphers = ssl.ciphers();
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);
}
ssl.set_compliance_policy(CompliancePolicy::NONE)
.expect_err("Testing expect err if set compliance policy to NONE");
}
#[test]
fn ex_data_drop() {
use crate::ssl::SslContextBuilder;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::Arc;
struct TrackDrop(Arc<AtomicU32>);
impl Drop for TrackDrop {
fn drop(&mut self) {
self.0.fetch_add(1, Relaxed);
}
}
let mut ctx = SslContextBuilder::new(SslMethod::tls()).unwrap();
let index = SslContext::new_ex_index().unwrap();
let d1 = Arc::new(AtomicU32::new(100));
let d2 = Arc::new(AtomicU32::new(200));
let d3 = Arc::new(AtomicU32::new(300));
ctx.set_ex_data(index, TrackDrop(d1.clone()));
assert_eq!(100, d1.load(Relaxed));
assert_eq!(200, d2.load(Relaxed));
ctx.replace_ex_data(index, TrackDrop(d2.clone()));
assert_eq!(101, d1.load(Relaxed));
assert_eq!(200, d2.load(Relaxed));
ctx.replace_ex_data(index, TrackDrop(d3.clone()));
assert_eq!(101, d1.load(Relaxed));
assert_eq!(201, d2.load(Relaxed));
assert_eq!(300, d3.load(Relaxed));
drop(ctx);
assert_eq!(101, d1.load(Relaxed));
assert_eq!(201, d2.load(Relaxed));
assert_eq!(301, d3.load(Relaxed));
let mut ctx2 = SslContextBuilder::new(SslMethod::tls()).unwrap();
ctx2.set_ex_data(index, TrackDrop(d1.clone()));
ctx2.set_ex_data(index, TrackDrop(d2.clone()));
drop(ctx2);
assert_eq!(102, d1.load(Relaxed));
assert_eq!(202, d2.load(Relaxed));
}

View File

@ -1,8 +1,6 @@
use once_cell::sync::OnceCell;
use super::server::{Builder, Server};
use super::KEY;
use crate::hash::{Hasher, MessageDigest};
use crate::hash::MessageDigest;
use crate::pkey::PKey;
use crate::rsa::Padding;
use crate::sign::{RsaPssSaltlen, Signer};
@ -10,11 +8,9 @@ use crate::ssl::{
ErrorCode, HandshakeError, PrivateKeyMethod, PrivateKeyMethodError, SslRef,
SslSignatureAlgorithm,
};
use crate::x509::X509;
use std::cmp;
use std::io::{Read, Write};
use std::io::Write;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
#[allow(clippy::type_complexity)]
pub(super) struct Method {
@ -235,7 +231,7 @@ fn test_sign_ok() {
#[test]
fn test_sign_retry_complete_ok() {
let input_cell = Arc::new(OnceCell::new());
let input_cell = Arc::new(OnceLock::new());
let input_cell_clone = input_cell.clone();
let mut builder = builder_with_private_key_method(

View File

@ -3,8 +3,7 @@ use std::net::{SocketAddr, TcpListener, TcpStream};
use std::thread::{self, JoinHandle};
use crate::ssl::{
HandshakeError, MidHandshakeSslStream, Ssl, SslContext, SslContextBuilder, SslFiletype,
SslMethod, SslRef, SslStream,
HandshakeError, Ssl, SslContext, SslContextBuilder, SslFiletype, SslMethod, SslRef, SslStream,
};
pub struct Server {
@ -33,6 +32,25 @@ impl Server {
io_cb: Box::new(|_| {}),
err_cb: Box::new(|_| {}),
should_error: false,
expected_connections_count: 1,
}
}
/// Serves the leaf and the root together.
pub fn builder_full_chain() -> Builder {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
// Uses certs.pem instead of cert.pem.
ctx.set_certificate_chain_file("test/certs.pem").unwrap();
ctx.set_private_key_file("test/key.pem", SslFiletype::PEM)
.unwrap();
Builder {
ctx,
ssl_cb: Box::new(|_| {}),
io_cb: Box::new(|_| {}),
err_cb: Box::new(|_| {}),
should_error: false,
expected_connections_count: 1,
}
}
@ -62,6 +80,7 @@ pub struct Builder {
io_cb: Box<dyn FnMut(SslStream<TcpStream>) + Send>,
err_cb: Box<dyn FnMut(HandshakeError<TcpStream>) + Send>,
should_error: bool,
expected_connections_count: usize,
}
impl Builder {
@ -93,6 +112,10 @@ impl Builder {
self.should_error = true;
}
pub fn expected_connections_count(&mut self, count: usize) {
self.expected_connections_count = count;
}
pub fn build(self) -> Server {
let ctx = self.ctx.build();
let socket = TcpListener::bind("127.0.0.1:0").unwrap();
@ -101,18 +124,27 @@ impl Builder {
let mut io_cb = self.io_cb;
let mut err_cb = self.err_cb;
let should_error = self.should_error;
let mut count = self.expected_connections_count;
let handle = thread::spawn(move || {
let socket = socket.accept().unwrap().0;
let mut ssl = Ssl::new(&ctx).unwrap();
ssl_cb(&mut ssl);
let r = ssl.accept(socket);
if should_error {
err_cb(r.unwrap_err());
} else {
let mut socket = r.unwrap();
socket.write_all(&[0]).unwrap();
io_cb(socket);
while count > 0 {
let socket = socket.accept().unwrap().0;
let mut ssl = Ssl::new(&ctx).unwrap();
ssl_cb(&mut ssl);
let r = ssl.accept(socket);
if should_error {
err_cb(r.unwrap_err());
} else {
let mut socket = r.unwrap();
socket.write_all(&[0]).unwrap();
io_cb(socket);
}
count -= 1;
}
});

View File

@ -0,0 +1,186 @@
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
use crate::ssl::test::server::Server;
use crate::ssl::{
ErrorCode, GetSessionPendingError, HandshakeError, Ssl, SslContext, SslContextBuilder,
SslMethod, SslOptions, SslSession, SslSessionCacheMode, SslVersion,
};
#[test]
fn idle_session() {
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
let ssl = Ssl::new(&ctx).unwrap();
assert!(ssl.session().is_none());
}
#[test]
fn active_session() {
let server = Server::builder().build();
let s = server.client().connect();
let session = s.ssl().session().unwrap();
let len = session.master_key_len();
let mut buf = vec![0; len - 1];
let copied = session.master_key(&mut buf);
assert_eq!(copied, buf.len());
let mut buf = vec![0; len + 1];
let copied = session.master_key(&mut buf);
assert_eq!(copied, len);
}
#[test]
fn new_get_session_callback() {
static FOUND_SESSION: AtomicBool = AtomicBool::new(false);
static SERVER_SESSION_DER: OnceLock<Vec<u8>> = OnceLock::new();
static CLIENT_SESSION_DER: OnceLock<Vec<u8>> = OnceLock::new();
let mut server = Server::builder();
server.expected_connections_count(2);
server
.ctx()
.set_max_proto_version(Some(SslVersion::TLS1_2))
.unwrap();
server.ctx().set_options(SslOptions::NO_TICKET);
server
.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();
});
unsafe {
server.ctx().set_get_session_callback(|_, id| {
let Some(der) = SERVER_SESSION_DER.get() else {
return Ok(None);
};
let session = SslSession::from_der(der).unwrap();
FOUND_SESSION.store(true, Ordering::SeqCst);
assert_eq!(id, session.id());
Ok(Some(session))
});
}
server.ctx().set_session_id_context(b"foo").unwrap();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
client.ctx().set_new_session_callback(|_, session| {
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap();
});
let client = client.build();
client.builder().connect();
assert!(CLIENT_SESSION_DER.get().is_some());
assert!(SERVER_SESSION_DER.get().is_some());
assert!(!FOUND_SESSION.load(Ordering::SeqCst));
let mut ssl_builder = client.builder();
unsafe {
ssl_builder
.ssl()
.set_session(&SslSession::from_der(CLIENT_SESSION_DER.get().unwrap()).unwrap())
.unwrap();
}
ssl_builder.connect();
assert!(FOUND_SESSION.load(Ordering::SeqCst));
}
#[test]
fn new_get_session_callback_pending() {
static CALLED_SERVER_CALLBACK: AtomicBool = AtomicBool::new(false);
let mut server = Server::builder();
server
.ctx()
.set_max_proto_version(Some(SslVersion::TLS1_2))
.unwrap();
server.ctx().set_options(SslOptions::NO_TICKET);
server
.ctx()
.set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL);
unsafe {
server.ctx().set_get_session_callback(|_, _| {
if !CALLED_SERVER_CALLBACK.swap(true, Ordering::SeqCst) {
return Err(GetSessionPendingError);
}
Ok(None)
});
}
server.ctx().set_session_id_context(b"foo").unwrap();
server.err_cb(|error| {
let HandshakeError::WouldBlock(mid_handshake) = error else {
panic!("should be WouldBlock");
};
assert!(mid_handshake.error().would_block());
assert_eq!(mid_handshake.error().code(), ErrorCode::PENDING_SESSION);
let mut socket = mid_handshake.handshake().unwrap();
socket.write_all(&[0]).unwrap();
});
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
client.connect();
}
#[test]
fn new_session_callback_swapped_ctx() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let mut server = Server::builder();
server.ctx().set_session_id_context(b"foo").unwrap();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_session_cache_mode(SslSessionCacheMode::CLIENT | SslSessionCacheMode::NO_INTERNAL);
client
.ctx()
.set_new_session_callback(|_, _| CALLED_BACK.store(true, Ordering::SeqCst));
let mut client = client.build().builder();
let ctx = SslContextBuilder::new(SslMethod::tls()).unwrap().build();
client.ssl().set_ssl_context(&ctx).unwrap();
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn session_cache_size() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.set_session_cache_size(1234);
let ctx = ctx.build();
assert_eq!(ctx.session_cache_size(), 1234);
}

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

@ -0,0 +1,150 @@
use super::server::Server;
use crate::hash::MessageDigest;
use crate::ssl::SslVerifyMode;
use crate::x509::store::X509StoreBuilder;
use crate::x509::X509;
use hex;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn untrusted() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client();
client.ctx().set_verify(SslVerifyMode::PEER);
client.connect_err();
}
#[test]
fn trusted() {
let server = Server::builder().build();
let client = server.client_with_root_ca();
client.connect();
}
#[test]
fn trusted_with_set_cert() {
let server = Server::builder().build();
let mut store = X509StoreBuilder::new().unwrap();
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
store.add_cert(&x509).unwrap();
let mut client = server.client();
client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_verify_cert_store(store.build()).unwrap();
client.connect();
}
#[test]
fn untrusted_callback_override_ok() {
let server = Server::builder().build();
let mut client = server.client();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, x509| {
assert!(x509.current_cert().is_some());
assert!(x509.verify_result().is_err());
true
});
client.connect();
}
#[test]
fn untrusted_callback_override_bad() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, _| false);
client.connect_err();
}
#[test]
fn trusted_callback_override_ok() {
let server = Server::builder().build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, x509| {
assert!(x509.current_cert().is_some());
assert_eq!(x509.verify_result(), Ok(()));
true
});
client.connect();
}
#[test]
fn trusted_callback_override_bad() {
let mut server = Server::builder();
server.should_error();
let server = server.build();
let mut client = server.client_with_root_ca();
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, |_, _| false);
client.connect_err();
}
#[test]
fn callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
client
.ctx()
.set_verify_callback(SslVerifyMode::PEER, move |_, x509| {
CALLED_BACK.store(true, Ordering::SeqCst);
let cert = x509.current_cert().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), expected);
true
});
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}
#[test]
fn ssl_callback() {
static CALLED_BACK: AtomicBool = AtomicBool::new(false);
let server = Server::builder().build();
let mut client = server.client().build().builder();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
client
.ssl()
.set_verify_callback(SslVerifyMode::PEER, move |_, x509| {
CALLED_BACK.store(true, Ordering::SeqCst);
let cert = x509.current_cert().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), expected);
true
});
client.connect();
assert!(CALLED_BACK.load(Ordering::SeqCst));
}

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,27 +176,30 @@ 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.
#[must_use]
pub fn len(&self) -> usize {
unsafe { OPENSSL_sk_num(self.as_stack()) }
}
/// Determines if the stack is empty.
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> Iter<T> {
#[must_use]
pub fn iter(&self) -> Iter<'_, T> {
Iter {
stack: self,
idxs: 0..self.len(),
}
}
pub fn iter_mut(&mut self) -> IterMut<T> {
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
IterMut {
idxs: 0..self.len(),
stack: self,
@ -205,6 +208,7 @@ impl<T: Stackable> StackRef<T> {
/// Returns a reference to the element at the given index in the
/// stack or `None` if the index is out of bounds
#[must_use]
pub fn get(&self, idx: usize) -> Option<&T::Ref> {
unsafe {
if idx >= self.len() {
@ -230,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(())
}
@ -243,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()
}
}
@ -329,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()))
}
}
@ -343,12 +347,12 @@ 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()))
}
}
}
impl<'a, T: Stackable> ExactSizeIterator for Iter<'a, T> {}
impl<T: Stackable> ExactSizeIterator for Iter<'_, T> {}
/// A mutable iterator over the stack's contents.
pub struct IterMut<'a, T: Stackable + 'a> {
@ -363,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()))
}
}
@ -377,9 +381,9 @@ 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()))
}
}
}
impl<'a, T: Stackable> ExactSizeIterator for IterMut<'a, T> {}
impl<T: Stackable> ExactSizeIterator for IterMut<'_, T> {}

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;
@ -13,6 +13,9 @@ foreign_type_and_impl_send_sync! {
type CType = c_char;
fn drop = free;
/// # Safety
///
/// MUST be UTF-8.
pub struct OpensslString;
}
@ -80,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,13 +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 {
@ -67,22 +69,83 @@ 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 {
/// Looks up the cipher for a certain nid.
///
/// This corresponds to [`EVP_get_cipherbynid`]
///
/// [`EVP_get_cipherbynid`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_get_cipherbyname.html
#[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 {
@ -90,82 +153,102 @@ impl Cipher {
}
}
#[must_use]
pub fn aes_128_ecb() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_128_ecb()) }
}
#[must_use]
pub fn aes_128_cbc() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_128_cbc()) }
}
#[must_use]
pub fn aes_128_ctr() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_128_ctr()) }
}
#[must_use]
pub fn aes_128_gcm() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_128_gcm()) }
}
#[must_use]
pub fn aes_128_ofb() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_128_ofb()) }
}
#[must_use]
pub fn aes_192_ecb() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_192_ecb()) }
}
#[must_use]
pub fn aes_192_cbc() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_192_cbc()) }
}
#[must_use]
pub fn aes_192_ctr() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_192_ctr()) }
}
#[must_use]
pub fn aes_192_gcm() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_192_gcm()) }
}
#[must_use]
pub fn aes_192_ofb() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_192_ofb()) }
}
#[must_use]
pub fn aes_256_ecb() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_256_ecb()) }
}
#[must_use]
pub fn aes_256_cbc() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_256_cbc()) }
}
#[must_use]
pub fn aes_256_ctr() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_256_ctr()) }
}
#[must_use]
pub fn aes_256_gcm() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_256_gcm()) }
}
#[must_use]
pub fn aes_256_ofb() -> Cipher {
unsafe { Cipher(ffi::EVP_aes_256_ofb()) }
}
#[must_use]
pub fn des_cbc() -> Cipher {
unsafe { Cipher(ffi::EVP_des_cbc()) }
}
#[must_use]
pub fn des_ecb() -> Cipher {
unsafe { Cipher(ffi::EVP_des_ecb()) }
}
#[must_use]
pub fn des_ede3() -> Cipher {
unsafe { Cipher(ffi::EVP_des_ede3()) }
}
#[must_use]
pub fn des_ede3_cbc() -> Cipher {
unsafe { Cipher(ffi::EVP_des_ede3_cbc()) }
}
#[must_use]
pub fn rc4() -> Cipher {
unsafe { Cipher(ffi::EVP_rc4()) }
}
@ -175,17 +258,20 @@ impl Cipher {
/// # Safety
///
/// The caller must ensure the pointer is valid for the `'static` lifetime.
#[must_use]
pub unsafe fn from_ptr(ptr: *const ffi::EVP_CIPHER) -> Cipher {
Cipher(ptr)
}
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn as_ptr(&self) -> *const ffi::EVP_CIPHER {
self.0
}
/// Returns the length of keys used with this cipher.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn key_len(&self) -> usize {
unsafe { EVP_CIPHER_key_length(self.0) as usize }
}
@ -193,6 +279,7 @@ impl Cipher {
/// Returns the length of the IV used with this cipher, or `None` if the
/// cipher does not use an IV.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
pub fn iv_len(&self) -> Option<usize> {
unsafe {
let len = EVP_CIPHER_iv_length(self.0) as usize;
@ -210,9 +297,18 @@ impl Cipher {
///
/// Stream ciphers such as RC4 have a block size of 1.
#[allow(clippy::trivially_copy_pass_by_ref)]
#[must_use]
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 {}
@ -323,34 +419,31 @@ impl Crypter {
mode,
))?;
assert!(key.len() <= c_int::max_value() 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_value() 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, None) => ptr::null_mut(),
(Some(_) | None, None) => ptr::null_mut(),
(None, Some(_)) => panic!("an IV is required for this cipher"),
};
cvt(ffi::EVP_CipherInit_ex(
crypter.ctx,
ptr::null(),
ptr::null_mut(),
key,
key.as_ptr().cast_mut(),
iv,
mode,
))?;
@ -365,7 +458,7 @@ impl Crypter {
/// be a multiple of the cipher's block size.
pub fn pad(&mut self, padding: bool) {
unsafe {
ffi::EVP_CIPHER_CTX_set_padding(self.ctx, padding as c_int);
ffi::EVP_CIPHER_CTX_set_padding(self.ctx, c_int::from(padding));
}
}
@ -374,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_value() 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(|_| ())
}
}
@ -392,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_value() 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(|_| ())
}
}
@ -410,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_value() 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(|_| ())
}
}
@ -430,16 +517,14 @@ impl Crypter {
/// `update`.
pub fn aad_update(&mut self, input: &[u8]) -> Result<(), ErrorStack> {
unsafe {
assert!(input.len() <= c_int::max_value() 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(|_| ())
}
}
@ -455,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_value()`.
pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
unsafe {
let block_size = if self.block_size > 1 {
@ -465,16 +548,14 @@ impl Crypter {
0
};
assert!(output.len() >= input.len() + block_size);
assert!(output.len() <= c_int::max_value() 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)
@ -497,7 +578,7 @@ impl Crypter {
if self.block_size > 1 {
assert!(output.len() >= self.block_size);
}
let mut outl = cmp::min(output.len(), c_int::max_value() as usize) as c_int;
let mut outl = cmp::min(output.len(), c_int::MAX as usize) as c_int;
cvt(ffi::EVP_CipherFinal_ex(
self.ctx,
@ -519,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_value() 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(|_| ())
}
}
}
@ -961,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

@ -1,10 +1,10 @@
use crate::error::ErrorStack;
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_char, c_int, c_void};
use std::any::Any;
use std::panic::{self, AssertUnwindSafe};
use std::slice;
use crate::error::ErrorStack;
/// Wraps a user-supplied callback and a slot for panics thrown inside the callback (while FFI
/// frames are on the stack).
///
@ -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) => {
@ -65,3 +65,30 @@ where
}
}
}
#[allow(dead_code)]
pub trait ForeignTypeExt: ForeignType {
unsafe fn from_ptr_opt(ptr: *mut Self::CType) -> Option<Self> {
if ptr.is_null() {
None
} else {
Some(Self::from_ptr(ptr))
}
}
}
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.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.cast_mut()))
}
}
}
impl<FT: ForeignTypeRef> ForeignTypeRefExt for FT {}

View File

@ -42,11 +42,13 @@ use crate::ffi::{
/// Version 0.9.5a had an interim interpretation that is like the current one, except the patch level got the highest bit set, to keep continuity. The number was therefore 0x0090581f
///
/// The return value of this function can be compared to the macro to make sure that the correct version of the library has been loaded, especially when using DLLs on Windows systems.
#[must_use]
pub fn number() -> i64 {
unsafe { OpenSSL_version_num() as i64 }
}
/// The text variant of the version number and the release date. For example, "OpenSSL 0.9.5a 1 Apr 2000".
#[must_use]
pub fn version() -> &'static str {
unsafe {
CStr::from_ptr(OpenSSL_version(OPENSSL_VERSION))
@ -57,6 +59,7 @@ pub fn version() -> &'static str {
/// The compiler flags set for the compilation process in the form "compiler: ..." if available or
/// "compiler: information not available" otherwise.
#[must_use]
pub fn c_flags() -> &'static str {
unsafe {
CStr::from_ptr(OpenSSL_version(OPENSSL_CFLAGS))
@ -66,6 +69,7 @@ pub fn c_flags() -> &'static str {
}
/// The date of the build process in the form "built on: ..." if available or "built on: date not available" otherwise.
#[must_use]
pub fn built_on() -> &'static str {
unsafe {
CStr::from_ptr(OpenSSL_version(OPENSSL_BUILT_ON))
@ -75,6 +79,7 @@ pub fn built_on() -> &'static str {
}
/// The "Configure" target of the library build in the form "platform: ..." if available or "platform: information not available" otherwise.
#[must_use]
pub fn platform() -> &'static str {
unsafe {
CStr::from_ptr(OpenSSL_version(OPENSSL_PLATFORM))
@ -84,6 +89,7 @@ pub fn platform() -> &'static str {
}
/// The "OPENSSLDIR" setting of the library build in the form "OPENSSLDIR: "..."" if available or "OPENSSLDIR: N/A" otherwise.
#[must_use]
pub fn dir() -> &'static str {
unsafe {
CStr::from_ptr(OpenSSL_version(OPENSSL_DIR))

View File

@ -35,7 +35,7 @@
//! store_builder.add_crl(builder.build())?;
//! store_builder
//! .param_mut()
//! .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?;
//! .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
//! Ok(store_builder.build())
//! }
//! ```
@ -367,6 +367,9 @@ impl fmt::Debug for X509CRLRef {
if let Some(revoked) = self.revoked() {
debug_struct.field("revoked", &revoked);
}
if let Some(extensions) = self.extensions() {
debug_struct.field("extensions", &extensions);
}
debug_struct.finish()
}
}

View File

@ -38,6 +38,7 @@ impl Default for BasicConstraints {
impl BasicConstraints {
/// Construct a new `BasicConstraints` extension.
#[must_use]
pub fn new() -> BasicConstraints {
BasicConstraints {
critical: false,
@ -78,7 +79,7 @@ impl BasicConstraints {
value.push_str("FALSE");
}
if let Some(pathlen) = self.pathlen {
write!(value, ",pathlen:{}", pathlen).unwrap();
write!(value, ",pathlen:{pathlen}").unwrap();
}
X509Extension::new_nid(None, None, Nid::BASIC_CONSTRAINTS, &value)
}
@ -106,6 +107,7 @@ impl Default for KeyUsage {
impl KeyUsage {
/// Construct a new `KeyUsage` extension.
#[must_use]
pub fn new() -> KeyUsage {
KeyUsage {
critical: false,
@ -234,6 +236,7 @@ impl Default for ExtendedKeyUsage {
impl ExtendedKeyUsage {
/// Construct a new `ExtendedKeyUsage` extension.
#[must_use]
pub fn new() -> ExtendedKeyUsage {
ExtendedKeyUsage {
critical: false,
@ -329,6 +332,7 @@ impl Default for SubjectKeyIdentifier {
impl SubjectKeyIdentifier {
/// Construct a new `SubjectKeyIdentifier` extension.
#[must_use]
pub fn new() -> SubjectKeyIdentifier {
SubjectKeyIdentifier { critical: false }
}
@ -365,6 +369,7 @@ impl Default for AuthorityKeyIdentifier {
impl AuthorityKeyIdentifier {
/// Construct a new `AuthorityKeyIdentifier` extension.
#[must_use]
pub fn new() -> AuthorityKeyIdentifier {
AuthorityKeyIdentifier {
critical: false,
@ -433,6 +438,7 @@ impl Default for SubjectAlternativeName {
impl SubjectAlternativeName {
/// Construct a new `SubjectAlternativeName` extension.
#[must_use]
pub fn new() -> SubjectAlternativeName {
SubjectAlternativeName {
critical: false,

File diff suppressed because it is too large Load Diff

View File

@ -36,20 +36,20 @@
//!
//! 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();
//! ```
use crate::ffi;
use foreign_types::{ForeignType, ForeignTypeRef};
use std::mem;
use crate::error::ErrorStack;
use crate::ffi;
use crate::stack::StackRef;
use crate::x509::crl::X509CRL;
use crate::x509::verify::X509VerifyParamRef;
use crate::x509::{X509Object, X509};
use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef};
use crate::x509::{X509Object, X509Ref};
use crate::{cvt, cvt_p};
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl_macros::corresponds;
use std::mem::ManuallyDrop;
foreign_type_and_impl_send_sync! {
type CType = ffi::X509_STORE;
@ -72,18 +72,18 @@ 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
pub fn add_cert(&mut self, cert: X509) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) }
#[corresponds(X509_STORE_add_cert)]
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.
@ -99,7 +99,7 @@ impl X509StoreBuilderRef {
///
/// This corresponds to [`X509_STORE_get0_param`].
///
/// [`X509_STORE_get0_param`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_get0_param
/// [`X509_STORE_add_crl`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_get0_param
pub fn param_mut(&mut self) -> &mut X509VerifyParamRef {
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::X509_STORE_get0_param(self.as_ptr())) }
}
@ -109,8 +109,37 @@ impl X509StoreBuilderRef {
/// These locations are read from the `SSL_CERT_FILE` and `SSL_CERT_DIR`
/// environment variables if present, or defaults specified at OpenSSL
/// 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.
#[corresponds(X509_STORE_set_flags)]
pub fn set_flags(&mut self, flags: X509VerifyFlags) {
unsafe {
cvt(ffi::X509_STORE_set_flags(self.as_ptr(), flags.bits())).unwrap();
}
}
/// Returns a mutable reference to the X509 verification configuration.
#[corresponds(X509_STORE_get0_param)]
pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef {
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::X509_STORE_get0_param(self.as_ptr())) }
}
/// 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())) }
}
/// For testing only
#[cfg(test)]
pub fn objects_len(&self) -> usize {
unsafe {
StackRef::<X509Object>::from_ptr(ffi::X509_STORE_get0_objects(self.as_ptr())).len()
}
}
}
@ -122,11 +151,59 @@ foreign_type_and_impl_send_sync! {
pub struct X509Store;
}
impl X509StoreRef {
/// Get a reference to the cache of certificates in this store.
pub fn objects(&self) -> &StackRef<X509Object> {
unsafe { StackRef::from_ptr(X509_STORE_get0_objects(self.as_ptr())) }
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())
}
}
}
use crate::ffi::X509_STORE_get0_objects;
impl Clone for X509Store {
fn clone(&self) -> X509Store {
(**self).to_owned()
}
}
impl X509StoreRef {
/// **Warning: this method is unsound**
///
/// Get a reference to the cache of certificates in this store.
///
/// # Safety
/// References may be invalidated by any access to the shared cache.
#[deprecated(
note = "This method is unsound https://github.com/sfackler/rust-openssl/issues/2096"
)]
#[corresponds(X509_STORE_get0_objects)]
#[must_use]
pub fn objects(&self) -> &StackRef<X509Object> {
unsafe { StackRef::from_ptr(ffi::X509_STORE_get0_objects(self.as_ptr())) }
}
/// For testing only, where it doesn't have to expose an unsafe pointer
#[cfg(test)]
#[allow(deprecated)]
#[must_use]
pub fn objects_len(&self) -> usize {
self.objects().len()
}
}
#[test]
#[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

@ -14,7 +14,9 @@ use crate::x509::extension::{
};
use crate::x509::store::X509StoreBuilder;
use crate::x509::verify::X509VerifyFlags;
use crate::x509::{X509Extension, X509Name, X509Req, X509StoreContext, X509};
use crate::x509::{X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyError, X509};
mod trusted_first;
fn pkey() -> PKey<Private> {
let rsa = Rsa::generate(2048).unwrap();
@ -32,7 +34,7 @@ where
#[test]
fn test_cert_loading() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let fingerprint = cert.digest(MessageDigest::sha1()).unwrap();
@ -44,9 +46,9 @@ fn test_cert_loading() {
#[test]
fn test_debug() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let debugged = format!("{:#?}", cert);
let debugged = format!("{cert:#?}");
assert!(debugged.contains(r#"serial_number: "8771f7bdee982fa5""#));
assert!(debugged.contains(r#"signature_algorithm: sha256WithRSAEncryption"#));
@ -58,7 +60,7 @@ fn test_debug() {
#[test]
fn test_cert_issue_validity() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let not_before = cert.not_before().to_string();
let not_after = cert.not_after().to_string();
@ -69,7 +71,7 @@ fn test_cert_issue_validity() {
#[test]
fn test_save_der() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let der = cert.to_der().unwrap();
@ -78,16 +80,16 @@ fn test_save_der() {
#[test]
fn test_subject_read_cn() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
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]
fn test_nid_values() {
let cert = include_bytes!("../../test/nid_test_cert.pem");
let cert = include_bytes!("../../../test/nid_test_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let subject = cert.subject_name();
@ -106,7 +108,7 @@ fn test_nid_values() {
#[test]
fn test_nameref_iterator() {
let cert = include_bytes!("../../test/nid_test_cert.pem");
let cert = include_bytes!("../../../test/nid_test_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let subject = cert.subject_name();
let mut all_entries = subject.entries();
@ -133,7 +135,7 @@ fn test_nameref_iterator() {
#[test]
fn test_nid_uid_value() {
let cert = include_bytes!("../../test/nid_uid_test_cert.pem");
let cert = include_bytes!("../../../test/nid_uid_test_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let subject = cert.subject_name();
@ -143,7 +145,7 @@ fn test_nid_uid_value() {
#[test]
fn test_subject_alt_name() {
let cert = include_bytes!("../../test/alt_name_cert.pem");
let cert = include_bytes!("../../../test/alt_name_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let subject_alt_names = cert.subject_alt_names().unwrap();
@ -160,7 +162,7 @@ fn test_subject_alt_name() {
#[test]
fn test_subject_alt_name_iter() {
let cert = include_bytes!("../../test/alt_name_cert.pem");
let cert = include_bytes!("../../../test/alt_name_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let subject_alt_names = cert.subject_alt_names().unwrap();
@ -188,6 +190,49 @@ fn test_subject_alt_name_iter() {
assert!(subject_alt_names_iter.next().is_none());
}
#[test]
fn test_subject_key_id() {
// nid_test_cert_pem has SKI, but no AKI
let cert = include_bytes!("../../../test/nid_test_cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ski = cert.subject_key_id().expect("unable to extract SKI");
assert_eq!(
ski.as_slice(),
[
80, 107, 158, 237, 95, 61, 235, 100, 212, 115, 249, 244, 219, 163, 124, 55, 141, 2, 76,
5
]
);
let aki = cert.authority_key_id();
assert!(aki.is_none());
}
#[test]
fn test_x509_name_print_ex() {
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let name_no_flags = cert
.subject_name()
.print_ex(0)
.expect("failed to print cert subject name");
assert_eq!(
name_no_flags,
"C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=foobar.com"
);
let name_rfc2253 = cert
.subject_name()
.print_ex(ffi::XN_FLAG_RFC2253)
.expect("failed to print cert subject name");
assert_eq!(
name_rfc2253,
"CN=foobar.com,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU"
);
}
#[test]
fn x509_builder() {
let pkey = pkey();
@ -216,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();
@ -251,6 +298,7 @@ fn x509_builder() {
assert!(pkey.public_eq(&x509.public_key().unwrap()));
assert!(x509.verify(&pkey).unwrap());
assert_eq!(x509.extensions().unwrap().len(), 6);
let cn = x509
.subject_name()
@ -395,7 +443,7 @@ fn x509_req_builder() {
#[test]
fn test_stack_from_pem() {
let certs = include_bytes!("../../test/certs.pem");
let certs = include_bytes!("../../../test/certs.pem");
let certs = X509::stack_from_pem(certs).unwrap();
assert_eq!(certs.len(), 2);
@ -411,9 +459,9 @@ fn test_stack_from_pem() {
#[test]
fn issued() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
assert_eq!(ca.issued(&cert), Ok(()));
@ -422,7 +470,7 @@ fn issued() {
#[test]
fn signature() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let signature = cert.signature();
assert_eq!(
@ -443,42 +491,54 @@ fn signature() {
#[test]
#[allow(clippy::redundant_clone)]
fn clone_x509() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
drop(cert.clone());
}
#[test]
fn test_verify_cert() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
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();
let mut context = X509StoreContext::new().unwrap();
assert!(context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
assert!(!context
.init(&empty_store, &cert, &chain, |c| c.verify_cert())
.unwrap());
assert!(context
.init(&store, &cert, &chain, |c| c.verify_cert())
.unwrap());
context
.reset_with_context_data(empty_store, cert.clone(), Stack::new().unwrap())
.unwrap();
assert!(!context.verify_cert().unwrap());
context.reset_with_context_data(store, cert, chain).unwrap();
assert!(context.verify_cert().unwrap());
}
#[test]
fn test_verify_fails() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/alt_name_cert.pem");
let ca = include_bytes!("../../../test/alt_name_cert.pem");
let ca = X509::from_pem(ca).unwrap();
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();
@ -489,21 +549,20 @@ fn test_verify_fails() {
#[test]
fn test_verify_revoked() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../test/crl.pem");
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
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()
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)
.unwrap();
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
let store = store_bldr.build();
let mut context = X509StoreContext::new().unwrap();
@ -514,37 +573,36 @@ fn test_verify_revoked() {
#[test]
fn test_crl_signature() {
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../test/bad_sig.pem");
let crl = include_bytes!("../../../test/bad_sig.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert!(!crl.verify(&ca.public_key().unwrap()).unwrap());
let crl = include_bytes!("../../test/crl.pem");
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert!(crl.verify(&ca.public_key().unwrap()).unwrap());
}
#[test]
fn test_untrusted_valid_crl() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
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)
.unwrap();
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
// cert is not revoked
let crl = include_bytes!("../../test/empty_crl.pem");
let crl = include_bytes!("../../../test/empty_crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let mut context = X509StoreContext::new().unwrap();
assert!(context
@ -553,70 +611,69 @@ fn test_untrusted_valid_crl() {
.unwrap());
// cert is revoked
let crl = include_bytes!("../../test/crl.pem");
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().unwrap_err().as_raw(),
ffi::X509_V_ERR_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]
fn test_untrusted_invalid_crl() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
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)
.unwrap();
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
let store = store_bldr.build();
// this CRL was issued by a different CA (not in the trusted store)
let crl = include_bytes!("../../test/invalid_crl.pem");
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().unwrap_err().as_raw(),
ffi::X509_V_ERR_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 = 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().unwrap_err().as_raw(),
ffi::X509_V_ERR_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]
fn test_revoked_serial_numbers() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let cert_sn = cert.serial_number().to_bn().unwrap();
let crl = include_bytes!("../../test/crl.pem");
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
assert_eq!(
@ -631,7 +688,7 @@ fn test_revoked_serial_numbers() {
#[test]
fn test_serialize_crl() {
let crl = include_bytes!("../../test/crl.pem");
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let digest = hex::encode(crl.digest(MessageDigest::sha1()).unwrap());
@ -641,9 +698,32 @@ fn test_serialize_crl() {
assert_eq!(digest, hex::encode(new_digest));
}
#[test]
fn test_crl_extensions() {
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let extensions = crl.extensions();
assert_eq!(
extensions
.unwrap()
.iter()
.filter(|ext| ext.object().nid() == Nid::AUTHORITY_KEY_IDENTIFIER)
.count(),
1
);
assert_eq!(
extensions
.unwrap()
.iter()
.filter(|ext| ext.object().nid() == Nid::CRL_NUMBER)
.count(),
1
);
}
#[test]
fn test_debug_crl() {
let crl = include_bytes!("../../test/crl.pem");
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
let debugged = format!("{:#?}", crl);
assert!(debugged.contains(r#"countryName = "AU""#));
@ -653,18 +733,20 @@ fn test_debug_crl() {
assert!(debugged.contains(r#"next_update: Jun 18 20:22:02 2032 GMT"#));
assert!(debugged.contains(r#"revocation_date: Jun 21 20:21:55 2022 GMT"#));
assert!(debugged.contains(r#"serial_number: "8771f7bdee982fa5""#));
assert!(debugged.contains(r#"object_nid: X509v3 Authority Key Identifier"#));
assert!(debugged.contains(r#"object_nid: X509v3 CRL Number"#));
}
#[test]
fn test_custom_time_valid() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
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();
@ -676,14 +758,14 @@ fn test_custom_time_valid() {
#[test]
fn test_custom_time_expired() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
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();
@ -695,14 +777,14 @@ fn test_custom_time_expired() {
#[test]
fn test_custom_time_too_soon() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
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();
@ -714,20 +796,19 @@ fn test_custom_time_too_soon() {
#[test]
fn test_custom_time_crl() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let ca = include_bytes!("../../test/root-ca.pem");
let ca = include_bytes!("../../../test/root-ca.pem");
let ca = X509::from_pem(ca).unwrap();
let crl = include_bytes!("../../test/crl.pem");
let crl = include_bytes!("../../../test/crl.pem");
let crl = X509CRL::from_pem(crl).unwrap();
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)
.unwrap();
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
store_bldr.param_mut().set_time(1640995200); // 2022-01-01, before the CRL's issue date
let store = store_bldr.build();
@ -740,17 +821,17 @@ fn test_custom_time_crl() {
#[test]
fn test_save_subject_der() {
let cert = include_bytes!("../../test/cert.pem");
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let der = cert.subject_name().to_der().unwrap();
println!("der: {:?}", der);
println!("der: {der:?}");
assert!(!der.is_empty());
}
#[test]
fn test_load_subject_der() {
// The subject from ../../test/cert.pem
// The subject from ../../../test/cert.pem
const SUBJECT_DER: &[u8] = &[
48, 90, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 65, 85, 49, 19, 48, 17, 6, 3, 85, 4, 8, 12,
10, 83, 111, 109, 101, 45, 83, 116, 97, 116, 101, 49, 33, 48, 31, 6, 3, 85, 4, 10, 12, 24,
@ -760,3 +841,16 @@ fn test_load_subject_der() {
];
X509Name::from_der(SUBJECT_DER).unwrap();
}
#[test]
fn test_check_ip_asc() {
// Covers 127.0.0.1 and 0:0:0:0:0:0:0:1
let cert = include_bytes!("../../../test/alt_name_cert.pem");
let cert = X509::from_pem(cert).unwrap();
assert!(cert.check_ip_asc("127.0.0.1").unwrap());
assert!(!cert.check_ip_asc("127.0.0.2").unwrap());
assert!(cert.check_ip_asc("0:0:0:0:0:0:0:1").unwrap());
assert!(!cert.check_ip_asc("0:0:0:0:0:0:0:2").unwrap());
}

View File

@ -0,0 +1,104 @@
//! See https://github.com/google/boringssl/blob/cc696073cffe7978d489297fbdeac4c0030384aa/crypto/x509/x509_test.cc#L3977-L3980
use crate::stack::Stack;
use crate::x509::store::X509StoreBuilder;
use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef};
use crate::x509::{X509Ref, X509StoreContext, X509VerifyError, X509VerifyResult, X509};
#[test]
fn test_verify_cert() {
let root2 = X509::from_pem(include_bytes!("../../../test/root-ca-2.pem")).unwrap();
let root1 = X509::from_pem(include_bytes!("../../../test/root-ca.pem")).unwrap();
let root1_cross = X509::from_pem(include_bytes!("../../../test/root-ca-cross.pem")).unwrap();
let intermediate = X509::from_pem(include_bytes!("../../../test/intermediate-ca.pem")).unwrap();
let leaf = X509::from_pem(include_bytes!("../../../test/cert-with-intermediate.pem")).unwrap();
assert_eq!(Ok(()), verify(&leaf, &[&root1], &[&intermediate], |_| {}));
#[cfg(not(feature = "legacy-compat-deprecated"))]
assert_eq!(
Ok(()),
verify(
&leaf,
&[&root1, &root2],
&[&intermediate, &root1_cross],
|_| {}
)
);
#[cfg(feature = "legacy-compat-deprecated")]
assert_eq!(
Err(X509VerifyError::CERT_HAS_EXPIRED),
verify(
&leaf,
&[&root1, &root2],
&[&intermediate, &root1_cross],
|_| {}
)
);
assert_eq!(
Ok(()),
verify(
&leaf,
&[&root1, &root2],
&[&intermediate, &root1_cross],
|param| param.set_flags(X509VerifyFlags::TRUSTED_FIRST),
)
);
assert_eq!(
Err(X509VerifyError::CERT_HAS_EXPIRED),
verify(
&leaf,
&[&root1, &root2],
&[&intermediate, &root1_cross],
|param| param.clear_flags(X509VerifyFlags::TRUSTED_FIRST),
)
);
assert_eq!(
Ok(()),
verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| {
param.clear_flags(X509VerifyFlags::TRUSTED_FIRST);
})
);
}
fn verify(
cert: &X509Ref,
trusted: &[&X509Ref],
untrusted: &[&X509Ref],
configure: impl FnOnce(&mut X509VerifyParamRef),
) -> X509VerifyResult {
let trusted = {
let mut builder = X509StoreBuilder::new().unwrap();
for cert in trusted {
builder.add_cert(cert).unwrap();
}
builder.build()
};
let untrusted = {
let mut stack = Stack::new().unwrap();
for cert in untrusted {
stack.push((**cert).to_owned()).unwrap();
}
stack
};
let mut store_ctx = X509StoreContext::new().unwrap();
store_ctx
.init(&trusted, cert, &untrusted, |ctx| {
configure(ctx.verify_param_mut());
ctx.verify_cert().unwrap();
Ok(ctx.verify_result())
})
.expect("failed to obtain X509VerifyResult")
}

View File

@ -1,14 +1,16 @@
use crate::ffi;
use foreign_types::ForeignTypeRef;
use libc::{c_uint, time_t};
use foreign_types::{ForeignType, ForeignTypeRef};
use libc::{c_int, c_uint, c_ulong, time_t};
use openssl_macros::corresponds;
use std::net::IpAddr;
use crate::cvt;
use crate::error::ErrorStack;
use crate::{cvt, cvt_p};
bitflags! {
/// Flags used to check an `X509` certificate.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct X509CheckFlags: c_uint {
const ALWAYS_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT as _;
const NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _;
@ -16,15 +18,17 @@ bitflags! {
const MULTI_LABEL_WILDCARDS = ffi::X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS as _;
const SINGLE_LABEL_SUBDOMAINS = ffi::X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS as _;
const NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as _;
#[deprecated(since = "0.10.6", note = "renamed to NO_WILDCARDS")]
const FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _;
#[cfg(feature = "underscore-wildcards")]
const UNDERSCORE_WILDCARDS = ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS as _;
}
}
bitflags! {
/// Flags used to configure verification of an `X509` certificate
pub struct X509VerifyFlags: c_uint {
/// 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 _;
const CRL_CHECK = ffi::X509_V_FLAG_CRL_CHECK as _;
@ -38,7 +42,7 @@ bitflags! {
const INHIBIT_MAP = ffi::X509_V_FLAG_INHIBIT_MAP as _;
const NOTIFY_POLICY = ffi::X509_V_FLAG_NOTIFY_POLICY as _;
const EXTENDED_CRL_SUPPORT = ffi::X509_V_FLAG_EXTENDED_CRL_SUPPORT as _;
const FLAG_USE_DELTAS = ffi::X509_V_FLAG_USE_DELTAS as _;
const USE_DELTAS = ffi::X509_V_FLAG_USE_DELTAS as _;
const CHECK_SS_SIGNATURE = ffi::X509_V_FLAG_CHECK_SS_SIGNATURE as _;
const TRUSTED_FIRST = ffi::X509_V_FLAG_TRUSTED_FIRST as _;
const PARTIAL_CHAIN = ffi::X509_V_FLAG_PARTIAL_CHAIN as _;
@ -54,39 +58,90 @@ foreign_type_and_impl_send_sync! {
pub struct X509VerifyParam;
}
impl X509VerifyParam {
/// Create an X509VerifyParam
#[corresponds(X509_VERIFY_PARAM_new)]
pub fn new() -> Result<Self, ErrorStack> {
unsafe {
ffi::init();
let handle = cvt_p(ffi::X509_VERIFY_PARAM_new())?;
Ok(Self::from_ptr(handle))
}
}
}
impl X509VerifyParamRef {
/// Set verification flags.
#[corresponds(X509_VERIFY_PARAM_set_flags)]
pub fn set_flags(&mut self, flags: X509VerifyFlags) {
unsafe {
cvt(ffi::X509_VERIFY_PARAM_set_flags(
self.as_ptr(),
flags.bits(),
))
.unwrap();
}
}
/// Clear verification flags.
#[corresponds(X509_VERIFY_PARAM_clear_flags)]
pub fn clear_flags(&mut self, flags: X509VerifyFlags) {
unsafe {
cvt(ffi::X509_VERIFY_PARAM_clear_flags(
self.as_ptr(),
flags.bits(),
))
.unwrap();
}
}
///
/// Set the host flags.
///
/// This corresponds to [`X509_VERIFY_PARAM_set_hostflags`].
///
/// [`X509_VERIFY_PARAM_set_hostflags`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_VERIFY_PARAM_set_hostflags.html
#[corresponds(X509_VERIFY_PARAM_set_hostflags)]
pub fn set_hostflags(&mut self, hostflags: X509CheckFlags) {
unsafe {
ffi::X509_VERIFY_PARAM_set_hostflags(self.as_ptr(), hostflags.bits());
}
}
/// Gets verification flags.
#[corresponds(X509_VERIFY_PARAM_get_flags)]
#[must_use]
pub fn flags(&self) -> X509VerifyFlags {
let bits = unsafe { ffi::X509_VERIFY_PARAM_get_flags(self.as_ptr()) };
X509VerifyFlags::from_bits_retain(bits)
}
/// Set the expected DNS hostname.
///
/// This corresponds to [`X509_VERIFY_PARAM_set1_host`].
///
/// [`X509_VERIFY_PARAM_set1_host`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_VERIFY_PARAM_set1_host.html
#[corresponds(X509_VERIFY_PARAM_set1_host)]
pub fn set_host(&mut self, host: &str) -> Result<(), ErrorStack> {
unsafe {
// len == 0 means "run strlen" :(
let raw_host = if host.is_empty() { "\0" } else { host };
cvt(ffi::X509_VERIFY_PARAM_set1_host(
self.as_ptr(),
host.as_ptr() as *const _,
raw_host.as_ptr().cast(),
host.len(),
))
.map(|_| ())
}
}
/// Set the expected email address.
#[corresponds(X509_VERIFY_PARAM_set1_email)]
pub fn set_email(&mut self, email: &str) -> Result<(), ErrorStack> {
unsafe {
// len == 0 means "run strlen" :(
let raw_email = if email.is_empty() { "\0" } else { email };
cvt(ffi::X509_VERIFY_PARAM_set1_email(
self.as_ptr(),
raw_email.as_ptr().cast(),
email.len(),
))
}
}
/// Set the expected IPv4 or IPv6 address.
///
/// This corresponds to [`X509_VERIFY_PARAM_set1_ip`].
///
/// [`X509_VERIFY_PARAM_set1_ip`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_VERIFY_PARAM_set1_ip.html
#[corresponds(X509_VERIFY_PARAM_set1_ip)]
pub fn set_ip(&mut self, ip: IpAddr) -> Result<(), ErrorStack> {
unsafe {
let mut buf = [0; 16];
@ -102,68 +157,29 @@ impl X509VerifyParamRef {
};
cvt(ffi::X509_VERIFY_PARAM_set1_ip(
self.as_ptr(),
buf.as_ptr() as *const _,
buf.as_ptr().cast(),
len,
))
.map(|_| ())
}
}
/// Sets the time to check certificates and CRLs against.
///
/// If unset, uses the current time.
///
/// This corresponds to [`X509_VERIFY_PARAM_set_time`]. Note that BoringSSL does not support
/// the OpenSSL `NO_CHECK_TIME` option.
///
/// [`X509_VERIFY_PARAM_set_time`]: https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_time.html
///
/// # Example
///
/// ```
/// use boring::x509::store::X509StoreBuilder;
/// use std::convert::TryInto;
/// use std::time::SystemTime;
///
/// let mut store_builder = X509StoreBuilder::new().expect("can create a new builder");
/// let duration_since_epoch = SystemTime::UNIX_EPOCH.elapsed().expect("it's after 1970");
/// let seconds_since_epoch: libc::time_t = duration_since_epoch
/// .as_secs()
/// .try_into()
/// .expect("time_t is large enough to represent this time");
/// store_builder.param_mut().set_time(seconds_since_epoch);
/// ```
pub fn set_time(&mut self, unix_time: time_t) {
unsafe { ffi::X509_VERIFY_PARAM_set_time(self.as_ptr(), unix_time) }
/// Set the verification time, where time is of type time_t, traditionaly defined as seconds since the epoch
#[corresponds(X509_VERIFY_PARAM_set_time)]
pub fn set_time(&mut self, time: time_t) {
unsafe { ffi::X509_VERIFY_PARAM_set_time(self.as_ptr(), time) }
}
/// Set the verify flags by OR-ing them with `flags`
///
/// This corresponds to [`X509_VERIFY_PARAM_set_flags`].
///
/// [`X509_VERIFY_PARAM_set_flags`]: https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_flags.html
pub fn set_flags(&mut self, flags: X509VerifyFlags) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_VERIFY_PARAM_set_flags(
self.as_ptr(),
flags.bits().into(),
))
.map(|_| ())
}
/// Set the verification depth
#[corresponds(X509_VERIFY_PARAM_set_depth)]
pub fn set_depth(&mut self, depth: c_int) {
unsafe { ffi::X509_VERIFY_PARAM_set_depth(self.as_ptr(), depth) }
}
/// Clears the verify flags in `flags`
/// Copies parameters from `src`.
///
/// This corresponds to [`X509_VERIFY_PARAM_clear_flags`]
///
/// [`X509_VERIFY_PARAM_clear_flags`]: https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_clear_flags.html
pub fn clear_flags(&mut self, flags: X509VerifyFlags) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::X509_VERIFY_PARAM_clear_flags(
self.as_ptr(),
flags.bits().into(),
))
.map(|_| ())
}
/// 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())) }
}
}

View File

@ -0,0 +1,20 @@
notAfter=Aug 12 11:30:03 2026 GMT
-----BEGIN CERTIFICATE-----
MIIDKDCCAhACFGwwuilXOHjBjQ584FD9drp9Uh/LMA0GCSqGSIb3DQEBCwUAMEUx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMxMjE4MTEzMDAzWhcNMjYwODEyMTEz
MDAzWjBcMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
BwwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRUwEwYDVQQDDAwqLmZvb2Jhci5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo9CWMRLMXo1CF/iOR
h9B4NhtJF/8tR9PlG95sNvyWuQQ/8jfev+8zErplxfLkt0pJqcoiZG8g9NU0kU6o
5T+/1QgZclCAoZaS0Jqxmoo2Yk/1Qsj16pnMBc10uSDk6V9aJSX1vKwONVNSwiHA
1MhX+i7Wf7/K0niq+k7hOkhleFkWgZtUq41gXh1VfOugka7UktYnk9mrBbAMjmal
oZNn2pMMAQxVg4ThiLm3zvuWqvXASWzUZc7IAd1GbN4AtDuhs252eqE9E4iTHk7F
14wAS1JWqv666hReGHrmZJGx0xQTM9vPD1HN5t2U3KTfhO/mTlAUWVyg9tCtOzbo
Kgs1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHG83qKMl5bPoL2s7TaJZ909NaQO
4C69ueXlD4HJEFe7L9mkeQoDaF7RwWSBwN2RZT5hzQhghRotqLA06XwKbQHji/R7
sYYVUHunobFUHsr51tFN1BIDoAWJa0N2rm/OxbcK471eWNKjMiS2vvvPdaMxxHAx
IsjAJBJec4IxNIUNNKqCS/xNYcdiyrmmU3oFWGqb0As/eDOBw0Amd0aayasFJrRV
3KZI5OcFg/J3XvdaxMJD+RPyUysKRXg6K8jzYc/PB8LhWVXpLxjEzeO2IHCaZprh
dUTP8+Ob+ioxujvlslxc4nrrUD5EWwnpEIr7e4af27JHQVaNyHbRw6wI2uk=
-----END CERTIFICATE-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDMzCCAhsCFBEiNxpuknaO7Pw1Yi88UW4aiGo0MA0GCSqGSIb3DQEBCwUAMFIx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMS4wLAYDVQQKDCVJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQgSW50ZXJtZWRpYXRlMB4XDTI0MDEwMzE0MjIz
MFoXDTI2MDgxMjE0MjIzMFowWjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUt
U3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UE
AwwKZm9vYmFyLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKj0
JYxEsxejUIX+I5GH0Hg2G0kX/y1H0+Ub3mw2/Ja5BD/yN96/7zMSumXF8uS3Skmp
yiJkbyD01TSRTqjlP7/VCBlyUIChlpLQmrGaijZiT/VCyPXqmcwFzXS5IOTpX1ol
JfW8rA41U1LCIcDUyFf6LtZ/v8rSeKr6TuE6SGV4WRaBm1SrjWBeHVV866CRrtSS
1ieT2asFsAyOZqWhk2fakwwBDFWDhOGIubfO+5aq9cBJbNRlzsgB3UZs3gC0O6Gz
bnZ6oT0TiJMeTsXXjABLUlaq/rrqFF4YeuZkkbHTFBMz288PUc3m3ZTcpN+E7+ZO
UBRZXKD20K07NugqCzUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEANWlOvyLEHdPV
8rMdfqLTZZyA79L1N3bP1FWS97fF36Y9EnTKChenwkBob1abY4jQ2/LICKND+ux8
xDlmMlYRH4aM5bXAjOcdpmq9R9SuzsK/2m79xONF//AX4zb0s5b+QEwdYkfJ5jiO
xMrnatwHQhFvQIQvuTo2o0WZEnkubNYDxVh7UOv9cOQjwm0+58CIEG5SHR9grG5u
TTswu7DswgpfSCKKPaFCF4pWxLfryYwadO0/4Ot/ZbElbAdJYC8CI1QC14knk2cD
0ZG9jaVPP9wCAt/ZIu8NbsZN7DNbISaXVfMju+xSdey8B3FLRkLq9TKmnLum5LR2
TyM6hDIh8A==
-----END CERTIFICATE-----

BIN
boring/test/echconfig Normal file

Binary file not shown.

BIN
boring/test/echconfig-2 Normal file

Binary file not shown.

BIN
boring/test/echconfiglist Normal file

Binary file not shown.

BIN
boring/test/echconfiglist-2 Normal file

Binary file not shown.

BIN
boring/test/echkey Normal file

Binary file not shown.

1
boring/test/echkey-2 Normal file
View File

@ -0,0 +1 @@
§5D$lþ°SLb~Ê.<11>V<À.j¢ç¯:}´rˆ¶…

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApyZi0joAIH8M7GaitH27lQs1XZaXwv5PI9bIeSkIX4BafIcU
I7hdPLreJoP6EsuoswXyhgZU8FQvNRjMAGPzvVviG+vY/l6jFI+JqW7Pphr35QJO
jqxwW/K/wYei0FS6C3SF+CJ2yIgG4YlJM9Hoq/EpfIxmyuZ2yHD/JiQ5kxNP7vKT
77f7k5zpCVG1kiQZKQT4iysQDz2e1ozi3W7NCDqnvM9+EX1DREiCqRUwuzIuTlkA
Z+8HpET5wS2Pm5uJQXDCid0slYMcuOL6JoHekbs69wkt4powcPPhXWoTCyUjpqsx
w0KPiBzViJUsd8JUq8ge+jVqP2pzvkQk4BWTMwIDAQABAoIBAFBiEHIjPH5kOzXQ
4fxE3xn9KuvYCSHYJP0KRJyn1AQBeQKb/15yQjx7bWw+WdwCHx4BBTHZB64P/ifd
xfWGG+h7sJBW6qLhpjG0GbLmvGuYWpDCfD72xI4jfn42mWDw7gumPOsov9EOQaji
2dZW4zsVHitsZd672HHqjXmtQBbvEcm/cuPoL2/s9crKwHbF90kb9BOYcz+/U138
zPfXFM5AbivpiQHA7CmMrQ833op42fQCfrxGs1XkZPO30EPpCtmHE6+Xhv+GGWp/
EF7D6pwkT4OElgzAP6cthPqZOtQLE2LCM/mTy/cFRtrBjKPlZ/UBrxjaLI1FHoAk
TYX81GECgYEA451EWFCrX5vWEMJE7zCALiQbxxOhQCIEufvkxUvDnsqjTPj/ylpZ
usu+YC5BcgwVxjso8dEL8pcd8pAFVLeX8wBDeGfTdhUmPPZmx8ViFbZTVq/Tz+Jw
tHo0foq8gfTLJrvdzuyFEHR6z5O16EtKOa+G1gIF/atV8hO/oRXLXvECgYEAu/7B
SpzbBJoxame27uaakYYVKuAD2wVAmfP9XuTV6IvE845sgmzTYURxSnO0RZCuNj5x
7u//HdGoFuA3o4Un4XLx1qot2op9ql1xuD/V3aqrvhpzvoCklks6t+PW3/Sef6TH
21TOCahpoQqD/UrSrHEJXC7lmkfcPCir3QflnGMCgYBV2AFnwXzwwShaB7rR7xvY
yxuC2H9vXaUks8DTPEDaCZjPNfXaznqa/a6ePbPHHJG1wqgtk2cLJj1QN0sbaWaw
akAIEDhrh4x1X4TiASp9/9askgGznLZfCtvzgcWYycc4o5ADM6b3zsZmtVHc+1BS
M0YKPpcd1dnDQ/l4+mxKMQKBgQCenCB+j/plVqaMjLaVty//yW2AgAIgvryzZ1yE
vHMRQSNJDgfUvnZVIUaoNxiIfLnPAD5mBkxq3yF/M2sd5lEwcCdEIs6PDLtbin1Q
o2MQI1fFC1JODwFN4GjJD0ySJTO4o9EO5uzyzwlXmqSjhoZagQARq2uCEFDq3LGr
yWba2wKBgDT+5qjFweI5RmE1pDvUH9gOPNflyOpyjnbueunmTn1rzcRhx9xgM3QB
ehWCRR1Y/vAnu5uupf0rG/Y/gtvIVyC3F+0csNox7T9e0t4sdgORYOVWbvsIF2t9
2HYjY782ws3EBF5yNKJDgV2sNjA0Wpb6lahkxRut314jnwGplR2w
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgIUE+pcEHU5e4wkMpp2qoyHy+wH8aMwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMDMxMDM0MDdaFw0yNjA4
MTIxMDM0MDdaMFIxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMS4w
LAYDVQQKDCVJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQgSW50ZXJtZWRpYXRlMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApyZi0joAIH8M7GaitH27lQs1
XZaXwv5PI9bIeSkIX4BafIcUI7hdPLreJoP6EsuoswXyhgZU8FQvNRjMAGPzvVvi
G+vY/l6jFI+JqW7Pphr35QJOjqxwW/K/wYei0FS6C3SF+CJ2yIgG4YlJM9Hoq/Ep
fIxmyuZ2yHD/JiQ5kxNP7vKT77f7k5zpCVG1kiQZKQT4iysQDz2e1ozi3W7NCDqn
vM9+EX1DREiCqRUwuzIuTlkAZ+8HpET5wS2Pm5uJQXDCid0slYMcuOL6JoHekbs6
9wkt4powcPPhXWoTCyUjpqsxw0KPiBzViJUsd8JUq8ge+jVqP2pzvkQk4BWTMwID
AQABo1AwTjAdBgNVHQ4EFgQUZ3ELefSmoenETTa39CQwVzdoLZcwHwYDVR0jBBgw
FoAUbNOlA6sNXyzJjYqciKeId7g3/ZowDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
AQsFAAOCAQEAHCXvCU1XQM5a7hhgrMcKQRto9GEVIljnPv5H7x+wPvCfR1By/kzI
fsl+hA1q02ymLtOW16aq4si4exsQl4SktC+5hyhu0yOCevRYXCcrh5NrNbwTFnK/
LIP4dRz4XBxC9pYg0rqvo+v64at6EBTXxYfBHo9Cj0QuZIJoYmGEOojdE0PudZdc
b1iuXk9FZlUueFq8uSkHD7EpxonPS9iWQA5dbw1Q+0hWqvA/npAvFXBHkF/Jyaht
fpqoUrr4LYUr/ShC1IVHG7TAEElnOGz0dY6uxkr1B6YxBvCJ6gesk2cJBC1i3g9I
9xvt4zxQNQynX0IHcar8xfgZqD4ZdWQY0w==
-----END CERTIFICATE-----

27
boring/test/root-ca-2.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwSgPW00nGOTje0FHm1HXT3ANHjW3HGPbxiMSRvSoBEdv8R9e
0PoKjse0xQsa5nKolbUhJBS6dkH+13VPOs6e+vZyXD6OYQfi2e42Mw0SPFl5g2qU
fy/17q3ipqPJnT6YHhGpx/P91F7yjDAF2MvNRGQ/6AYWD4MUr6+5vPHEYhQ/JlAK
cN/uWUM+bg2mwMNpmuGRV1tg0hKl9nT1fX1i6OSszPLIY1dC0eHv2gRh1unrXNoY
A76xohiwcJYAAPjrx167S8td8gmWnOwYnCnGIw2ceNpEQv4/bEvtKiLGOu4HUO/x
a7emy1QZgJth5TqQf0pwPEJC0mV5LFoXtlOpjwIDAQABAoIBAB3xBshhYlEikfy2
NtJl0ll3BiGLtBHLjPLe1uN242CebkTTVxBP4jkVzfjJaucUGPvz8uoz6F+ShV2C
ysBT7SL79uhDrjBuV4TuvyoUuaHvQL3VVKWOmrHf9IVeWE9ut4fZtxbOxKcZ/MEs
ZIuhs/UJETr3To4jBJ7jP4iBda66LXeGDwnhia04zpFWwRRyRFvdVwTvMm9TN2GY
21J6rnc44bGQ/l0qsxSrv/OuW3ZmFNz85mRbuLswgawJgjMSlt1orLz5mdofDxlz
898nSrpJq/mAj8kgV+sH2jGv5FVZrv30W/0GlXObiJLhJiywYkpXbSn/H//w9+ij
ItQXjDECgYEA9RNootH8F0vuWVezKrt9ApE+8AzwltDgMlFc/HEjINnKFg7SWsSN
oinPVcQ07PS+2E7Fgv/6IFv/RTATGIPYrNWGE17dR8xmwh+Xrexz45c49cHTwVCF
VM2J0PvlcEEAscMe1bd5HqIOJm+hvvqfwWdacUPZgtsaS9F/e33vj0cCgYEAycQx
c5EQ9T82z+qMhQ/mf63kYMYDsHbv6F92Pt38V24yh8NTW5lEcV+NonDFrPVH5Vd3
gU1lvXnG8e3Aj6EXOeEzfu9dpfdyqyZXIU2hbRPYuha+goBd03pM8+YEIgJX3ktu
1Q+G6uMrSLVbe+l06OEcYmvj8xGNWrk/3+ZiB3kCgYEA15+c92xjLSgcbDTyKU3O
Pj0Gr/Pilf7u0ratZlowew3DdMbToxK+PogkqKQ5oKXxZ6Vet9R6AJCQtxIGKxKN
x/sRvOdBL5OScYeUT2zzxbFeZzODGNm8hZFViS6nfq1ibARtk8Gaai5Q3tZm6/3c
IzDI7VCyBiS6LS0EyeVSqa8CgYBRmM6G9jvtcssv+qMpjOyi5iheGraTPwZ262Re
uFe85Av7a7riaHGNiB83enP3JpsU3PKvkCV9IyqZ3JTrgTJrbe/tfdBZtmDhZngG
N+b4vfYADAKvtEo9pFBKstMpDdmLROZltAnUJFr05KNC0X8+Twuzof5l5stLzW9P
lVQ/wQKBgQDT4ixRRx4DlMMzBXNRTkUuZloEhZtLC5xj71KhE7OeOdJ0e6DHJMg3
VDVQk+y3Qc+8Hh9yxMK/zrYLdHSVyvHTk+7AbppLGX7ZtyLm/gVq3l3VjWKmXKbm
ZT+3+2gqVyjr/p69T7/aLexvfzU5LdjwO7SQFNB4qZaG74WpGAlkMg==
-----END RSA PRIVATE KEY-----

20
boring/test/root-ca-2.pem Normal file
View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDSzCCAjOgAwIBAgIUOQyKTQXHMKXZgwc5iktVZkL7hfwwDQYJKoZIhvcNAQEL
BQAwRzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxIzAhBgNVBAoM
GkludGVybmV0IFdpZGdpdHMgUHR5IEx0ZCAyMB4XDTIzMTIwNDEwMzA0N1oXDTIz
MTIxNDEwMzA0N1owRzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
IzAhBgNVBAoMGkludGVybmV0IFdpZGdpdHMgUHR5IEx0ZCAyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSgPW00nGOTje0FHm1HXT3ANHjW3HGPbxiMS
RvSoBEdv8R9e0PoKjse0xQsa5nKolbUhJBS6dkH+13VPOs6e+vZyXD6OYQfi2e42
Mw0SPFl5g2qUfy/17q3ipqPJnT6YHhGpx/P91F7yjDAF2MvNRGQ/6AYWD4MUr6+5
vPHEYhQ/JlAKcN/uWUM+bg2mwMNpmuGRV1tg0hKl9nT1fX1i6OSszPLIY1dC0eHv
2gRh1unrXNoYA76xohiwcJYAAPjrx167S8td8gmWnOwYnCnGIw2ceNpEQv4/bEvt
KiLGOu4HUO/xa7emy1QZgJth5TqQf0pwPEJC0mV5LFoXtlOpjwIDAQABoy8wLTAd
BgNVHQ4EFgQU1rQttC2Y2T0HZAjzRkacyFLVBr8wDAYDVR0TBAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAsVucQLIzAKHwN/4ZuVOPpfy/B3+i/Stu2tvNhBxWpbh9
RQTa0ylpDfaAOLr+TfxCyT0/NmblK4QWxN6AJ5AZS9fVnstLhInafv7So0n3LCg5
eQkVcQtMdwHucfMw/iz7r229mOHBbK6cnZhu72rcnn7N/RlU+iEucfi6jO+r9iD1
y20glRta+wEqIBg7nGhulOwwdHVkX7ulpnXIqNCgNvU7/Mp7J+CxuWmeZKLvUQAh
D/gHs9kOPK4izN9QBrRwbiyTaD8G7kFlVWD1tPXrOhBdE1L4OJWvUDSfO0DKueIW
aQa2fFsR1iPuFX/jeTuPk5X2+u5eH4pXj13NEqKvOA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDajCCAlKgAwIBAgIUSYINSQdbr8yzV186s/zQj+2zol8wDQYJKoZIhvcNAQEL
BQAwRzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxIzAhBgNVBAoM
GkludGVybmV0IFdpZGdpdHMgUHR5IEx0ZCAyMB4XDTI0MDEwMzEwMzUyM1oXDTI2
MDgxMjEwMzUyM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAK1R1hZ+di25dZefXsXbmZ7VUmcg2KcwzQ/kti1H
Dun0QVoVf9Ss6MfthmabW7jBpnyN4gJ29AhU+Lgt5AZEEJV6JxgE0lcmhUxUfo6v
5XNEj/vQXe0gV4niFXiF5WNU75cCL49zbcPc1/rHEwOEl8R+jNKyr/YEzrm9rwjE
h3hdel/A0K+F7GbkK+wqe49SOGqjicmqeSU5eYo5hvHJ7tJ/vFHEZQc8vfXS1iRt
AHyN1USXVqRkzVWfdmhX390aStxf1iNoKd6ldcp0QCrr5p3Bgtyw72H3HNnYLHNT
ehX6vBiK5IEaG+ngXJJQx6dXdNty8K3vlWlQ0qNf/2O9lBcCAwEAAaNQME4wHQYD
VR0OBBYEFGzTpQOrDV8syY2KnIiniHe4N/2aMB8GA1UdIwQYMBaAFNa0LbQtmNk9
B2QI80ZGnMhS1Qa/MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALw+
mUsLEoqk6eI4jGv5TPP56RPMRdI+wwmQ8+sQ4DIOzDErkIIQMtoP3aqU6kstHrfY
RZ2tJSWfKb9GcE2SL5VtHQCjSJLsE7f+fTpCFn41q0QMsXF22IOxT2eDvK4Kb496
NVulV6DhsHmbSjo6kla9U3Zqv4WiqLTNj757j+YgmplZQNx8vT5HkPIUi20IxEKV
m6CtPa0M2c2Hl/Y9v006AHmaXnabGvwnLsK92NV0oQb6KnB0mxOrL8od765SF9T0
OXiNK/2ilN2UB1ft16GI/tU+2N+sTmW9/+S5lExfG/S3qXJwc1l4OC9tH9CxOtYt
6Q+cAmgl6qxF3ltltCM=
-----END CERTIFICATE-----

View File

@ -17,7 +17,9 @@ footer = ""
[git]
conventional_commits = false
commit_parsers = [
{ message = "^Pull request", skip = true }
{ message = "^Merge", skip = true },
{ message = "^Pull request", skip = true },
{ message = "^Release", skip = true },
]
filter_commits = false
tag_pattern = "[v0-9]*"

View File

@ -4,54 +4,48 @@ version = { workspace = true }
authors = ["Steven Fackler <sfackler@gmail.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
edition = { workspace = true }
description = "Hyper TLS support via BoringSSL"
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
repository = { workspace = true }
documentation = "https://docs.rs/hyper-boring"
readme = "README.md"
exclude = ["test/*"]
rust-version = { workspace = true }
[package.metadata.docs.rs]
features = ["rpk", "pq-experimental"]
features = []
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["runtime"]
runtime = ["hyper/runtime"]
default = ["boring/default", "tokio-boring/default"]
# Use a FIPS-validated version of boringssl.
fips = ["tokio-boring/fips"]
# Link with precompiled FIPS-validated `bcm.o` module.
fips-link-precompiled = ["tokio-boring/fips-link-precompiled"]
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
rpk = ["tokio-boring/rpk"]
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
pq-experimental = ["tokio-boring/pq-experimental"]
# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, but
# keeps the related Rust API.
#
# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL_PATH` env variable) or
# with custom BoringSSL sources (via `BORING_BSSL_SOURCE_PATH` env variable) already containing
# required patches.
no-patches = ["tokio-boring/no-patches"]
fips = ["boring/fips", "tokio-boring/fips"]
[dependencies]
antidote = { workspace = true }
http = { workspace = true }
hyper = { workspace = true, features = ["client"] }
hyper = { workspace = true }
hyper-util = { workspace = true, features = ["client", "client-legacy"] }
linked_hash_set = { workspace = true }
once_cell = { 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 }
[dev-dependencies]
hyper = { workspace = true }
tokio = { workspace = true }
bytes = { workspace = true }
http-body-util = { workspace = true }
hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] }
hyper = { workspace = true, features = ["server"] }
tokio = { workspace = true, features = [ "full" ] }
tower = { workspace = true, features = ["util"] }
futures = { workspace = true }
[package.metadata.release]
pre-release-hook = [
"git-cliff",
"--workdir", "..",
"-o", "../RELEASE_NOTES",
"--tag", "{{version}}"
]

View File

@ -40,24 +40,32 @@ impl Borrow<[u8]> for HashSession {
pub struct SessionCache {
sessions: HashMap<SessionKey, LinkedHashSet<HashSession>>,
reverse: HashMap<HashSession, SessionKey>,
/// Maximum capacity of LinkedHashSet per SessionKey
per_key_session_capacity: usize,
}
impl SessionCache {
pub fn new() -> SessionCache {
pub fn with_capacity(per_key_session_capacity: usize) -> SessionCache {
SessionCache {
sessions: HashMap::new(),
reverse: HashMap::new(),
per_key_session_capacity,
}
}
pub fn insert(&mut self, key: SessionKey, session: SslSession) {
let session = HashSession(session);
self.sessions
.entry(key.clone())
.or_default()
.insert(session.clone());
let sessions = self.sessions.entry(key.clone()).or_default();
// if sessions exceed capacity, discard oldest
if sessions.len() >= self.per_key_session_capacity {
if let Some(hash) = sessions.pop_front() {
self.reverse.remove(&hash);
}
}
sessions.insert(session.clone());
self.reverse.insert(session, key);
}
@ -78,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

@ -1,256 +1,59 @@
//! Hyper SSL support via OpenSSL.
//! Hyper SSL support via BoringSSL.
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use crate::cache::{SessionCache, SessionKey};
use antidote::Mutex;
use crate::cache::SessionKey;
use boring::error::ErrorStack;
use boring::ex_data::Index;
use boring::ssl::{
ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslSessionCacheMode,
};
use http::uri::Scheme;
use hyper::client::connect::{Connected, Connection};
#[cfg(feature = "runtime")]
use hyper::client::HttpConnector;
use hyper::service::Service;
use hyper::Uri;
use once_cell::sync::OnceCell;
use std::fmt::Debug;
use std::future::Future;
use std::io;
use std::net;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{error::Error, fmt};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use boring::ssl::Ssl;
use std::fmt;
use std::sync::LazyLock;
use tokio_boring::SslStream;
use tower_layer::Layer;
mod cache;
#[cfg(test)]
mod test;
mod v1;
pub use self::v1::*;
fn key_index() -> Result<Index<Ssl, SessionKey>, ErrorStack> {
static IDX: OnceCell<Index<Ssl, SessionKey>> = OnceCell::new();
IDX.get_or_try_init(Ssl::new_ex_index).map(|v| *v)
static IDX: LazyLock<Index<Ssl, SessionKey>> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
Ok(*IDX)
}
#[derive(Clone)]
struct Inner {
ssl: SslConnector,
cache: Arc<Mutex<SessionCache>>,
#[allow(clippy::type_complexity)]
callback: Option<
Arc<dyn Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + Sync + Send>,
>,
/// Settings for [`HttpsLayer`]
pub struct HttpsLayerSettings {
session_cache_capacity: usize,
}
impl Inner {
fn setup_ssl(&self, uri: &Uri, host: &str) -> Result<ConnectConfiguration, 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);
Ok(conf)
impl HttpsLayerSettings {
/// Constructs an [`HttpsLayerSettingsBuilder`] for configuring settings
#[must_use]
pub fn builder() -> HttpsLayerSettingsBuilder {
HttpsLayerSettingsBuilder(HttpsLayerSettings::default())
}
}
/// A layer which wraps services in an `HttpsConnector`.
pub struct HttpsLayer {
inner: Inner,
}
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(mut ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
let cache = Arc::new(Mutex::new(SessionCache::new()));
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);
}
}
});
ssl.set_remove_session_callback({
let cache = cache.clone();
move |_, session| cache.lock().remove(session)
});
Ok(HttpsLayer {
inner: Inner {
ssl: ssl.build(),
cache,
callback: None,
},
})
}
/// Registers a callback which can customize the configuration of each connection.
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));
}
}
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 Default for HttpsLayerSettings {
fn default() -> Self {
Self {
session_cache_capacity: 8,
}
}
}
/// A Connector using OpenSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
inner: Inner,
}
/// Builder for [`HttpsLayerSettings`]
pub struct HttpsLayerSettingsBuilder(HttpsLayerSettings);
#[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 + 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))
impl HttpsLayerSettingsBuilder {
/// Sets maximum number of sessions to cache. Session capacity is per session key (domain).
/// Defaults to 8.
pub fn set_session_cache_capacity(&mut self, capacity: usize) {
self.0.session_cache_capacity = capacity;
}
/// Registers a callback which can customize the configuration of each connection.
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));
}
}
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 + 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 let (Some('['), Some(']')) = (chars.next(), chars.last()) {
if host[1..last].parse::<net::Ipv6Addr>().is_ok() {
host = &host[1..last];
}
}
}
let config = inner.setup_ssl(&uri, host)?;
let stream = tokio_boring::connect(config, host, conn).await?;
Ok(MaybeHttpsStream::Https(stream))
};
Box::pin(f)
/// Consumes the builder, returning a new [`HttpsLayerSettings`]
#[must_use]
pub fn build(self) -> HttpsLayerSettings {
self.0
}
}
@ -262,72 +65,6 @@ pub enum MaybeHttpsStream<T> {
Https(SslStream<T>),
}
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),
}
}
}
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> fmt::Debug for MaybeHttpsStream<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {

343
hyper-boring/src/v1.rs Normal file
View File

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

Some files were not shown because too many files have changed in this diff Show More