Compare commits

...
Sign in to create a new pull request.

69 commits

Author SHA1 Message Date
Richard Levitte
95b4936aa1 OSSL_FN: Add mod_mul and mod_sqr stanza tests
Some checks failed
Run-checker CI / run-checker (no-threads) (push) Has been cancelled
Run-checker CI / run-checker (no-tls) (push) Has been cancelled
Run-checker CI / run-checker (no-tls1_2) (push) Has been cancelled
Run-checker CI / run-checker (no-tls1_3) (push) Has been cancelled
Run-checker merge / run-checker (enable-asan enable-ubsan no-shared no-asm -DOPENSSL_SMALL_FOOTPRINT -fno-sanitize=function) (push) Has been cancelled
Run-checker merge / run-checker (enable-pie) (push) Has been cancelled
Run-checker merge / run-checker (enable-ubsan no-asm -DOPENSSL_SMALL_FOOTPRINT -fno-sanitize=function) (push) Has been cancelled
Run-checker merge / run-checker (enable-weak-ssl-ciphers) (push) Has been cancelled
Run-checker merge / run-checker (enable-zlib) (push) Has been cancelled
Run-checker merge / run-checker (no-dso) (push) Has been cancelled
Run-checker merge / run-checker (no-ec2m enable-fips) (push) Has been cancelled
Run-checker merge / run-checker (no-err) (push) Has been cancelled
Run-checker merge / run-checker (no-filenames) (push) Has been cancelled
Run-checker merge / run-checker (no-integrity-only-ciphers) (push) Has been cancelled
Run-checker merge / run-checker (no-module) (push) Has been cancelled
Run-checker merge / run-checker (no-ocsp) (push) Has been cancelled
Run-checker merge / run-checker (no-pinshared) (push) Has been cancelled
Run-checker merge / run-checker (no-shared) (push) Has been cancelled
Run-checker merge / run-checker (no-srp) (push) Has been cancelled
Run-checker merge / run-checker (no-srtp) (push) Has been cancelled
Run-checker merge / run-checker (no-ts) (push) Has been cancelled
Run-checker merge / run-checker (no-ui) (push) Has been cancelled
Run-checker merge / jitter (push) Has been cancelled
Run-checker merge / threads_sanitizer_atomic_fallback (push) Has been cancelled
Windows GitHub CI / plain (push) Has been cancelled
Windows GitHub CI / minimal (push) Has been cancelled
Windows GitHub CI / cygwin (windows-2022, map[arch:win64 config:-DCMAKE_C_COMPILER=gcc --strict-warnings enable-demos no-fips]) (push) Has been cancelled
Windows GitHub CI / mingw64 (map[arch:mingw config:-Wno-array-bounds -Wno-stringop-overflow target:i686]) (push) Has been cancelled
Windows GitHub CI / mingw64 (map[arch:mingw64 config:enable-demos -Wno-array-bounds target:x86_64]) (push) Has been cancelled
Run-checker CI / run-checker (no-ecx) (push) Failing after 5s
Add file_modmul() and file_modsqr() to test/fntest.c, using the
existing bntest.c counterparts as a template.  The ModMul and ModSqr
stanzas in bnmod.txt now exercise OSSL_FN_mod_mul() and
OSSL_FN_mod_sqr() through the stanza-driven test harness, providing a
real call site for the new modular operations.

Because OSSL_FN is unsigned, the operands are passed as absolute values
(via bn_get_ossl_fn()).  For mod_mul, if the operands have different
signs, the non-negative modular residue is recovered with BN_sub(ret,
m, ret) after release, keeping the sign logic at the BIGNUM boundary.
No sign fixup is needed for mod_sqr since squaring is always
non-negative.

Include bnmod.txt in 11-test_fn.t so the mod stanzas are run as part
of the regular test suite.

Related-to: doc/designs/fixed-size-large-numbers.md
Issue: https://github.com/openssl/project/issues/1932
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Thu Jul 23 15:02:40 2026
(Merged from https://github.com/openssl/openssl/pull/31479)
2026-07-24 12:07:38 +02:00
Richard Levitte
7a88869224 OSSL_FN: Add modular context sizing
The modular OSSL_FN operations are layered on top of each other, but
the sizing of the required OSSL_FN_CTX arena was not available as
operation-specific helpers, mirroring OSSL_FN_div_ctx_size(),
OSSL_FN_mul_ctx_size(), and OSSL_FN_sqr_ctx_size().

Add ctx-size helpers for the modular operations and compose them from
lower-level helpers.  Each helper returns arena payload bytes, accounts
for its own live temporaries, and adds the maximum nested requirement
while those temporaries are live.

Assisted-by: Pi:openai/gpt-5.5
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Thu Jul 23 15:02:39 2026
(Merged from https://github.com/openssl/openssl/pull/31479)
2026-07-24 12:07:38 +02:00
Richard Levitte
74084eb28e Add unit tests for OSSL_FN modular arithmetic functions
Assisted-by: Pi:moonshotai/kimi-k2.6
Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Thu Jul 23 15:02:37 2026
(Merged from https://github.com/openssl/openssl/pull/31479)
2026-07-24 12:07:38 +02:00
Richard Levitte
10a5bb87dc OSSL_FN: Add the modular arithmetic functions
- OSSL_FN_mod_add
- OSSL_FN_mod_add_quick
- OSSL_FN_mod_sub
- OSSL_FN_mod_sub_quick
- OSSL_FN_mod_mul
- OSSL_FN_mod_sqr
- OSSL_FN_mod_lshift
- OSSL_FN_mod_lshift_quick
- OSSL_FN_mod_lshift1
- OSSL_FN_mod_lshift1_quick

Related-to: doc/designs/fixed-size-large-numbers.md
Issue: https://github.com/openssl/project/issues/1932
Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Thu Jul 23 15:02:35 2026
(Merged from https://github.com/openssl/openssl/pull/31479)
2026-07-24 12:07:38 +02:00
Richard Levitte
18a19d81b9 OSSL_FN: implement internal 'ossl_fn_add_words' and 'ossl_fn_add_words'
These functions fulfill the same purpose as 'bn_add_words' and
'bn_sub_words', but adapted to OSSL_FN_ULONG arrays with varying
sizes.  They are useful for partial calculations where carry and
borrow are of interest, but also fulfill a purpose when that's not
interesting.

Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Thu Jul 23 15:02:33 2026
(Merged from https://github.com/openssl/openssl/pull/31479)
2026-07-24 12:07:38 +02:00
Billy Brumley
ab42643c66 [test] bntest: add ModInv file tests
Add file_modinv() to bntest.c, driven by a new bnmodinv.txt corpus and
wired into 10-test_bn.t.
- Positive tests assert equalBN and a mult sanity check
- Negative tests assert NULL return plus the BN_R_NO_INVERSE error
Corpus covers:
- in-range A
- out-of-range A
- positive and negative A for both of the above
- degenerate moduli M = 0 and M = 1
- RFC 4754 sourced KATs
- RFC 7091 sourced KATs

Assisted-by: Claude:claude-opus-4-8

Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
MergeDate: Thu Jul 23 14:20:08 2026
(Merged from https://github.com/openssl/openssl/pull/31985)
2026-07-24 12:07:38 +02:00
Richard Levitte
82b214d9bc OSSL_FN: Make RAND top selector count-based
Renumber OSSL_FN_RAND_TOP_ANY / _ONE / _TWO from -1 / 0 / 1 to
0 / 1 / 2, so each value is the number of high bits the result is
forced to set.  This matches the already-count-based
OSSL_FN_RAND_BOTTOM_* (0 = unconstrained, 1 = force the low bit),
making the whole selector family read the same way.

This is a deliberate departure from the BN counterpart: BN_RAND_TOP_*
stays -1 / 0 / 1, but OSSL_FN_RAND_TOP_* has no legacy tier and no
need for a sentinel, so the values are the counts themselves.
Callers that use the symbolic macros are unaffected; only raw-literal
callers would notice, and there are none in tree.

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:17:18 2026
(Merged from https://github.com/openssl/openssl/pull/31959)
2026-07-24 12:07:38 +02:00
Richard Levitte
1588f1f3eb OSSL_FN: Add random-number functions rand / priv_rand / range
Add OSSL_FN_rand(), OSSL_FN_priv_rand(), OSSL_FN_rand_range(), and
OSSL_FN_priv_rand_range(), the OSSL_FN counterparts of the BN_rand_ex() /
BN_priv_rand_ex() / BN_rand_range_ex() / BN_priv_rand_range_ex() family
from crypto/bn/bn_rand.c -- the first OSSL_FN random-number helpers.

The BN counterparts take a BN_CTX only to recover the library context
(via ossl_bn_get_libctx()); the OSSL_FN peers take an OSSL_LIB_CTX
directly, and per the OSSL_FN naming convention the BN _ex / _ex{n}
suffixes are dropped.

ossl_fn_rand() departs from the BN counterpart's byte-buffer mechanics:
bnrand() draws into a heap big-endian buffer and shapes via BN_bin2bn(),
whereas OSSL_FN limbs are fixed-size, so random bytes are drawn straight
into rnd->d and the top/bottom/mask shaping is done as limb value
operations (endian-neutral).  A destination too small for |bits| is an
error (OSSL_FN_R_RESULT_ARG_TOO_SMALL), not an implicit expansion.

The range variants port bnrand_range() faithfully; the range->neg
rejection is dropped (OSSL_FN is unsigned).  The leak profile matches the
BN counterpart: control flow branches on bits/top/bottom (caller-chosen,
public) and the byte-draw return value, never on the random bytes
themselves; the range loop count leaks the magnitude of |range| and the
rejection probability.  The TESTING mangle variant (BN_bntest_rand) is
deferred; a TODO(FIXNUM) marks the spot.

New error reasons: OSSL_FN_R_BITS_TOO_SMALL, OSSL_FN_R_INVALID_RANGE,
OSSL_FN_R_TOO_MANY_ITERATIONS.

Tests are property-based (random values cannot be value-compared) and
verify the bit-width, top/bottom constraints, zero-padding, and
0 <= r < range, on both 64-bit and 32-bit limb widths.

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:17:16 2026
(Merged from https://github.com/openssl/openssl/pull/31959)
2026-07-24 12:07:38 +02:00
Richard Levitte
d0e86cb69e OSSL_FN: Add assignment helpers set_word, one, zero
The OSSL_FN counterparts of the small BN assignment helpers that the
modular-square-root operation (and other upcoming operations) lean on:
OSSL_FN_set_word(), OSSL_FN_one(), and OSSL_FN_zero() mirror
BN_set_word(), BN_one(), and BN_zero().

The fixed-size semantics diverge where BN_set_word() grows the BIGNUM
(bn_wexpand(a, 1)) to guarantee room: OSSL_FN cannot grow, so a
zero-width destination fails with OSSL_FN_R_RESULT_ARG_TOO_SMALL
instead.

OSSL_FN_set_word() is constant-time with respect to w's value, unlike
BN_set_word() (which sets a->top = (w ? 1 : 0)); OSSL_FN has no top, so
the full dsize array always holds the value and the only branch is on
the public width.

OSSL_FN_zero() is a plain value assignment, not a secure wipe;
OSSL_FN_clear() (OPENSSL_cleanse()) is the secure-wipe counterpart,
mirroring the BN_zero() / BN_clear() split.

Tests cross-check set_word against the internal ossl_fn_set_words() and
cover the dsize == 0 error path.  Verified on both 64-bit (linux-x86_64)
and 32-bit (linux-x86) limb widths.

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:17:14 2026
(Merged from https://github.com/openssl/openssl/pull/31959)
2026-07-24 12:07:38 +02:00
Richard Levitte
9cc605f48e OSSL_FN: Implement OSSL_FN_add_word / OSSL_FN_sub_word
The declarations of OSSL_FN_add_word() / OSSL_FN_sub_word() have been in
include/crypto/fn.h since the initial 'add'/'sub' commit, but no
implementation ever landed -- the symbols were simply absent, with no
caller noticing.  This adds them next to OSSL_FN_add() / OSSL_FN_sub().

While here, correct the signature: the word argument was declared
'const OSSL_FN_ULONG *w' (a pointer), which matches no BN counterpart;
it is now 'OSSL_FN_ULONG w' by value, mirroring BN_add_word() /
BN_sub_word().  Safe because there was no implementation and no caller.

These are the OSSL_FN counterparts of BN_add_word() / BN_sub_word().
The fixed-size unsigned semantics diverge where the BN counterpart grows
the BIGNUM (bn_wexpand) or records a sign (a->neg, storing w - a): OSSL_FN
instead truncates a carry/borrow past the last limb, and a borrow off the
top yields the 2's-complement wrap-around.

Both are non-constant-time, matching BN_add_word()/BN_sub_word(): the
carry/borrow loop stops early once exhausted, so the number of limbs
touched depends on the operand's value.

Tests cross-check every case against OSSL_FN_add() / OSSL_FN_sub() with
a single-limb operand.  Verified on both 64-bit (linux-x86_64) and 32-bit
(linux-x86) limb widths.

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:17:12 2026
(Merged from https://github.com/openssl/openssl/pull/31959)
2026-07-24 12:07:38 +02:00
Richard Levitte
97618c446f OSSL_FN: Add introspection helpers is_word, is_zero, is_one, is_odd
The OSSL_FN counterparts of the small BN predicates that the
modular-square-root operation (and other upcoming operations) lean on:

  - OSSL_FN_is_word() mirrors BN_is_word() and BN_abs_is_word(), which
    collapse into one here since OSSL_FN is unsigned (no sign to ignore);
    a separate OSSL_FN_abs_is_word is therefore not added.
  - OSSL_FN_is_zero() and OSSL_FN_is_one() are kept as named predicates
    for readability at call sites rather than written inline.
  - OSSL_FN_is_odd() mirrors BN_is_odd().

The leak profile matches the BN counterparts: control flow branches only
on the operand's public width (dsize), not on limb values; limb values
are combined with constant-time selects, as in OSSL_FN_cmp() and the
fixed-top BN helpers.

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:17:10 2026
(Merged from https://github.com/openssl/openssl/pull/31959)
2026-07-24 12:07:38 +02:00
Richard Levitte
60551b673b OSSL_FN: Add OSSL_FN_is_bit_set()
Add OSSL_FN_is_bit_set(), the OSSL_FN counterpart of BN_is_bit_set().
The leak profile matches the BN counterpart: control flow branches only
on the operand's public width (dsize), not on limb values.

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:17:09 2026
(Merged from https://github.com/openssl/openssl/pull/31959)
2026-07-24 12:07:38 +02:00
Richard Levitte
ed9ea9710d OSSL_FN: Add lshift1 round-trip checks to fntest
file_lshift1() now mirrors bntest.c's file_lshift1() where OSSL_FN
primitives allow: it cross-checks LShift1 against A + A and A * 2,
then round-trips LShift1 >> 1 back to A with OSSL_FN_rshift1.

The LShift1 / 2, LShift1 % 2, and set-LSB rshift1-flooring checks
from bntest.c are deferred to a future commit: they need
OSSL_FN_div() and OSSL_FN_set_bit(), neither of which exists yet.

Issue: https://github.com/openssl/project/issues/2015
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:15:42 2026
(Merged from https://github.com/openssl/openssl/pull/31830)
2026-07-24 12:07:38 +02:00
Richard Levitte
10d074a041 OSSL_FN: Add OSSL_FN right-shift tests to fntest
The new file_rshift() handler is driven by the RShift stanzas already
in bnshift.txt, so no dedicated data file is added.  The N == 1
OSSL_FN_rshift1 cross-check and the file_lshift() round-trip mirror
bntest.c.

Issue: https://github.com/openssl/project/issues/2015
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:15:41 2026
(Merged from https://github.com/openssl/openssl/pull/31830)
2026-07-24 12:07:38 +02:00
Richard Levitte
c6866fa3d3 OSSL_FN: Add OSSL_FN right shifts
Add fixed-size right-shift operations for OSSL_FN values and cover them
with direct API tests.

The tests exercise bit and limb shifts, wider and narrower destinations,
aliasing, and invalid negative shift counts.

Related-to: doc/designs/fixed-size-large-numbers.md
Issue: https://github.com/openssl/project/issues/2015
Assisted-by: Pi:openai/gpt-5.5
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:15:39 2026
(Merged from https://github.com/openssl/openssl/pull/31830)
2026-07-24 12:07:38 +02:00
Richard Levitte
7593990ddf OSSL_FN: Add div and mod stanza tests
Teach fntest to run the existing quotient/remainder stanzas through
OSSL_FN_div(), and additionally check the remainder against
OSSL_FN_mod().  fntest now runs all recognized stanzas from each
data file, so 11-test_fn.t passes the files directly without any
per-stanza filtering.  Also include crypto/fn.h before crypto/bn.h
in fntest so the OSSL_FN typedef is visible where bn.h uses it.

Related-to: doc/designs/fixed-size-large-numbers.md
Issue: https://github.com/openssl/project/issues/1852
Assisted-by: Pi:openai/gpt-5.5
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:14:03 2026
(Merged from https://github.com/openssl/openssl/pull/30822)
2026-07-24 12:07:38 +02:00
Richard Levitte
b5aa70d2c1 OSSL_FN: Add division context sizing
Add OSSL_FN_div_ctx_size() and make OSSL_FN_mod_ctx_size() a thin
inline wrapper around it.  The sizing remains conservative: it uses
allocated operand widths and does not duplicate the runtime scan that
trims the denominator to its highest non-zero limb.  Document both
helpers with Doxygen comments matching the mul/sqr ctx_size helpers.

Related-to: doc/designs/fixed-size-large-numbers.md
Issue: https://github.com/openssl/project/issues/1852
Assisted-by: Pi:openai/gpt-5.5
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:14:00 2026
(Merged from https://github.com/openssl/openssl/pull/30822)
2026-07-24 12:07:38 +02:00
Richard Levitte
12929d3852 OSSL_FN: Add the 'div' and 'mod' functions
OSSL_FN_div() divides two numbers, and calculates the quotient and
the remainder.  The result OSSL_FNs may be of any size, and if they're
smaller than the results of dividing the operands, the results are
truncated to that size.

OSSL_FN_mod() is a very simple function that wraps around OSSL_FN_div()
and only returns the remainder.

Related-to: doc/designs/fixed-size-large-numbers.md
Issue: https://github.com/openssl/project/issues/1852
Assisted-by: Pi:openai/gpt-5.5
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul 23 14:13:58 2026
(Merged from https://github.com/openssl/openssl/pull/30822)
2026-07-24 12:07:38 +02:00
Richard Levitte
496092d36f OSSL_FN: use ctx-size helpers in fntest
The mul and sqr tests in test/fntest.c sized their OSSL_FN_CTX arenas
by hand, passing fixed frame/number/limb counts to OSSL_FN_CTX_new().
Switch them to OSSL_FN_CTX_new_size() driven by OSSL_FN_mul_ctx_size()
/ OSSL_FN_sqr_ctx_size(), passing the actual result and operand
OSSL_FNs so the per-op helpers can account for aliasing and
destination width.

This gives the ctx-size helpers real callers in the tree and makes the
test sizing match what OSSL_FN_mul() / OSSL_FN_sqr() actually need
instead of the previous oversized estimates.

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>
Assisted-by: Pi:openai/gpt-5.5

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Wed Jul 15 13:04:50 2026
(Merged from https://github.com/openssl/openssl/pull/31794)
2026-07-14 17:42:57 +02:00
Richard Levitte
9ff9bf1c0a OSSL_FN: Size contexts in bytes
OSSL_FN_CTX callers currently describe the arena in frames, numbers
and limbs.  That works for a single operation, but it makes higher
level sizing awkward because each caller has to keep redoing the same
conversion.

Add an overflow-checked OSSL_FN_CTX_size() helper that returns the
arena payload size, plus byte-sized constructors for normal and secure
contexts.  The existing frame/number/limb constructors become wrappers
around it so current callers keep working while newer code can compose
byte sizes directly.  The old internal ossl_fn_ctx_calculate_arena_size()
inline is replaced by the public, overflow-checked helper.

Add per-operation ctx-size helpers OSSL_FN_mul_ctx_size() and
OSSL_FN_sqr_ctx_size(), which fold in the aliasing and
destination-width considerations of OSSL_FN_mul() / OSSL_FN_sqr().

Assisted-by: Pi:openai/gpt-5.5
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Wed Jul 15 13:04:48 2026
(Merged from https://github.com/openssl/openssl/pull/31794)
2026-07-14 17:42:57 +02:00
Richard Levitte
3923fd51b1 OSSL_FN: unwrap BN_CTX's wrapping of OSSL_FN_CTX
The BN_CTX -> OSSL_FN_CTX acquisition machinery
(bn_ctx_acquire_ossl_fn_ctx / bn_ctx_release_ossl_fn_ctx and the
fn_ctx field on struct bignum_ctx) existed only to serve the BN_*
arithmetic wrappers that delegated to OSSL_FN.  Those wrappers were
removed in previous commits, leaving the machinery orphaned except
for the self-test that exercised it; OSSL_FN consumers obtain their
OSSL_FN_CTX directly via OSSL_FN_CTX_new() / OSSL_FN_CTX_secure_new().

Remove the wrapping machinery and its dedicated test
(test_bn_ctx_fn_ctx) by reverting:

  e3213b0c03 ("Add OSSL_FN_CTX integration with BN_CTX")

Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Tue Jul 14 11:06:24 2026
(Merged from https://github.com/openssl/openssl/pull/31895)
2026-07-14 16:13:07 +02:00
Richard Levitte
5c2f95c4b0 OSSL_FN: Add OSSL_FN stanza arithmetic test
Add fntest as a stanza-driven arithmetic test for the OSSL_FN
operations currently available in this branch.  The test reuses the
existing BN input files and reports known but unsupported stanza types
as skipped.

Wire the new test program into the build and add a test_fn recipe for
sum, multiplication, square, and left-shift coverage.

Assisted-by: Pi:openai/gpt-5.5
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>
Resolves: https://github.com/openssl/project/issues/2021

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jul 12 08:37:03 2026
(Merged from https://github.com/openssl/openssl/pull/31881)
2026-07-14 16:13:07 +02:00
Richard Levitte
0df3c5e1de OSSL_FN: Add read-only BIGNUM OSSL_FN accessor
Add bn_get_ossl_fn(), a read-only counterpart to bn_acquire_ossl_fn().
Unlike the latter, it neither acquires nor resizes the BIGNUM: the
BIGNUM stays usable with BN_ functions, no bn_release() is needed, and
the returned OSSL_FN keeps the BIGNUM's current allocated width.  This
suits read-only operands at OSSL_FN call sites.

Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jul 12 08:37:02 2026
(Merged from https://github.com/openssl/openssl/pull/31881)
2026-07-14 16:13:07 +02:00
Richard Levitte
312250036e OSSL_FN: Expose BN OSSL_FN acquire helpers
Move the BIGNUM to OSSL_FN acquire/release helpers out of
bn_local.h and declare them in the internal BN header.  This makes
them available to call sites that need to operate temporarily on the
fixed-width representation.

Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jul 12 08:37:00 2026
(Merged from https://github.com/openssl/openssl/pull/31881)
2026-07-14 16:13:07 +02:00
Richard Levitte
9a90994e39 OSSL_FN: unwrap the BIGNUM shift and compare functions
Restore the top-aware implementations of BN_num_bits, BN_ucmp, BN_cmp,
BN_lshift1, and BN_lshift to their pre-wrap form, removing the OSSL_FN
call-site wrapping (the dual-path `if (a->data == NULL) { legacy } else
{ OSSL_FN_* }` branches, each marked TODO(FIXNUM): TO BE REMOVED)
introduced in:

  5072424737 ("OSSL_FN: Wrap BN shift and compare functions around OSSL_FN functions")

This also reverts the in-body tuning that landed on top of that wrapping:

  1a87fdb411 ("Check result of acquiring OSSL_FN")

The foundational BN_MAX_WORDS bound in bn_expand_internal (bn_lib.c) and
the bn_check_top dmax assert (bn_local.h), both from e3ae863a2d, are
preserved -- restoring bn_lib.c to 5072424737^ keeps them, since they
predate the shift/compare wrapper and were never modified by it.

The conversion helpers bn_acquire_ossl_fn()/bn_release() are left in
place; BN->OSSL_FN conversion moves to top-level crypto call sites
instead.

Issue: https://github.com/openssl/project/issues/2020
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul  9 15:34:26 2026
(Merged from https://github.com/openssl/openssl/pull/31884)
2026-07-14 16:13:07 +02:00
Richard Levitte
ebc87a83dc OSSL_FN: unwrap the BIGNUM 'sqr' function
Restore the top-aware BN_sqr implementation to its pre-wrap form, removing
the OSSL_FN call-site wrapping introduced in:

  f52bf9d167 ("OSSL_FN: Wrap the BIGNUM 'sqr' function around the OSSL_FN 'sqr' function")

This also reverts the in-body tuning that landed on top of that wrapping:

  1763d6aff3 ("Acquire BN_sqr result before ctx sizing")
  a7b662c282 ("Use BN_CTX cached OSSL_FN_CTX in BN_mul() and BN_sqr()")
  cbc3f0b448 ("Check result of acquiring OSSL_FN or OSSL_FN_CTX")

(only the BN_sqr portions of a7b662c282 and cbc3f0b448 are affected).

The conversion helpers bn_acquire_ossl_fn()/bn_release() and the
foundational BN_MAX_WORDS hardening in bn_lib.c/bn_local.h are left in
place; BN->OSSL_FN conversion moves to top-level crypto call sites
instead.

Issue: https://github.com/openssl/project/issues/2020
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul  9 15:34:24 2026
(Merged from https://github.com/openssl/openssl/pull/31884)
2026-07-14 16:13:07 +02:00
Richard Levitte
d8f17e7d37 OSSL_FN: unwrap the BIGNUM 'mul' function
Restore the top-aware BN_mul implementation to its pre-wrap form, removing
the OSSL_FN call-site wrapping introduced in:

  8f77f825fa ("OSSL_FN: Wrap BIGNUM the 'mul' function around the OSSL_FN 'mul' function")

This also reverts the in-body tuning that landed on top of that wrapping:

  06bc8339d9 ("Acquire BN_mul result before ctx sizing")
  a7b662c282 ("Use BN_CTX cached OSSL_FN_CTX in BN_mul() and BN_sqr()")
  cbc3f0b448 ("Check result of acquiring OSSL_FN or OSSL_FN_CTX")

(only the BN_mul portions of a7b662c282 and cbc3f0b448 are affected).

The conversion helpers bn_acquire_ossl_fn()/bn_release() and the
foundational BN_MAX_WORDS hardening in bn_lib.c/bn_local.h are left in
place; BN->OSSL_FN conversion moves to top-level crypto call sites
instead.

Issue: https://github.com/openssl/project/issues/2020
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul  9 15:34:22 2026
(Merged from https://github.com/openssl/openssl/pull/31884)
2026-07-14 16:13:07 +02:00
Richard Levitte
3b2c8afdc1 OSSL_FN: unwrap the BIGNUM 'add' and 'sub' functions
Restore the top-aware BN_add/BN_sub/BN_uadd/BN_usub implementations to
their pre-wrap form, removing the OSSL_FN call-site wrapping introduced in:

  e3ae863a2d ("OSSL_FN: Wrap BIGNUM 'add' and 'sub' functions around OSSL_FN functions")

This also reverts the in-body tuning that landed on top of that wrapping:

  3eb0b45e63 ("Use top to size BN add/sub results")
  cbc3f0b448 ("Check result of acquiring OSSL_FN or OSSL_FN_CTX")

(only the BN_add/BN_sub portions of cbc3f0b448 are affected).

The conversion helpers bn_acquire_ossl_fn()/bn_release() and the
foundational BN_MAX_WORDS hardening in bn_lib.c/bn_local.h are left in
place; BN->OSSL_FN conversion moves to top-level crypto call sites
instead.

Issue: https://github.com/openssl/project/issues/2020
Assisted-by: Pi:z-ai/glm-5.2
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Jul  9 15:34:20 2026
(Merged from https://github.com/openssl/openssl/pull/31884)
2026-07-14 16:13:07 +02:00
Richard Levitte
910c2fcfc0 Decouple BIGNUM from OSSL_FN in the design doc
Reflect the decision (openssl/project#2018) to stop wrapping BN_
operator functions around OSSL_FN_ operator functions: BN functions
keep their current behaviour, and BIGNUM<->OSSL_FN conversion
happens at top-level crypto call sites.

Also drop the "Dropping top from BIGNUM" section and the stale
TODO(FIXNUM) field-removal comments, since `top` stays in BIGNUM.

Refs: https://github.com/openssl/project/issues/2019
Assisted-by: Pi:z-ai/glm-5.2
Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Thu Jul  9 10:54:20 2026
(Merged from https://github.com/openssl/openssl/pull/31847)
2026-07-14 16:13:07 +02:00
Igor Ustinov
e3a6ede280 Check result of acquiring OSSL_FN
These calls might return NULL because they allocate memory.

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Richard Levitte <levitte@openssl.org>
MergeDate: Tue Jun 30 06:45:42 2026
(Merged from https://github.com/openssl/openssl/pull/31754)
2026-07-14 16:13:07 +02:00
Richard Levitte
d07d760f8c OSSL_FN: Wrap BN shift and compare functions around OSSL_FN functions
Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Fri Jun 26 14:23:18 2026
(Merged from https://github.com/openssl/openssl/pull/31460)
2026-07-14 16:13:07 +02:00
Richard Levitte
0b478aba2c OSSL_FN: Add basic shift and compare functions
The following functions are added:

- OSSL_FN_num_bits
- OSSL_FN_cmp
- OSSL_FN_lshift
- OSSL_FN_lshift1

Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Fri Jun 26 14:23:17 2026
(Merged from https://github.com/openssl/openssl/pull/31460)
2026-07-14 16:13:07 +02:00
Tomas Mraz
04566a1781 Check result of acquiring OSSL_FN or OSSL_FN_CTX
These calls might return NULL because they
allocate memory.

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Daniel Kubec <kubec@openssl.foundation>
MergeDate: Fri Jun 26 14:09:33 2026
(Merged from https://github.com/openssl/openssl/pull/31613)
2026-07-14 16:13:07 +02:00
Richard Levitte
ac0e2da45e Use BN_CTX cached OSSL_FN_CTX in BN_mul() and BN_sqr()
The previous commit added bn_ctx_acquire_ossl_fn_ctx() and
bn_ctx_release_ossl_fn_ctx() to cache an OSSL_FN_CTX inside a BN_CTX.
However, BN_mul() and BN_sqr() were still creating and freeing their own
temporary OSSL_FN_CTX on every call, ignoring the passed-in BN_CTX entirely.

Update both functions to acquire the OSSL_FN_CTX from the BN_CTX and release
it afterwards, allowing the cached context to be reused across calls.

Assisted-by: Pi:moonshotai/kimi-k2.6
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jun 14 08:40:47 2026
(Merged from https://github.com/openssl/openssl/pull/31431)
2026-07-14 16:13:07 +02:00
Richard Levitte
0e8b647fdb Add OSSL_FN_CTX integration with BN_CTX
Add an OSSL_FN_CTX * pointer to struct bignum_ctx, allowing a BN_CTX
to cache an OSSL_FN_CTX for use by BIGNUM wrapper functions.  The
pointer is freed automatically when BN_CTX_free() is called.

Also add bn_ctx_acquire_ossl_fn_ctx() and bn_ctx_release_ossl_fn_ctx():
- acquire creates (or reuses if large enough) an OSSL_FN_CTX inside
  the BN_CTX, sizing it according to the caller's needs.
- release checks that no frames remain in the cached OSSL_FN_CTX.

The acquire function respects BN_FLG_SECURE: if the BN_CTX was created
with BN_CTX_secure_new(), the OSSL_FN_CTX is also allocated in secure
memory.

Assisted-by: Pi:moonshotai/kimi-k2.6
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jun 14 08:40:44 2026
(Merged from https://github.com/openssl/openssl/pull/31431)
2026-07-14 16:13:07 +02:00
Richard Levitte
5678648723 Expose OSSL_FN_CTX internals via fn_local.h
Move struct ossl_fn_ctx_st and struct ossl_fn_ctx_frame_st from
fn_ctx.c to fn_local.h, making them accessible to internal code in
the bn/ directory and elsewhere.  Also expose
ossl_fn_ctx_calculate_arena_size() as an inline helper so callers
can determine the required arena size without duplicating the formula.

This is groundwork for BN_CTX integration.

Assisted-by: Pi:moonshotai/kimi-k2.6
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jun 14 08:40:42 2026
(Merged from https://github.com/openssl/openssl/pull/31431)
2026-07-14 16:13:07 +02:00
Richard Levitte
3a5594d70e Add test for OSSL_FN_CTX_peak_usage
Verify that peak_usage starts at zero, rises after frame start and
allocation, and never decreases after a frame is ended.

Also correct misleading comments that claimed OSSL_FN_CTX_new()
takes a size in bytes -- the last parameter is actually max_n_limbs.

Assisted-by: Pi:moonshotai/kimi-k2.6
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jun 14 08:40:40 2026
(Merged from https://github.com/openssl/openssl/pull/31431)
2026-07-14 16:13:07 +02:00
Richard Levitte
e77ef33428 Add OSSL_FN_CTX_peak_usage() for arena usage instrumentation
Track the maximum number of frames, numbers and limbs ever consumed
from an OSSL_FN_CTX arena during its lifetime.  This allows callers
to empirically size their contexts instead of predicting precise
max_n_frames / max_n_numbers / max_n_limbs ahead of time.

These peak usages are updated whenever a frame is started or an OSSL_FN
is obtained.  They never decrease, reflecting the worst-case simultaneous
allocation across all nested frames.

Assisted-by: Pi:moonshotai/kimi-k2.6
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Sun Jun 14 08:40:38 2026
(Merged from https://github.com/openssl/openssl/pull/31431)
2026-07-14 16:13:07 +02:00
Richard Levitte
277448082d Document OSSL_FN_copy zero-padding of oversized destination limbs
The OSSL_FN_copy() function already zeroes any high destination limbs
beyond the source size, but this was not stated in the public header.
Add an explicit note so callers know unused high bytes are cleared.

Assisted-by: Pi:moonshotai/kimi-k2.6
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Sun Jun 14 08:35:06 2026
(Merged from https://github.com/openssl/openssl/pull/31452)
2026-07-14 16:13:07 +02:00
Richard Levitte
96609d570a Add OSSL_FN_BITS, use it instead of 'OSSL_FN_BYTES * 8'
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Sun Jun 14 08:35:04 2026
(Merged from https://github.com/openssl/openssl/pull/31452)
2026-07-14 16:13:07 +02:00
Richard Levitte
76f93f270e Acquire BN_sqr result before ctx sizing
Acquire the writable result before reading the operand dmax value for the
OSSL_FN_CTX arena size. This avoids stale context sizing when the result
aliases the operand and acquisition expands that BIGNUM.

Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Fri Jun 12 15:16:36 2026
(Merged from https://github.com/openssl/openssl/pull/31448)
2026-07-14 16:13:07 +02:00
Richard Levitte
f03efd47b7 Acquire BN_mul result before ctx sizing
Acquire the writable result before reading operand dmax values for the
OSSL_FN_CTX arena size. This avoids stale context sizing when the result
aliases an operand and acquisition expands that BIGNUM.

Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Fri Jun 12 15:16:34 2026
(Merged from https://github.com/openssl/openssl/pull/31448)
2026-07-14 16:13:07 +02:00
Richard Levitte
54f13a2ef7 Use top to size BN add/sub results
Size the acquired result OSSL_FN from the significant operand limbs instead of
the allocated operand width. This keeps BN_uadd() and BN_usub() consistent
with the other wrappers that already use top for result sizing.

This follows a realization that applying OSSL_FN semantics onto BIGNUM too much
is pointless, and as demonstrated with BN_mul(), may be harmful, especially when
the result BIGNUM as the same as one of the operands.

Assisted-by: Pi:openai/gpt-5.5
Signed-off-by: Richard Levitte <levitte@openssl.foundation>

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Igor Ustinov <igus@openssl.foundation>
MergeDate: Fri Jun 12 15:16:33 2026
(Merged from https://github.com/openssl/openssl/pull/31448)
2026-07-14 16:13:07 +02:00
Igor Ustinov
f53cc8e213 Add an ownership token for OSSL_FN_CTX frames
This prevents ending a frame that wasn't started by the caller.

Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Thu Apr 30 09:18:52 2026
(Merged from https://github.com/openssl/openssl/pull/30886)
2026-07-14 16:13:07 +02:00
Igor Ustinov
17c4d15971 Some bugfixes for OSSL_FN
- Fixed the stage 2 exit criteria in OSSL_FN_add() and OSSL_FN_sub().
- Fixed the calculation of a-b when a is shorter than b and borrow==1
  after stage 1.
- Added sanity check in OSSL_FN_CTX_end().
- Do not set the is_dynamically_allocated flag when getting a
  new OSSL_FN from OSSL_FN_CTX.
- Added units of measurement to the error message in the OSSL_FN_copy()
  function.
- is_dynamically_allocated and is_securely_allocated shouldn't be
  copied in OSSL_BN_copy().
- Fixed the comment in OSSL_FN_add().
- Added a comment to bn_mul_truncated() that it is a duty of the calling
  function to zero out the rest of r in case of rn>an+bn.

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Richard Levitte <levitte@openssl.org>
MergeDate: Fri Apr 17 16:02:44 2026
(Merged from https://github.com/openssl/openssl/pull/30833)
2026-07-14 16:13:07 +02:00
Igor Ustinov
7d5084df86 Added OSSL_FN_truncate() function, which allows to copy a longer number
to a shorter one, truncating the high bytes.

Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Richard Levitte <levitte@openssl.org>
MergeDate: Fri Apr 17 14:09:28 2026
(Merged from https://github.com/openssl/openssl/pull/30846)
2026-06-19 10:31:49 +02:00
Richard Levitte
f52bf9d167 OSSL_FN: Wrap the BIGNUM 'sqr' function around the OSSL_FN 'sqr' function
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Fri Apr  3 12:32:22 2026
(Merged from https://github.com/openssl/openssl/pull/29819)
2026-06-19 10:31:49 +02:00
Richard Levitte
929db7822d OSSL_FN: Add the 'sqr' function
OSSL_FN_sqr() takes the square of on operand.  The result OSSL_FN may be of
any size, and if it's smaller than the result of squaring the operand, the
result is truncated to that size.

This also adds OSSL_FN_sqr() testcases in test/fn_api_test.c

Related-to: doc/designs/fixed-size-large-numbers.md
Issue: https://github.com/openssl/project/issues/1654

Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Fri Apr  3 12:32:20 2026
(Merged from https://github.com/openssl/openssl/pull/29819)
2026-06-19 10:31:49 +02:00
Richard Levitte
7e7280f1b2 Small fixups of things that somehow passed through our tests
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Fri Apr  3 12:32:17 2026
(Merged from https://github.com/openssl/openssl/pull/29819)
2026-06-19 10:31:49 +02:00
Richard Levitte
ded391d6ce OSSL_FN: Refactor OSSL_FN_add() and OSSL_FN_sub() for truncation
OSSL_FN_mul() set a path that wasn't considered for OSSL_FN_add() and
OSSL_FN_sub(); a truncated result if the result OSSL_FN isn't large
enough to contain the full result.

This is done to keep the OSSL_FN API consistent, with a (tentative)
bonus, that the function calls become more constant time accross
repeated calls with the same size for operands and result.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29309)
2026-06-19 10:31:49 +02:00
Richard Levitte
ab79f54114 OSSL_FN: Add the OSSL_FN_ typedefs to .clang-format's TypeNames
Furthermore, reformatting left some weird commenting in fn_api_test.c
because of (legitimate) line breaks, so they get adjust for purpose.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29412)
2026-06-19 10:31:49 +02:00
Richard Levitte
8f77f825fa OSSL_FN: Wrap BIGNUM the 'mul' function around the OSSL_FN 'mul' function
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29203)
2026-06-19 10:31:49 +02:00
Richard Levitte
5ea9b46e95 OSSL_FN: Add the 'mul' function
OSSL_FN_mul() multiplies two operands.  The result OSSL_FN may be of any
size, and if it's smaller than the result of multiplying the two operands,
the result is truncated to that size.

This also adds the function OSSL_FN_copy(), a counterpart for BN_copy(),
as well as an OSSL_FN API test program.

Finally, test/fn_api_test.c is updated with a 'struct test_case_st', used
to instruct test functions what numbers to operate on, and some conditions,
and the 'add' and 'sub' test functions are upgraded to use that structure
for their input.

Related-to: doc/designs/fixed-size-large-numbers.md
Resolves: https://github.com/openssl/openssl/issues/28934

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29203)
2026-06-19 10:31:49 +02:00
Richard Levitte
12e7939872 Add ossl_num_bits(), which returns the significant number of bits in a size_t
This existed as an isolated static function in crypto/asn1/x_long.c, but
is really a pretty generic integer function, so it will serve better by
being exactly that.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29203)
2026-06-19 10:31:49 +02:00
Richard Levitte
2532cdf821 OSSL_FN: constify some of the internal construction and introspection functions
This should have been done when they were added.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29203)
2026-06-19 10:31:49 +02:00
Richard Levitte
74ebdc1535 BIGNUM: add internal function 'bn_mul_truncated'
This is a variant of 'bn_mul_normal' that allows the result array to
have a smaller size than otherwise expected.

'bn_mul_normal' is refactored to be a wrapper around 'bn_mul_truncated',
with an assumed result size (which is the sum of the operands' sizes).

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29203)
2026-06-19 10:31:49 +02:00
Richard Levitte
504fb345af BIGNUM: separate out word-only helper functions from bn_mul.c
This separation will allow us to use the word-only helper functions
from OSSL_FN functions without pulling in functions that operate on
BIGNUMs.

This also starts the collection of source files with word-only BN
functions that haven't found their way into bn_asm.c for various
reasons.
To recognise them, they are prefixed 'bnw_' instead of 'bn_'.

While at it, consitification is done where appropriate among words
helpers.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29203)
2026-06-19 10:31:49 +02:00
Richard Levitte
cf738674e1 Add the fixed number context (OSSL_FN_CTX) with (de)allocators and tests
OSSL_FN_CTX is a reimplementation of BN_CTX, with the intent to have
good enough API parity.

The distinguishing feature with OSSL_FN_CTX is that it works as an arena
allocator, so it must be allocated with an estimate of how much memory
space will be needed for all OSSL_FN instances that will be retrieved
from that arena, and a bit of overhead.

Related-to: doc/designs/fixed-size-large-numbers.md
Resolves: https://github.com/openssl/openssl/issues/28933

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29161)
2026-06-19 10:31:49 +02:00
Richard Levitte
e148c8a233 BIGNUM: fix setting of 'top' in 'bn_mul_fixed_top'
When 'bn_set_top()' was applied, a couple of spots were missed.  This
cause some test failures.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29028)
2026-06-19 10:31:49 +02:00
Richard Levitte
e3ae863a2d OSSL_FN: Wrap BIGNUM 'add' and 'sub' functions around OSSL_FN functions
This involves the following functions:

- 'BN_add'
- 'BN_sub'
- 'BN_uadd'
- 'BN_usub'

Care is taken to use the previous (now legacy) code if any of the argument
BIGNUMs isn't backed by an OSSL_FN (i.e. if its 'data' field is NULL).

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29028)
2026-06-19 10:31:49 +02:00
Richard Levitte
1bde024e9b OSSL_FN: Add internal functions to acquire the OSSL_FN from a BIGNUM
bn_acquire_ossl_fn() returns the OSSL_FN of a BIGNUM if there is one,
expanded to a given number of limbs.
bn_release() makes necessary adjustments to the BIGNUM after a run of
OSSL_FN operations on the OSSL instance it's backed by.

These two functions are most useful with BIGNUMs used to store operation
results, and are meant to help refactoring BN operators to be wrappers
around corresponding OSSL_FN operators.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29028)
2026-06-19 10:31:49 +02:00
Richard Levitte
23fdb656c3 OSSL_FN: Add 'add' and 'sub' functions
This also introduces 'cmp' and 'ucmp' functions, as well as an OSSL_FN
API test program.

OSSL_FNs must not be polluted, so if a BIGNUM has a non-NULL 'data'
field, bn_pollute() will not pollute it.  It may be a good idea, though,
to pollute an OSSL_FN before an operation result is written to it, for
testing purposes.

Related-to: doc/designs/fixed-size-large-numbers.md
Resolves: https://github.com/openssl/openssl/issues/28932

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29028)
2026-06-19 10:31:49 +02:00
Richard Levitte
96dc6a9ac8 OSSL_FN: Add internal construction and introspection functions
These functions will be useful with other test programs without having
to include crypto/fn/fn_local.h, making them closer to real world use.

This also introduces OSSL_FN errors

Related-to: doc/designs/fixed-size-large-numbers.md

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29028)
2026-06-19 10:31:49 +02:00
Richard Levitte
133420b172 BN_DEBUG: Refactor 'bn_check_top', 'bn_wcheck_size' into static inline functions
All the BN_DEBUG code is also moved below the definition of 'struct bignum_st',
to keep the compilers happy.

Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29015)
2026-06-19 10:31:49 +02:00
Richard Levitte
2ccd86a0b1 BIGNUM: Adjust the requirements on 'top' and the 'd' array for OSSL_FN compat
BIGNUM is quite sloppy with its contents of the 'd' array above 'top'.  This
has been further exasperated by the 'bn_pollute' macro, which makes that slop
quite explicit.

That's fine within a purely BIGNUM context.

Enter OSSL_FN, which requires that the whole 'd' array is numerically
consistent, not just the BN_ULONGs up to 'top'.

This will, of course, cause trouble as soon as an OSSL_FN that's integrated
in a BIGNUM gets passed to OSSL_FN functions.

To ensure consistency, the following updates are made:

- [only for BIGNUMs in which 'data' is non-NULL] when decreasing 'top', all
  BN_ULONGs between the preceding 'top' and the new 'top' must be made zero.
- Drop bn_pollute() entirely, as it's now more harmful than useful.
- Modify bn_check_top() to better check the consistency of BIGNUM with
  integrated OSSL_FN, by checking that the part of the 'd' array between
  'top' and 'dmax' is all zeroes.
- Add the function 'bn_set_top()', which is recommended to use instead of
  assigning 'top' directly, as it will zeroise the intermediary limbs in
  the 'd' array when 'top' decreases.

On using 'bn_set_top()', it's highly recommended to use it everywhere,
unless you can be absolutely sure that the BIGNUM that's modified will never
be checked with 'bn_check_top()' or passed to any OSSL_FN function.

Related-to: doc/designs/fixed-size-large-numbers.md

Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29015)
2026-06-19 10:31:49 +02:00
Richard Levitte
2a691a034e First integration of OSSL_FN into BIGNUM
This integration is made in such a way that OSSL_FN is an optional
'data' field in BIGNUM, i.e. it's allowed to be NULL even though
the BIGNUM's 'd' field is non-NULL.

The public BIGNUM API will do what it can to ensure that the 'data'
field becomes non-NULL, but remains lax on input BIGNUMs, for now.
This allows diverse internal bn constants and hacks to continue to
function with minimal friction.  These constants and hacks will
incrementally be modified to use OSSL_FN where they currently use
BN_ULONG.

Related-to: doc/designs/fixed-size-large-numbers.md
Resolves: https://github.com/openssl/openssl/issues/28931

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28930)
2026-06-19 10:31:49 +02:00
Richard Levitte
584e7138af Add the fixed number type (OSSL_FN) and its allocators and deallocator
This includes a small test program that performs introspection of the
OSSL_FN, to check that diverse functions do what's expected of them.

For future compatibility reasons, the limb type OSSL_FN_ULONG is based
on BN_ULONG.  This caused a slight rearrangement of public BIGNUM related
headers.

Note: experiments with changing the current BIGNUM's 'dmax' and 'top' to be
"size_t" has shown disastrous effects, due to some lower level functions
assuming that they'll receive the size in "int" form rather than "size_t"
form (on some major platforms, these two types have different sizes).
Therefore, this change deviates slightly from the design for fixed numbers
(doc/designs/fixed-size-large-numbers.md) by making OSSL_FN's 'dsize' an
"int" rather than a "size_t".

Related-to: doc/designs/fixed-size-large-numbers.md
Resolves: https://github.com/openssl/project/issues/1649

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28784)
2026-06-19 10:31:49 +02:00
Richard Levitte
a5b9c8d60f Fix spelling in doc/designs/fixed-size-large-numbers.md
Found by codespell.

payed → paid

Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
(Merged from https://github.com/openssl/openssl/pull/28787)
2026-06-19 10:03:29 +02:00
Richard Levitte
8e4c928280 design: Fixed size large numbers
For the longest time, we have mitigated security issues related to large
numbers (BIGNUM) and constant time in a piece-meal fashion, without really
looking at the problem from a zoomed out, holistic perspective.

An interesting aspect in this problem is that large numbers can vary in
size, and that depending on their combined sizes, the time to perform
mathematical calculations with them vary equally much, and may thereby
unintentionally leak information on those numbers.

To mitigate that sort of timing issue, we introduce fixed size numbers,
which are designed to have payload sizes that are pre-determined, usually by
the crypto system that uses them.  This means that even a very small number
(let's take 1 as a ridiculous example) would have the same size payload as a
much larger number, and calculations using them would perform across all
payload bits of all input numbers combined.

These fixed size numbers primarly differ from BIGNUMs in that once they have
been allocated to a certain size, that size will not change throughout its
lifetime.

Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28522)
2026-06-19 10:03:29 +02:00
67 changed files with 79786 additions and 933 deletions

View file

@ -599,6 +599,8 @@ TypeNames:
- "ossl_finish_mutate_cb"
- "OSSL_FIPS_IND"
- "OSSL_FIPS_IND_CHECK_CB"
- "OSSL_FN_CTX"
- "OSSL_FN_ULONG"
- "OSSL_FUNC"
- "OSSL_HANDSHAKE_STATE"
- "OSSL_HASH"

View file

@ -8,8 +8,8 @@
*/
#include <stdio.h>
#include "internal/cryptlib.h"
#include <openssl/asn1t.h>
#include "crypto/cryptlib.h"
#define COPY_SIZE(a, b) (sizeof(a) < sizeof(b) ? sizeof(a) : sizeof(b))
@ -56,31 +56,6 @@ static void long_free(ASN1_VALUE **pval, const ASN1_ITEM *it)
memcpy(pval, &it->size, COPY_SIZE(*pval, it->size));
}
/*
* Originally BN_num_bits_word was called to perform this operation, but
* trouble is that there is no guarantee that sizeof(long) equals to
* sizeof(BN_ULONG). BN_ULONG is a configurable type that can be as wide
* as long, but also double or half...
*/
static int num_bits_ulong(unsigned long value)
{
size_t i;
unsigned long ret = 0;
/*
* It is argued that *on average* constant counter loop performs
* not worse [if not better] than one with conditional break or
* mask-n-table-lookup-style, because of branch misprediction
* penalties.
*/
for (i = 0; i < sizeof(value) * 8; i++) {
ret += (value != 0);
value >>= 1;
}
return (int)ret;
}
static int long_i2c(const ASN1_VALUE **pval, unsigned char *cont, int *putype,
const ASN1_ITEM *it)
{
@ -103,7 +78,7 @@ static int long_i2c(const ASN1_VALUE **pval, unsigned char *cont, int *putype,
sign = 0;
utmp = ltmp;
}
clen = num_bits_ulong(utmp);
clen = (int)ossl_num_bits(utmp);
/* If MSB of leading octet set we need to pad */
if (!(clen & 0x7))
pad = 1;

View file

@ -261,7 +261,7 @@ BN_ULONG bn_sub_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
#else
/* Simics 1.4<7 has buggy sbbq:-( */
#define BN_MASK2 0xffffffffffffffffL
BN_ULONG bn_sub_words(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n)
BN_ULONG bn_sub_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n)
{
BN_ULONG t1, t2;
int c = 0;
@ -408,7 +408,7 @@ BN_ULONG bn_sub_words(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n)
#define sqr_add_c2(a, i, j, c0, c1, c2) \
mul_add_c2((a)[i], (a)[j], c0, c1, c2)
void bn_mul_comba8(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
void bn_mul_comba8(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b)
{
BN_ULONG c1, c2, c3;
@ -511,7 +511,7 @@ void bn_mul_comba8(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
r[15] = c1;
}
void bn_mul_comba4(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
void bn_mul_comba4(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b)
{
BN_ULONG c1, c2, c3;

View file

@ -96,7 +96,7 @@ int BN_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)
if (bn_wexpand(r, max + 1) == NULL)
return 0;
r->top = max;
bn_set_top(r, max);
ap = a->d;
bp = b->d;
@ -114,7 +114,7 @@ int BN_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)
carry &= (t2 == 0);
}
*rp = carry;
r->top += (int)carry;
bn_set_top(r, r->top + (int)carry);
r->neg = 0;
bn_check_top(r);
@ -162,9 +162,8 @@ int BN_usub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)
while (max && *--rp == 0)
max--;
r->top = max;
bn_set_top(r, max);
r->neg = 0;
bn_pollute(r);
return 1;
}

View file

@ -622,7 +622,7 @@ BN_ULONG bn_sub_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
mul_add_c2((a)[i], (a)[j], c0, c1, c2)
#endif /* !BN_LLONG */
void bn_mul_comba8(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
void bn_mul_comba8(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b)
{
BN_ULONG c1, c2, c3;
@ -725,7 +725,7 @@ void bn_mul_comba8(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
r[15] = c1;
}
void bn_mul_comba4(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
void bn_mul_comba4(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b)
{
BN_ULONG c1, c2, c3;
@ -1006,7 +1006,7 @@ void bn_sqr_comba8(BN_ULONG *r, const BN_ULONG *a)
bn_sqr_normal(r, a, 8, t);
}
void bn_mul_comba4(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
void bn_mul_comba4(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b)
{
r[4] = bn_mul_words(&(r[0]), a, 4, b[0]);
r[5] = bn_mul_add_words(&(r[1]), a, 4, b[1]);
@ -1014,7 +1014,7 @@ void bn_mul_comba4(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
r[7] = bn_mul_add_words(&(r[3]), a, 4, b[3]);
}
void bn_mul_comba8(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b)
void bn_mul_comba8(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b)
{
r[8] = bn_mul_words(&(r[0]), a, 8, b[0]);
r[9] = bn_mul_add_words(&(r[1]), a, 8, b[1]);

View file

@ -185,7 +185,7 @@ int BN_BLINDING_invert_ex(BIGNUM *n, const BIGNUM *r, BN_BLINDING *b,
}
mask = (BN_ULONG)0 - ((rtop - ntop) >> (8 * sizeof(ntop) - 1));
/* always true, if (rtop >= ntop) n->top = r->top; */
n->top = (int)((rtop & ~mask) | (ntop & mask));
bn_set_top(n, (int)((rtop & ~mask) | (ntop & mask)));
n->flags |= (BN_FLG_FIXED_TOP & ~mask);
}
ret = bn_mul_mont_fixed_top(n, n, r, b->m_ctx, ctx);

View file

@ -178,7 +178,7 @@ int BN_hex2bn(BIGNUM **bn, const char *a)
}
j -= BN_BYTES * 2;
}
ret->top = h;
bn_set_top(ret, h);
bn_correct_top(ret);
*bn = ret;

View file

@ -1374,50 +1374,53 @@ static const BN_ULONG ffdhe8192_q[] = {
#define make_dh_bn(x) \
extern const BIGNUM ossl_bignum_##x; \
const BIGNUM ossl_bignum_##x = { \
(BN_ULONG *)x, \
OSSL_NELEM(x), \
OSSL_NELEM(x), \
0, BN_FLG_STATIC_DATA \
};
.d = (BN_ULONG *)x, \
.top = OSSL_NELEM(x), \
.dmax = OSSL_NELEM(x), \
.flags = BN_FLG_STATIC_DATA, \
}
static const BN_ULONG value_2 = 2;
const BIGNUM ossl_bignum_const_2 = {
(BN_ULONG *)&value_2, 1, 1, 0, BN_FLG_STATIC_DATA
.d = (BN_ULONG *)&value_2,
.top = 1,
.dmax = 1,
.flags = BN_FLG_STATIC_DATA,
};
make_dh_bn(dh1024_160_p)
make_dh_bn(dh1024_160_q)
make_dh_bn(dh1024_160_g)
make_dh_bn(dh2048_224_p)
make_dh_bn(dh2048_224_q)
make_dh_bn(dh2048_224_g)
make_dh_bn(dh2048_256_p)
make_dh_bn(dh2048_256_q)
make_dh_bn(dh2048_256_g)
make_dh_bn(dh1024_160_p);
make_dh_bn(dh1024_160_q);
make_dh_bn(dh1024_160_g);
make_dh_bn(dh2048_224_p);
make_dh_bn(dh2048_224_q);
make_dh_bn(dh2048_224_g);
make_dh_bn(dh2048_256_p);
make_dh_bn(dh2048_256_q);
make_dh_bn(dh2048_256_g);
make_dh_bn(ffdhe2048_p)
make_dh_bn(ffdhe2048_q)
make_dh_bn(ffdhe3072_p)
make_dh_bn(ffdhe3072_q)
make_dh_bn(ffdhe4096_p)
make_dh_bn(ffdhe4096_q)
make_dh_bn(ffdhe6144_p)
make_dh_bn(ffdhe6144_q)
make_dh_bn(ffdhe8192_p)
make_dh_bn(ffdhe8192_q)
make_dh_bn(ffdhe2048_p);
make_dh_bn(ffdhe2048_q);
make_dh_bn(ffdhe3072_p);
make_dh_bn(ffdhe3072_q);
make_dh_bn(ffdhe4096_p);
make_dh_bn(ffdhe4096_q);
make_dh_bn(ffdhe6144_p);
make_dh_bn(ffdhe6144_q);
make_dh_bn(ffdhe8192_p);
make_dh_bn(ffdhe8192_q);
#ifndef FIPS_MODULE
make_dh_bn(modp_1536_p)
make_dh_bn(modp_1536_q)
make_dh_bn(modp_1536_p);
make_dh_bn(modp_1536_q);
#endif
make_dh_bn(modp_2048_p)
make_dh_bn(modp_2048_q)
make_dh_bn(modp_3072_p)
make_dh_bn(modp_3072_q)
make_dh_bn(modp_4096_p)
make_dh_bn(modp_4096_q)
make_dh_bn(modp_6144_p)
make_dh_bn(modp_6144_q)
make_dh_bn(modp_8192_p)
make_dh_bn(modp_8192_q)
make_dh_bn(modp_2048_p);
make_dh_bn(modp_2048_q);
make_dh_bn(modp_3072_p);
make_dh_bn(modp_3072_q);
make_dh_bn(modp_4096_p);
make_dh_bn(modp_4096_q);
make_dh_bn(modp_6144_p);
make_dh_bn(modp_6144_q);
make_dh_bn(modp_8192_p);
make_dh_bn(modp_8192_q);

View file

@ -61,7 +61,7 @@ int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, const BIGNUM *d,
BN_zero(dv);
if (bn_wexpand(dv, 1) == NULL)
goto end;
dv->top = 1;
bn_set_top(dv, 1);
if (!BN_lshift(D, D, nm - nd))
goto end;
@ -310,7 +310,9 @@ int bn_div_fixed_top(BIGNUM *dv, BIGNUM *rm, const BIGNUM *num,
if (bn_wexpand(snum, div_n + 1) == NULL)
goto err;
memset(&(snum->d[num_n]), 0, (div_n - num_n + 1) * sizeof(BN_ULONG));
snum->top = num_n = div_n + 1;
num_n = div_n + 1;
bn_set_top(snum, num_n);
snum->flags |= BN_FLG_FIXED_TOP;
}
loop = num_n - div_n;
@ -330,13 +332,15 @@ int bn_div_fixed_top(BIGNUM *dv, BIGNUM *rm, const BIGNUM *num,
goto err;
num_neg = num->neg;
res->neg = (num_neg ^ divisor->neg);
res->top = loop;
bn_set_top(res, loop);
res->flags |= BN_FLG_FIXED_TOP;
resp = &(res->d[loop]);
/* space for temp */
if (!bn_wexpand(tmp, (div_n + 1)))
goto err;
tmp->top = div_n + 1;
tmp->flags |= BN_FLG_FIXED_TOP;
for (i = 0; i < loop; i++, wnumtop--) {
BN_ULONG q, l0;
@ -446,7 +450,7 @@ int bn_div_fixed_top(BIGNUM *dv, BIGNUM *rm, const BIGNUM *num,
}
/* snum holds remainder, it's as wide as divisor */
snum->neg = num_neg;
snum->top = div_n;
bn_set_top(snum, div_n);
snum->flags |= BN_FLG_FIXED_TOP;
if (rm != NULL && bn_rshift_fixed_top(rm, snum, norm_shift) == 0)

View file

@ -402,7 +402,7 @@ int BN_mod_exp_mont(BIGNUM *rr, const BIGNUM *a, const BIGNUM *p,
r->d[0] = (0 - m->d[0]) & BN_MASK2;
for (i = 1; i < j; i++)
r->d[i] = (~m->d[i]) & BN_MASK2;
r->top = j;
bn_set_top(r, j);
r->flags |= BN_FLG_FIXED_TOP;
} else
#endif
@ -468,7 +468,8 @@ int BN_mod_exp_mont(BIGNUM *rr, const BIGNUM *a, const BIGNUM *p,
val[0]->d[0] = 1; /* borrow val[0] */
for (i = 1; i < j; i++)
val[0]->d[i] = 0;
val[0]->top = j;
bn_set_top(val[0], j);
val[0]->flags |= BN_FLG_FIXED_TOP;
if (!BN_mod_mul_montgomery(rr, r, val[0], mont, ctx))
goto err;
} else
@ -581,7 +582,7 @@ static int MOD_EXP_CTIME_COPY_FROM_PREBUF(BIGNUM *b, int top,
}
}
b->top = top;
bn_set_top(b, top);
b->flags |= BN_FLG_FIXED_TOP;
return 1;
}
@ -612,7 +613,7 @@ int bn_mod_exp_mont_fixed_top(BIGNUM *rr, const BIGNUM *a, const BIGNUM *p,
unsigned char *powerbufFree = NULL;
int powerbufLen = 0;
unsigned char *powerbuf = NULL;
BIGNUM tmp, am;
BIGNUM tmp = { .data = NULL }, am = { .data = NULL };
#if defined(SPARC_T4_MONT)
unsigned int t4 = 0;
#endif
@ -681,7 +682,7 @@ int bn_mod_exp_mont_fixed_top(BIGNUM *rr, const BIGNUM *a, const BIGNUM *p,
goto err;
RSAZ_1024_mod_exp_avx2(rr->d, a->d, p->d, m->d, mont->RR.d,
mont->n0[0]);
rr->top = 16;
bn_set_top(rr, 16);
rr->neg = 0;
bn_correct_top(rr);
ret = 1;
@ -690,7 +691,7 @@ int bn_mod_exp_mont_fixed_top(BIGNUM *rr, const BIGNUM *a, const BIGNUM *p,
if (NULL == bn_wexpand(rr, 8))
goto err;
RSAZ_512_mod_exp(rr->d, a->d, p->d, m->d, mont->n0[0], mont->RR.d);
rr->top = 8;
bn_set_top(rr, 8);
rr->neg = 0;
bn_correct_top(rr);
ret = 1;
@ -1488,12 +1489,12 @@ int BN_mod_exp_mont_consttime_x2(BIGNUM *rr1, const BIGNUM *a1, const BIGNUM *p1
mont2->RR.d, mont2->n0[0],
mod_bits);
rr1->top = topn;
bn_set_top(rr1, topn);
rr1->neg = 0;
bn_correct_top(rr1);
bn_check_top(rr1);
rr2->top = topn;
bn_set_top(rr2, topn);
rr2->neg = 0;
bn_correct_top(rr2);
bn_check_top(rr2);

View file

@ -267,7 +267,7 @@ int BN_GF2m_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)
r->d[i] = at->d[i];
}
r->top = at->top;
bn_set_top(r, at->top);
bn_correct_top(r);
return 1;
@ -305,7 +305,7 @@ int BN_GF2m_mod_arr(BIGNUM *r, const BIGNUM *a, const int p[])
for (j = 0; j < a->top; j++) {
r->d[j] = a->d[j];
}
r->top = a->top;
bn_set_top(r, a->top);
}
z = r->d;
@ -419,7 +419,7 @@ int BN_GF2m_mod_mul_arr(BIGNUM *r, const BIGNUM *a, const BIGNUM *b,
zlen = a->top + b->top + 4;
if (!bn_wexpand(s, zlen))
goto err;
s->top = zlen;
bn_set_top(s, zlen);
for (i = 0; i < zlen; i++)
s->d[i] = 0;
@ -498,7 +498,7 @@ int BN_GF2m_mod_sqr_arr(BIGNUM *r, const BIGNUM *a, const int p[],
s->d[2 * i] = SQR0(a->d[i]);
}
s->top = 2 * a->top;
bn_set_top(s, 2 * a->top);
bn_correct_top(s);
if (!BN_GF2m_mod_arr(r, s, p))
goto err;
@ -618,20 +618,20 @@ static int BN_GF2m_mod_inv_vartime(BIGNUM *r, const BIGNUM *a,
udp = u->d;
for (i = u->top; i < top; i++)
udp[i] = 0;
u->top = top;
bn_set_top(u, top);
if (!bn_wexpand(b, top))
goto err;
bdp = b->d;
bdp[0] = 1;
for (i = 1; i < top; i++)
bdp[i] = 0;
b->top = top;
bn_set_top(b, top);
if (!bn_wexpand(c, top))
goto err;
cdp = c->d;
for (i = 0; i < top; i++)
cdp[i] = 0;
c->top = top;
bn_set_top(c, top);
vdp = v->d; /* It pays off to "cache" *->d pointers,
* because it allows optimizer to be more
* aggressive. But we don't have to "cache"

View file

@ -174,7 +174,9 @@ void bn_set_static_words(BIGNUM *a, const BN_ULONG *words, int size)
* |const| qualifier omission is compensated by BN_FLG_STATIC_DATA
* flag, which effectively means "read-only data".
*/
a->data = NULL;
a->d = (BN_ULONG *)words;
/* No need to call bn_set_top() in this case */
a->dmax = a->top = size;
a->neg = 0;
a->flags |= BN_FLG_STATIC_DATA;
@ -188,8 +190,10 @@ int bn_set_words(BIGNUM *a, const BN_ULONG *words, int num_words)
return 0;
}
/* TODO(FIXNUM): In the future, we'll use an OSSL_FN function on a->data */
memcpy(a->d, words, sizeof(BN_ULONG) * num_words);
a->top = num_words;
bn_set_top(a, num_words);
bn_correct_top(a);
return 1;
}

View file

@ -9,11 +9,14 @@
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <openssl/bn.h>
#include <openssl/opensslconf.h>
#include "internal/cryptlib.h"
#include "internal/endian.h"
#include "bn_local.h"
#include <openssl/opensslconf.h>
#include "internal/constant_time.h"
#include "crypto/fn.h"
#include "bn_local.h"
/* This stuff appears to be completely unused, so is deprecated */
#ifndef OPENSSL_NO_DEPRECATED_0_9_8
@ -83,7 +86,10 @@ const BIGNUM *BN_value_one(void)
{
static const BN_ULONG data_one = 1L;
static const BIGNUM const_one = {
(BN_ULONG *)&data_one, 1, 1, 0, BN_FLG_STATIC_DATA
.d = (BN_ULONG *)&data_one,
.top = 1,
.dmax = 1,
.flags = BN_FLG_STATIC_DATA,
};
return &const_one;
@ -199,7 +205,7 @@ int BN_num_bits(const BIGNUM *a)
return ((i * BN_BITS2) + BN_num_bits_word(a->d[i]));
}
static void bn_free_d(BIGNUM *a, int clear)
static void bn_free_d(BIGNUM *a, bool clear)
{
if (BN_get_flags(a, BN_FLG_SECURE))
OPENSSL_secure_clear_free(a->d, a->dmax * sizeof(a->d[0]));
@ -213,8 +219,12 @@ void BN_clear_free(BIGNUM *a)
{
if (a == NULL)
return;
if (a->d != NULL && !BN_get_flags(a, BN_FLG_STATIC_DATA))
bn_free_d(a, 1);
if (!BN_get_flags(a, BN_FLG_STATIC_DATA)) {
if (a->data != NULL)
OSSL_FN_clear_free(a->data);
else
bn_free_d(a, true);
}
if (BN_get_flags(a, BN_FLG_MALLOCED)) {
OPENSSL_cleanse(a, sizeof(*a));
OPENSSL_free(a);
@ -225,8 +235,12 @@ void BN_free(BIGNUM *a)
{
if (a == NULL)
return;
if (!BN_get_flags(a, BN_FLG_STATIC_DATA))
bn_free_d(a, 0);
if (!BN_get_flags(a, BN_FLG_STATIC_DATA)) {
if (a->data != NULL)
OSSL_FN_free(a->data);
else
bn_free_d(a, false);
}
if (a->flags & BN_FLG_MALLOCED)
OPENSSL_free(a);
}
@ -261,11 +275,11 @@ BIGNUM *BN_secure_new(void)
/* This is used by bn_expand2() */
/* The caller MUST check that words > b->dmax before calling this */
static BN_ULONG *bn_expand_internal(const BIGNUM *b, int words)
static OSSL_FN *bn_expand_internal(const BIGNUM *b, int words)
{
BN_ULONG *a = NULL;
OSSL_FN *a = NULL;
if (ossl_unlikely(words > (INT_MAX / (4 * BN_BITS2)))) {
if (ossl_unlikely(words > BN_MAX_WORDS)) {
ERR_raise(ERR_LIB_BN, BN_R_BIGNUM_TOO_LONG);
return NULL;
}
@ -274,15 +288,19 @@ static BN_ULONG *bn_expand_internal(const BIGNUM *b, int words)
return NULL;
}
if (BN_get_flags(b, BN_FLG_SECURE))
a = OPENSSL_secure_calloc(words, sizeof(*a));
a = OSSL_FN_secure_new_limbs(words);
else
a = OPENSSL_calloc(words, sizeof(*a));
a = OSSL_FN_new_limbs(words);
if (ossl_unlikely(a == NULL))
return NULL;
assert(b->top <= words);
if (b->top > 0)
memcpy(a, b->d, sizeof(*a) * b->top);
if (b->top > 0) {
if (b->data != NULL)
ossl_fn_copy_internal(a, b->data, -1);
else if (b->d != NULL)
ossl_fn_copy_internal_limbs(a, b->d, b->top);
}
return a;
}
@ -298,19 +316,58 @@ static BN_ULONG *bn_expand_internal(const BIGNUM *b, int words)
BIGNUM *bn_expand2(BIGNUM *b, int words)
{
if (ossl_likely(words > b->dmax)) {
BN_ULONG *a = bn_expand_internal(b, words);
OSSL_FN *a = bn_expand_internal(b, words);
if (ossl_unlikely(!a))
return NULL;
if (b->d != NULL)
bn_free_d(b, 1);
b->d = a;
b->dmax = words;
if (b->data != NULL)
OSSL_FN_clear_free(b->data);
else if (b->d != NULL)
bn_free_d(b, true);
b->data = a;
/* TODO(FIXNUM) The following is TO BE REMOVED */
b->d = b->data->d;
b->dmax = b->data->dsize;
}
return b;
}
OSSL_FN *bn_acquire_ossl_fn(BIGNUM *b, int limbs)
{
if (ossl_unlikely(b == NULL))
return NULL;
if (bn_wexpand(b, limbs) == NULL)
return NULL;
/* TODO(FIXNUM): should we add a flag bit for this in b->flags ? */
return b->data;
}
void bn_release(BIGNUM *b, int limbs)
{
if (ossl_unlikely(b == NULL || b->data == NULL))
return;
int fixed_top = (b->flags & BN_FLG_FIXED_TOP) != 0;
bn_set_top(b, limbs);
/* Don't correct top if BN_FLG_FIXED_TOP was set */
if (fixed_top)
return;
bn_correct_top(b);
}
OSSL_FN *bn_get_ossl_fn(const BIGNUM *bn)
{
if (ossl_unlikely(bn == NULL))
return NULL;
return bn->data;
}
BIGNUM *BN_dup(const BIGNUM *a)
{
BIGNUM *t;
@ -343,11 +400,14 @@ BIGNUM *BN_copy(BIGNUM *a, const BIGNUM *b)
if (ossl_unlikely(bn_wexpand(a, bn_words) == NULL))
return NULL;
if (ossl_likely(b->top > 0))
memcpy(a->d, b->d, sizeof(b->d[0]) * bn_words);
if (ossl_likely(bn_words > 0)) {
if (b->data != NULL)
ossl_fn_copy_internal(a->data, b->data, bn_words);
else if (b->d != NULL)
ossl_fn_copy_internal_limbs(a->data, b->d, bn_words);
}
a->neg = b->neg;
a->top = b->top;
bn_set_top(a, b->top);
a->flags |= b->flags & BN_FLG_FIXED_TOP;
bn_check_top(a);
return a;
@ -359,6 +419,7 @@ BIGNUM *BN_copy(BIGNUM *a, const BIGNUM *b)
void BN_swap(BIGNUM *a, BIGNUM *b)
{
int flags_old_a, flags_old_b;
OSSL_FN *tmp_data;
BN_ULONG *tmp_d;
int tmp_top, tmp_dmax, tmp_neg;
@ -368,16 +429,19 @@ void BN_swap(BIGNUM *a, BIGNUM *b)
flags_old_a = a->flags;
flags_old_b = b->flags;
tmp_data = a->data;
tmp_d = a->d;
tmp_top = a->top;
tmp_dmax = a->dmax;
tmp_neg = a->neg;
a->data = b->data;
a->d = b->d;
a->top = b->top;
a->dmax = b->dmax;
a->neg = b->neg;
b->data = tmp_data;
b->d = tmp_d;
b->top = tmp_top;
b->dmax = tmp_dmax;
@ -394,10 +458,12 @@ void BN_clear(BIGNUM *a)
if (a == NULL)
return;
bn_check_top(a);
if (a->d != NULL)
if (a->data != NULL)
OSSL_FN_clear(a->data);
else if (a->d != NULL)
OPENSSL_cleanse(a->d, sizeof(*a->d) * a->dmax);
a->neg = 0;
a->top = 0;
bn_set_top(a, 0);
a->flags &= ~BN_FLG_FIXED_TOP;
}
@ -414,11 +480,11 @@ BN_ULONG BN_get_word(const BIGNUM *a)
int BN_set_word(BIGNUM *a, BN_ULONG w)
{
bn_check_top(a);
if (bn_expand(a, (int)sizeof(BN_ULONG) * 8) == NULL)
if (bn_wexpand(a, 1) == NULL)
return 0;
a->neg = 0;
a->d[0] = w;
a->top = (w ? 1 : 0);
bn_set_top(a, (w ? 1 : 0));
a->flags &= ~BN_FLG_FIXED_TOP;
bn_check_top(a);
return 1;
@ -500,7 +566,7 @@ static BIGNUM *bin2bn(const unsigned char *s, int len, BIGNUM *ret,
}
/* If it was all zeros, we're done */
if (len == 0) {
ret->top = 0;
bn_set_top(ret, 0);
return ret;
}
n = ((len - 1) / BN_BYTES) + 1; /* Number of resulting bignum chunks */
@ -508,7 +574,7 @@ static BIGNUM *bin2bn(const unsigned char *s, int len, BIGNUM *ret,
BN_free(bn);
return NULL;
}
ret->top = n;
bn_set_top(ret, n);
ret->neg = neg;
for (i = 0; n-- > 0; i++) {
BN_ULONG l = 0; /* Accumulator */
@ -789,7 +855,7 @@ int BN_cmp(const BIGNUM *a, const BIGNUM *b)
int BN_set_bit(BIGNUM *a, int n)
{
int i, j, k;
int i, j;
if (n < 0)
return 0;
@ -799,9 +865,12 @@ int BN_set_bit(BIGNUM *a, int n)
if (a->top <= i) {
if (bn_wexpand(a, i + 1) == NULL)
return 0;
for (k = a->top; k < i + 1; k++)
a->d[k] = 0;
a->top = i + 1;
/*
* If 'a' is actually expanded, we know that the expanded
* part of the 'd' array is zeroed during allocation, so
* no need to zero it again here.
*/
bn_set_top(a, i + 1);
a->flags &= ~BN_FLG_FIXED_TOP;
}
@ -854,9 +923,9 @@ int ossl_bn_mask_bits_fixed_top(BIGNUM *a, int n)
if (w >= a->top)
return 0;
if (b == 0)
a->top = w;
bn_set_top(a, w);
else {
a->top = w + 1;
bn_set_top(a, w + 1);
a->d[w] &= ~(BN_MASK2 << b);
}
a->flags |= BN_FLG_FIXED_TOP;
@ -1023,7 +1092,7 @@ int BN_security_bits(int L, int N)
void BN_zero_ex(BIGNUM *a)
{
a->neg = 0;
a->top = 0;
bn_set_top(a, 0);
a->flags &= ~BN_FLG_FIXED_TOP;
}
@ -1034,7 +1103,12 @@ int BN_abs_is_word(const BIGNUM *a, const BN_ULONG w)
int BN_is_zero(const BIGNUM *a)
{
return a->top == 0;
if ((a->flags & BN_FLG_FIXED_TOP) == 0)
return a->top == 0;
for (size_t i = a->top; i-- > 0;)
if (a->d[i] != (BN_ULONG)0)
return 0;
return 1;
}
int BN_is_one(const BIGNUM *a)
@ -1081,6 +1155,7 @@ int BN_to_montgomery(BIGNUM *r, const BIGNUM *a, BN_MONT_CTX *mont,
void BN_with_flags(BIGNUM *dest, const BIGNUM *b, int flags)
{
dest->data = b->data;
dest->d = b->d;
dest->top = b->top;
dest->dmax = b->dmax;
@ -1164,6 +1239,11 @@ void bn_correct_top_consttime(BIGNUM *a)
}
mask = constant_time_eq_int(atop, 0);
/*
* We just went through the whole 'd' array to identify where
* any leading set of zeros are located, so there's no need to
* call bn_set_top() here.
*/
a->top = atop;
a->neg = constant_time_select_int(mask, 0, a->neg);
a->flags &= ~BN_FLG_FIXED_TOP;
@ -1180,10 +1260,14 @@ void bn_correct_top(BIGNUM *a)
if (*ftl != 0)
break;
}
/*
* We just verified that the BN_ULONGs between a->top and
* tmp_top are all zero, so there's no need to call
* bn_set_top() here.
*/
a->top = tmp_top;
}
if (a->top == 0)
a->neg = 0;
a->flags &= ~BN_FLG_FIXED_TOP;
bn_pollute(a);
}

View file

@ -10,27 +10,24 @@
#ifndef OSSL_CRYPTO_BN_LOCAL_H
#define OSSL_CRYPTO_BN_LOCAL_H
#include <assert.h>
#include <string.h>
#include <openssl/opensslconf.h>
#include "internal/cryptlib.h"
#include "internal/numbers.h"
#include "crypto/bn.h"
#include "../fn/fn_local.h"
/*
* These preprocessor symbols control various aspects of the bignum headers
* and library code. They're not defined by any "normal" configuration, as
* they are intended for development and testing purposes. NB: defining
* them can be useful for debugging application code as well as openssl
* itself. BN_DEBUG - turn on various debugging alterations to the bignum
* code BN_RAND_DEBUG - uses random poisoning of unused words to trip up
* mismanagement of bignum internals. Enable BN_RAND_DEBUG is known to
* break some of the OpenSSL tests.
* BN_RAND_DEBUG was historically used to poison unused words in bignum data,
* for integrity debugging purposes. This isn't done any more, but enabling
* BN_RAND_DEBUG also defined BN_DEBUG, which we preserve for the moment.
*/
#if defined(BN_RAND_DEBUG) && !defined(BN_DEBUG)
#define BN_DEBUG
#endif
#if defined(BN_RAND_DEBUG)
#include <openssl/rand.h>
#endif
/*
* This should limit the stack usage due to alloca to about 4K.
@ -120,32 +117,65 @@ typedef unsigned long long BN_ULLONG;
#define BN_DEC_FMT2 "%09u"
#endif
#define BN_MAX_WORDS (INT_MAX / (4 * BN_BITS2))
BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, int num,
BN_ULONG w);
BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, int num, BN_ULONG w);
void bn_sqr_words(BN_ULONG *rp, const BN_ULONG *ap, int num);
BN_ULONG bn_div_words(BN_ULONG h, BN_ULONG l, BN_ULONG d);
BN_ULONG bn_add_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
int num);
BN_ULONG bn_sub_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
int num);
struct bignum_st {
/* The number itself is a FIXNUM */
OSSL_FN *data;
/* Some of these flags are replicated in OSSL_FN, some are not */
int flags;
BN_ULONG *d; /* Pointer to |data->d| */
int top; /* Index of last used d +1. */
/* The next are internal book keeping for bn_expand. */
int dmax; /* Copy of |data->dsize| */
int neg; /* One if the number is negative */
};
/*-
* Bignum consistency macros
*
* There is one "API" macro, bn_fix_top(), for stripping leading zeroes from
* bignum data after direct manipulations on the data. There is also an
* "internal" macro, bn_check_top(), for verifying that there are no leading
* zeroes. Unfortunately, some auditing is required due to the fact that
* bn_fix_top() has become an overabused duct-tape because bignum data is
* occasionally passed around in an inconsistent state. So the following
* changes have been made to sort this out;
* zeroes, and in case the BIGNUM has an integrated OSSL_FN, check the
* consistency of the integration, including that the unused part of the
* data is all zeros.
*
* Unfortunately, some auditing is required due to the fact that bn_fix_top()
* has become an overabused duck-tape because bignum data is occasionally
* passed around in an inconsistent state. So the following changes have been
* made to sort this out;
*
* - bn_fix_top()s implementation has been moved to bn_correct_top()
* - if BN_DEBUG isn't defined, bn_fix_top() maps to bn_correct_top(), and
* bn_check_top() is as before.
* - if BN_DEBUG *is* defined;
* - bn_check_top() tries to pollute unused words even if the bignum 'top' is
* consistent. (ed: only if BN_RAND_DEBUG is defined)
* - if BN_DEBUG isn't defined:
* - bn_check_top() does nothing.
* - bn_fix_top() maps to bn_correct_top()
* - if BN_DEBUG is defined:
* - bn_check_top() performs its consistency checks
* - bn_fix_top() maps to bn_check_top() rather than "fixing" anything.
*
* The idea is to have debug builds flag up inconsistent bignums when they
* occur. If that occurs in a bn_fix_top(), we examine the code in question; if
* the use of bn_fix_top() was appropriate (ie. it follows directly after code
* that manipulates the bignum) it is converted to bn_correct_top(), and if it
* was not appropriate, we convert it permanently to bn_check_top() and track
* down the cause of the bug. Eventually, no internal code should be using the
* bn_fix_top() macro. External applications and libraries should try this with
* their own code too, both in terms of building against the openssl headers
* with BN_DEBUG defined *and* linking with a version of OpenSSL built with it
* defined. This not only improves external code, it provides more test
* occur. If that occurs in a bn_fix_top(), we examine the code in question;
* if the use of bn_fix_top() was appropriate (ie. it follows directly after
* code that manipulates the bignum) it is converted to bn_correct_top(),
* and if it was not appropriate, we convert it permanently to bn_check_top()
* and track down the cause of the bug. Eventually, no internal code should be
* using the bn_fix_top() macro. External applications and libraries should try
* this with their own code too, both in terms of building against the openssl
* headers with BN_DEBUG defined *and* linking with a version of OpenSSL built
* with it defined. This not only improves external code, it provides more test
* coverage for openssl's own code.
*/
@ -164,54 +194,49 @@ typedef unsigned long long BN_ULLONG;
* all operations manipulating the bit in question in non-BN_DEBUG build.
*/
#define BN_FLG_FIXED_TOP 0x10000
#ifdef BN_RAND_DEBUG
#define bn_pollute(a) \
do { \
const BIGNUM *_bnum1 = (a); \
if (_bnum1->top < _bnum1->dmax) { \
unsigned char _tmp_char; \
/* We cast away const without the compiler knowing, any \
* *genuinely* constant variables that aren't mutable \
* wouldn't be constructed with top!=dmax. */ \
BN_ULONG *_not_const; \
memcpy(&_not_const, &_bnum1->d, sizeof(_not_const)); \
(void)RAND_bytes(&_tmp_char, 1); /* Debug only - safe to ignore error return */ \
memset(_not_const + _bnum1->top, _tmp_char, \
sizeof(*_not_const) * (_bnum1->dmax - _bnum1->top)); \
} \
} while (0)
#else
#define bn_pollute(a)
#endif
#define bn_check_top(a) \
do { \
const BIGNUM *_bnum2 = (a); \
if (_bnum2 != NULL) { \
int _top = _bnum2->top; \
if (_top == 0) { \
assert(!_bnum2->neg); \
} else if ((_bnum2->flags & BN_FLG_FIXED_TOP) == 0) { \
assert(_bnum2->d[_top - 1] != 0); \
} \
bn_pollute(_bnum2); \
} \
} while (0)
static ossl_inline bool bn_check_zero(BN_ULONG *words, int num_words)
{
for (int i = 0; i < num_words; i++)
if (words[i] != 0)
return false;
return true;
}
static ossl_inline void bn_check_top(const BIGNUM *bn)
{
if (bn != NULL) {
/* BIGNUM <-> OSSL_FN compat checks */
if (bn->data != NULL) {
/* TODO(FIXNUM): Assertion for the future */
/* assert(_bnum2->d == NULL); */
assert(bn->d == bn->data->d);
assert(bn->dmax == bn->data->dsize);
assert(bn_check_zero(&bn->d[bn->top], bn->dmax - bn->top));
}
/* BIGNUM specific checks */
if (bn->top == 0) {
assert(!bn->neg);
} else if ((bn->flags & BN_FLG_FIXED_TOP) == 0) {
assert(bn->d[bn->top - 1] != 0);
}
assert(bn->dmax >= 0 && bn->dmax <= BN_MAX_WORDS);
}
}
#define bn_fix_top(a) bn_check_top(a)
static ossl_inline void bn_wcheck_size(const BIGNUM *bn, int words)
{
assert(words <= bn->dmax);
assert(words >= bn->top);
}
#define bn_check_size(bn, bits) bn_wcheck_size(bn, ((bits + BN_BITS2 - 1)) / BN_BITS2)
#define bn_wcheck_size(bn, words) \
do { \
const BIGNUM *_bnum2 = (bn); \
assert((words) <= (_bnum2)->dmax && (words) >= (_bnum2)->top); \
/* avoid unused variable warning with NDEBUG */ \
(void)(_bnum2); \
} while (0)
#else /* !BN_DEBUG */
#define BN_FLG_FIXED_TOP 0
#define bn_pollute(a)
#define bn_check_top(a)
#define bn_fix_top(a) bn_correct_top(a)
#define bn_check_size(bn, bits)
@ -219,29 +244,6 @@ typedef unsigned long long BN_ULLONG;
#endif
BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, int num,
BN_ULONG w);
BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, int num, BN_ULONG w);
void bn_sqr_words(BN_ULONG *rp, const BN_ULONG *ap, int num);
BN_ULONG bn_div_words(BN_ULONG h, BN_ULONG l, BN_ULONG d);
BN_ULONG bn_add_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
int num);
BN_ULONG bn_sub_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
int num);
struct bignum_st {
BN_ULONG *d; /*
* Pointer to an array of 'BN_BITS2' bit
* chunks. These chunks are organised in
* a least significant chunk first order.
*/
int top; /* Index of last used d +1. */
/* The next are internal book keeping for bn_expand. */
int dmax; /* Size of the d array. */
int neg; /* one if the number is negative */
int flags;
};
/* Used for montgomery multiplication */
struct bn_mont_ctx_st {
BIGNUM RR; /* used to convert to montgomery form,
@ -643,21 +645,21 @@ void BN_RECP_CTX_init(BN_RECP_CTX *recp);
void BN_MONT_CTX_init(BN_MONT_CTX *ctx);
void bn_init(BIGNUM *a);
void bn_mul_normal(BN_ULONG *r, BN_ULONG *a, int na, BN_ULONG *b, int nb);
void bn_mul_comba8(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b);
void bn_mul_comba4(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b);
void bn_mul_normal(BN_ULONG *r, const BN_ULONG *a, int na, const BN_ULONG *b, int nb);
void bn_mul_comba8(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b);
void bn_mul_comba4(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b);
void bn_sqr_normal(BN_ULONG *r, const BN_ULONG *a, int n, BN_ULONG *tmp);
void bn_sqr_comba8(BN_ULONG *r, const BN_ULONG *a);
void bn_sqr_comba4(BN_ULONG *r, const BN_ULONG *a);
int bn_cmp_words(const BN_ULONG *a, const BN_ULONG *b, int n);
int bn_cmp_part_words(const BN_ULONG *a, const BN_ULONG *b, int cl, int dl);
void bn_mul_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n2,
void bn_mul_recursive(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n2,
int dna, int dnb, BN_ULONG *t);
void bn_mul_part_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b,
void bn_mul_part_recursive(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
int n, int tna, int tnb, BN_ULONG *t);
void bn_sqr_recursive(BN_ULONG *r, const BN_ULONG *a, int n2, BN_ULONG *t);
void bn_mul_low_normal(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n);
void bn_mul_low_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n2,
void bn_mul_low_normal(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n);
void bn_mul_low_recursive(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n2,
BN_ULONG *t);
BN_ULONG bn_sub_part_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
int cl, int dl);
@ -667,6 +669,7 @@ void bn_correct_top_consttime(BIGNUM *a);
BIGNUM *int_bn_mod_inverse(BIGNUM *in,
const BIGNUM *a, const BIGNUM *n, BN_CTX *ctx,
int *noinv);
void bn_mul_truncated(BN_ULONG *r, int nr, const BN_ULONG *a, int na, const BN_ULONG *b, int nb);
static ossl_inline BIGNUM *bn_expand(BIGNUM *a, int bits)
{
@ -682,4 +685,29 @@ static ossl_inline BIGNUM *bn_expand(BIGNUM *a, int bits)
int ossl_bn_check_prime(const BIGNUM *w, int checks, BN_CTX *ctx,
int do_trial_division, BN_GENCB *cb);
/**
* Set top on a given BIGNUM. If it has an associated OSSL_FN (the 'data'
* field is non-NULL), and the new 'top' is less than the existing 'top',
* zeroise the space between them.
*
* @param[in] b The BIGNUM instance to zeroise
* @param[in] newtop The new 'top'
* @returns the new 'top'
* @pre b must not be NULL and newtop must be zero or positive
*/
static ossl_inline int bn_set_top(BIGNUM *b, int newtop)
{
assert(b != NULL && newtop >= 0);
if (b->data != NULL && newtop < b->top) {
BN_ULONG *start = &(b->d[newtop]);
size_t bytes = sizeof(BN_ULONG) * (b->top - newtop);
memset(start, 0, bytes);
}
b->top = newtop;
return b->top;
}
#endif

View file

@ -90,7 +90,7 @@ int bn_mod_add_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b,
rp[i] = (carry & tp[i]) | (~carry & rp[i]);
((volatile BN_ULONG *)tp)[i] = 0;
}
r->top = (int)mtop;
bn_set_top(r, (int)mtop);
r->flags |= BN_FLG_FIXED_TOP;
r->neg = 0;
@ -176,7 +176,7 @@ int bn_mod_sub_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b,
carry += (rp[i] < ta);
}
r->top = (int)mtop;
bn_set_top(r, (int)mtop);
r->flags |= BN_FLG_FIXED_TOP;
r->neg = 0;

View file

@ -46,7 +46,7 @@ int bn_mul_mont_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b,
return 0;
if (bn_mul_mont(r->d, a->d, b->d, mont->N.d, mont->n0, num)) {
r->neg = a->neg ^ b->neg;
r->top = num;
bn_set_top(r, num);
r->flags |= BN_FLG_FIXED_TOP;
return 1;
}
@ -94,7 +94,7 @@ static int bn_from_montgomery_word(BIGNUM *ret, BIGNUM *r, BN_MONT_CTX *mont)
n = &(mont->N);
nl = n->top;
if (nl == 0) {
ret->top = 0;
bn_set_top(ret, 0);
return 1;
}
@ -112,7 +112,7 @@ static int bn_from_montgomery_word(BIGNUM *ret, BIGNUM *r, BN_MONT_CTX *mont)
rp[i] &= v;
}
r->top = max;
bn_set_top(r, max);
r->flags |= BN_FLG_FIXED_TOP;
n0 = mont->n0[0];
@ -131,7 +131,7 @@ static int bn_from_montgomery_word(BIGNUM *ret, BIGNUM *r, BN_MONT_CTX *mont)
if (bn_wexpand(ret, nl) == NULL)
return 0;
ret->top = nl;
bn_set_top(ret, nl);
ret->flags |= BN_FLG_FIXED_TOP;
ret->neg = r->neg;
@ -325,7 +325,7 @@ int BN_MONT_CTX_set(BN_MONT_CTX *mont, const BIGNUM *mod, BN_CTX *ctx)
Ri->neg = 0;
Ri->d[0] = BN_MASK2;
Ri->d[1] = BN_MASK2;
Ri->top = 2;
bn_set_top(Ri, 2);
}
if (!BN_div(Ri, NULL, Ri, &tmod, ctx))
goto err;

View file

@ -65,7 +65,7 @@ BIGNUM *BN_mpi2bn(const unsigned char *d, int n, BIGNUM *ain)
if (len == 0) {
a->neg = 0;
a->top = 0;
bn_set_top(a, 0);
return a;
}
d += 4;

View file

@ -11,479 +11,6 @@
#include "internal/cryptlib.h"
#include "bn_local.h"
#if defined(OPENSSL_NO_ASM) || !defined(OPENSSL_BN_ASM_PART_WORDS)
/*
* Here follows specialised variants of bn_add_words() and bn_sub_words().
* They have the property performing operations on arrays of different sizes.
* The sizes of those arrays is expressed through cl, which is the common
* length ( basically, min(len(a),len(b)) ), and dl, which is the delta
* between the two lengths, calculated as len(a)-len(b). All lengths are the
* number of BN_ULONGs... For the operations that require a result array as
* parameter, it must have the length cl+abs(dl). These functions should
* probably end up in bn_asm.c as soon as there are assembler counterparts
* for the systems that use assembler files.
*/
BN_ULONG bn_sub_part_words(BN_ULONG *r,
const BN_ULONG *a, const BN_ULONG *b,
int cl, int dl)
{
BN_ULONG c, t;
assert(cl >= 0);
c = bn_sub_words(r, a, b, cl);
if (dl == 0)
return c;
r += cl;
a += cl;
b += cl;
if (dl < 0) {
for (;;) {
t = b[0];
r[0] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
t = b[1];
r[1] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
t = b[2];
r[2] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
t = b[3];
r[3] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
b += 4;
r += 4;
}
} else {
int save_dl = dl;
while (c) {
t = a[0];
r[0] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
t = a[1];
r[1] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
t = a[2];
r[2] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
t = a[3];
r[3] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
save_dl = dl;
a += 4;
r += 4;
}
if (dl > 0) {
if (save_dl > dl) {
switch (save_dl - dl) {
case 1:
r[1] = a[1];
if (--dl <= 0)
break;
/* fall through */
case 2:
r[2] = a[2];
if (--dl <= 0)
break;
/* fall through */
case 3:
r[3] = a[3];
if (--dl <= 0)
break;
}
a += 4;
r += 4;
}
}
if (dl > 0) {
for (;;) {
r[0] = a[0];
if (--dl <= 0)
break;
r[1] = a[1];
if (--dl <= 0)
break;
r[2] = a[2];
if (--dl <= 0)
break;
r[3] = a[3];
if (--dl <= 0)
break;
a += 4;
r += 4;
}
}
}
return c;
}
#endif
#ifndef OPENSSL_SMALL_FOOTPRINT
/*
* Karatsuba recursive multiplication algorithm (cf. Knuth, The Art of
* Computer Programming, Vol. 2)
*/
/*-
* r is 2*n2 words in size,
* a and b are both n2 words in size.
* n2 must be a power of 2.
* We multiply and return the result.
* t must be 2*n2 words in size
* We calculate
* a[0]*b[0]
* a[0]*b[0]+a[1]*b[1]+(a[0]-a[1])*(b[1]-b[0])
* a[1]*b[1]
*/
/* dnX may not be positive, but n2/2+dnX has to be */
void bn_mul_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n2,
int dna, int dnb, BN_ULONG *t)
{
int n = n2 / 2, c1, c2;
int tna = n + dna, tnb = n + dnb;
unsigned int neg, zero;
BN_ULONG ln, lo, *p;
/*
* Only call bn_mul_comba 8 if n2 == 8 and the two arrays are complete
* [steve]
*/
if (n2 == 8 && dna == 0 && dnb == 0) {
bn_mul_comba8(r, a, b);
return;
}
/* Else do normal multiply */
if (n2 < BN_MUL_RECURSIVE_SIZE_NORMAL) {
bn_mul_normal(r, a, n2 + dna, b, n2 + dnb);
if ((dna + dnb) < 0)
memset(&r[2 * n2 + dna + dnb], 0,
sizeof(BN_ULONG) * -(dna + dnb));
return;
}
/* r=(a[0]-a[1])*(b[1]-b[0]) */
c1 = bn_cmp_part_words(a, &(a[n]), tna, n - tna);
c2 = bn_cmp_part_words(&(b[n]), b, tnb, tnb - n);
zero = neg = 0;
switch (c1 * 3 + c2) {
case -4:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
break;
case -3:
zero = 1;
break;
case -2:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n); /* + */
neg = 1;
break;
case -1:
case 0:
case 1:
zero = 1;
break;
case 2:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna); /* + */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
neg = 1;
break;
case 3:
zero = 1;
break;
case 4:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna);
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n);
break;
}
if (n == 4 && dna == 0 && dnb == 0) { /* XXX: bn_mul_comba4 could take
* extra args to do this well */
if (!zero)
bn_mul_comba4(&(t[n2]), t, &(t[n]));
else
memset(&t[n2], 0, sizeof(*t) * 8);
bn_mul_comba4(r, a, b);
bn_mul_comba4(&(r[n2]), &(a[n]), &(b[n]));
} else if (n == 8 && dna == 0 && dnb == 0) { /* XXX: bn_mul_comba8 could
* take extra args to do
* this well */
if (!zero)
bn_mul_comba8(&(t[n2]), t, &(t[n]));
else
memset(&t[n2], 0, sizeof(*t) * 16);
bn_mul_comba8(r, a, b);
bn_mul_comba8(&(r[n2]), &(a[n]), &(b[n]));
} else {
p = &(t[n2 * 2]);
if (!zero)
bn_mul_recursive(&(t[n2]), t, &(t[n]), n, 0, 0, p);
else
memset(&t[n2], 0, sizeof(*t) * n2);
bn_mul_recursive(r, a, b, n, 0, 0, p);
bn_mul_recursive(&(r[n2]), &(a[n]), &(b[n]), n, dna, dnb, p);
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0]), c1 is the sign
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
*/
c1 = (int)(bn_add_words(t, r, &(r[n2]), n2));
if (neg) { /* if t[32] is negative */
c1 -= (int)(bn_sub_words(&(t[n2]), t, &(t[n2]), n2));
} else {
/* Might have a carry */
c1 += (int)(bn_add_words(&(t[n2]), &(t[n2]), t, n2));
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0])+(a[0]*b[0])+(a[1]*b[1])
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
* c1 holds the carry bits
*/
c1 += (int)(bn_add_words(&(r[n]), &(r[n]), &(t[n2]), n2));
if (c1) {
p = &(r[n + n2]);
lo = *p;
ln = (lo + c1) & BN_MASK2;
*p = ln;
/*
* The overflow will stop before we over write words we should not
* overwrite
*/
if (ln < (BN_ULONG)c1) {
do {
p++;
lo = *p;
ln = (lo + 1) & BN_MASK2;
*p = ln;
} while (ln == 0);
}
}
}
/*
* n+tn is the word length t needs to be n*4 is size, as does r
*/
/* tnX may not be negative but less than n */
void bn_mul_part_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n,
int tna, int tnb, BN_ULONG *t)
{
int i, j, n2 = n * 2;
int c1, c2, neg;
BN_ULONG ln, lo, *p;
if (n < 8) {
bn_mul_normal(r, a, n + tna, b, n + tnb);
return;
}
/* r=(a[0]-a[1])*(b[1]-b[0]) */
c1 = bn_cmp_part_words(a, &(a[n]), tna, n - tna);
c2 = bn_cmp_part_words(&(b[n]), b, tnb, tnb - n);
neg = 0;
switch (c1 * 3 + c2) {
case -4:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
break;
case -3:
case -2:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n); /* + */
neg = 1;
break;
case -1:
case 0:
case 1:
case 2:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna); /* + */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
neg = 1;
break;
case 3:
case 4:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna);
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n);
break;
}
/*
* The zero case isn't yet implemented here. The speedup would probably
* be negligible.
*/
#if 0
if (n == 4) {
bn_mul_comba4(&(t[n2]), t, &(t[n]));
bn_mul_comba4(r, a, b);
bn_mul_normal(&(r[n2]), &(a[n]), tn, &(b[n]), tn);
memset(&r[n2 + tn * 2], 0, sizeof(*r) * (n2 - tn * 2));
} else
#endif
if (n == 8) {
bn_mul_comba8(&(t[n2]), t, &(t[n]));
bn_mul_comba8(r, a, b);
bn_mul_normal(&(r[n2]), &(a[n]), tna, &(b[n]), tnb);
memset(&r[n2 + tna + tnb], 0, sizeof(*r) * (n2 - tna - tnb));
} else {
p = &(t[n2 * 2]);
bn_mul_recursive(&(t[n2]), t, &(t[n]), n, 0, 0, p);
bn_mul_recursive(r, a, b, n, 0, 0, p);
i = n / 2;
/*
* If there is only a bottom half to the number, just do it
*/
if (tna > tnb)
j = tna - i;
else
j = tnb - i;
if (j == 0) {
bn_mul_recursive(&(r[n2]), &(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
memset(&r[n2 + i * 2], 0, sizeof(*r) * (n2 - i * 2));
} else if (j > 0) { /* eg, n == 16, i == 8 and tn == 11 */
bn_mul_part_recursive(&(r[n2]), &(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
memset(&(r[n2 + tna + tnb]), 0,
sizeof(BN_ULONG) * (n2 - tna - tnb));
} else { /* (j < 0) eg, n == 16, i == 8 and tn == 5 */
memset(&r[n2], 0, sizeof(*r) * n2);
if (tna < BN_MUL_RECURSIVE_SIZE_NORMAL
&& tnb < BN_MUL_RECURSIVE_SIZE_NORMAL) {
bn_mul_normal(&(r[n2]), &(a[n]), tna, &(b[n]), tnb);
} else {
for (;;) {
i /= 2;
/*
* these simplified conditions work exclusively because
* difference between tna and tnb is 1 or 0
*/
if (i < tna || i < tnb) {
bn_mul_part_recursive(&(r[n2]),
&(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
break;
} else if (i == tna || i == tnb) {
bn_mul_recursive(&(r[n2]),
&(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
break;
}
}
}
}
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0]), c1 is the sign
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
*/
c1 = (int)(bn_add_words(t, r, &(r[n2]), n2));
if (neg) { /* if t[32] is negative */
c1 -= (int)(bn_sub_words(&(t[n2]), t, &(t[n2]), n2));
} else {
/* Might have a carry */
c1 += (int)(bn_add_words(&(t[n2]), &(t[n2]), t, n2));
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0])+(a[0]*b[0])+(a[1]*b[1])
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
* c1 holds the carry bits
*/
c1 += (int)(bn_add_words(&(r[n]), &(r[n]), &(t[n2]), n2));
if (c1) {
p = &(r[n + n2]);
lo = *p;
ln = (lo + c1) & BN_MASK2;
*p = ln;
/*
* The overflow will stop before we over write words we should not
* overwrite
*/
if (ln < (BN_ULONG)c1) {
do {
p++;
lo = *p;
ln = (lo + 1) & BN_MASK2;
*p = ln;
} while (ln == 0);
}
}
}
/*-
* a and b must be the same size, which is n2.
* r needs to be n2 words and t needs to be n2*2
*/
void bn_mul_low_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n2,
BN_ULONG *t)
{
int n = n2 / 2;
bn_mul_recursive(r, a, b, n, 0, 0, &(t[0]));
if (n >= BN_MUL_LOW_RECURSIVE_SIZE_NORMAL) {
bn_mul_low_recursive(&(t[0]), &(a[0]), &(b[n]), n, &(t[n2]));
bn_add_words(&(r[n]), &(r[n]), &(t[0]), n);
bn_mul_low_recursive(&(t[0]), &(a[n]), &(b[0]), n, &(t[n2]));
bn_add_words(&(r[n]), &(r[n]), &(t[0]), n);
} else {
bn_mul_low_normal(&(t[0]), &(a[0]), &(b[n]), n);
bn_mul_low_normal(&(t[n]), &(a[n]), &(b[0]), n);
bn_add_words(&(r[n]), &(r[n]), &(t[0]), n);
bn_add_words(&(r[n]), &(r[n]), &(t[n]), n);
}
}
#endif /* OPENSSL_SMALL_FOOTPRINT */
int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
{
int ret = bn_mul_fixed_top(r, a, b, ctx);
@ -501,8 +28,6 @@ int bn_mul_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
BIGNUM *rr;
#if !defined(OPENSSL_SMALL_FOOTPRINT)
int i;
BIGNUM *t = NULL;
int j = 0, k;
#endif
bn_check_top(a);
@ -533,7 +58,8 @@ int bn_mul_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
if (al == 4) {
if (bn_wexpand(rr, 8) == NULL)
goto err;
rr->top = 8;
rr->flags |= BN_FLG_FIXED_TOP;
bn_set_top(rr, 8);
bn_mul_comba4(rr->d, a->d, b->d);
goto end;
}
@ -541,7 +67,8 @@ int bn_mul_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
if (al == 8) {
if (bn_wexpand(rr, 16) == NULL)
goto err;
rr->top = 16;
rr->flags |= BN_FLG_FIXED_TOP;
bn_set_top(rr, 16);
bn_mul_comba8(rr->d, a->d, b->d);
goto end;
}
@ -549,6 +76,9 @@ int bn_mul_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
if ((al >= BN_MULL_SIZE_NORMAL) && (bl >= BN_MULL_SIZE_NORMAL)) {
if (i >= -1 && i <= 1) {
BIGNUM *t = NULL;
int j = 0, k;
/*
* Find out the power of two lower or equal to the longest of the
* two numbers
@ -568,26 +98,33 @@ int bn_mul_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
if (al > j || bl > j) {
if (bn_wexpand(t, k * 4) == NULL)
goto err;
t->top = k * 4;
t->flags |= BN_FLG_FIXED_TOP;
if (bn_wexpand(rr, k * 4) == NULL)
goto err;
bn_set_top(rr, k * 4);
rr->flags |= BN_FLG_FIXED_TOP;
bn_mul_part_recursive(rr->d, a->d, b->d,
j, al - j, bl - j, t->d);
} else { /* al <= j || bl <= j */
if (bn_wexpand(t, k * 2) == NULL)
goto err;
t->top = k * 2;
t->flags |= BN_FLG_FIXED_TOP;
if (bn_wexpand(rr, k * 2) == NULL)
goto err;
bn_set_top(rr, k * 2);
rr->flags |= BN_FLG_FIXED_TOP;
bn_mul_recursive(rr->d, a->d, b->d, j, al - j, bl - j, t->d);
}
rr->top = top;
bn_set_top(rr, top);
goto end;
}
}
#endif /* OPENSSL_SMALL_FOOTPRINT */
if (bn_wexpand(rr, top) == NULL)
goto err;
rr->top = top;
bn_set_top(rr, top);
bn_mul_normal(rr->d, a->d, al, b->d, bl);
#if !defined(OPENSSL_SMALL_FOOTPRINT)
@ -604,66 +141,3 @@ err:
BN_CTX_end(ctx);
return ret;
}
void bn_mul_normal(BN_ULONG *r, BN_ULONG *a, int na, BN_ULONG *b, int nb)
{
BN_ULONG *rr;
if (na < nb) {
int itmp;
BN_ULONG *ltmp;
itmp = na;
na = nb;
nb = itmp;
ltmp = a;
a = b;
b = ltmp;
}
rr = &(r[na]);
if (nb <= 0) {
(void)bn_mul_words(r, a, na, 0);
return;
} else
rr[0] = bn_mul_words(r, a, na, b[0]);
for (;;) {
if (--nb <= 0)
return;
rr[1] = bn_mul_add_words(&(r[1]), a, na, b[1]);
if (--nb <= 0)
return;
rr[2] = bn_mul_add_words(&(r[2]), a, na, b[2]);
if (--nb <= 0)
return;
rr[3] = bn_mul_add_words(&(r[3]), a, na, b[3]);
if (--nb <= 0)
return;
rr[4] = bn_mul_add_words(&(r[4]), a, na, b[4]);
rr += 4;
r += 4;
b += 4;
}
}
void bn_mul_low_normal(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n)
{
bn_mul_words(r, a, n, b[0]);
for (;;) {
if (--n <= 0)
return;
bn_mul_add_words(&(r[1]), a, n, b[1]);
if (--n <= 0)
return;
bn_mul_add_words(&(r[2]), a, n, b[2]);
if (--n <= 0)
return;
bn_mul_add_words(&(r[3]), a, n, b[3]);
if (--n <= 0)
return;
bn_mul_add_words(&(r[4]), a, n, b[4]);
r += 4;
b += 4;
}
}

View file

@ -184,43 +184,38 @@ static const BN_ULONG _nist_p_521_sqr[] = {
#endif
static const BIGNUM ossl_bignum_nist_p_192 = {
(BN_ULONG *)_nist_p_192[0],
BN_NIST_192_TOP,
BN_NIST_192_TOP,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_192[0],
.top = BN_NIST_192_TOP,
.dmax = BN_NIST_192_TOP,
.flags = BN_FLG_STATIC_DATA,
};
static const BIGNUM ossl_bignum_nist_p_224 = {
(BN_ULONG *)_nist_p_224[0],
BN_NIST_224_TOP,
BN_NIST_224_TOP,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_224[0],
.top = BN_NIST_224_TOP,
.dmax = BN_NIST_224_TOP,
.flags = BN_FLG_STATIC_DATA,
};
static const BIGNUM ossl_bignum_nist_p_256 = {
(BN_ULONG *)_nist_p_256[0],
BN_NIST_256_TOP,
BN_NIST_256_TOP,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_256[0],
.top = BN_NIST_256_TOP,
.dmax = BN_NIST_256_TOP,
.flags = BN_FLG_STATIC_DATA,
};
static const BIGNUM ossl_bignum_nist_p_384 = {
(BN_ULONG *)_nist_p_384[0],
BN_NIST_384_TOP,
BN_NIST_384_TOP,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_384[0],
.top = BN_NIST_384_TOP,
.dmax = BN_NIST_384_TOP,
.flags = BN_FLG_STATIC_DATA,
};
static const BIGNUM ossl_bignum_nist_p_521 = {
(BN_ULONG *)_nist_p_521,
BN_NIST_521_TOP,
BN_NIST_521_TOP,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_521,
.top = BN_NIST_521_TOP,
.dmax = BN_NIST_521_TOP,
.flags = BN_FLG_STATIC_DATA,
};
const BIGNUM *BN_get0_nist_prime_192(void)
@ -359,10 +354,10 @@ int BN_nist_mod_192(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
} buf;
BN_ULONG c_d[BN_NIST_192_TOP], *res;
static const BIGNUM ossl_bignum_nist_p_192_sqr = {
(BN_ULONG *)_nist_p_192_sqr,
OSSL_NELEM(_nist_p_192_sqr),
OSSL_NELEM(_nist_p_192_sqr),
0, BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_192_sqr,
.top = OSSL_NELEM(_nist_p_192_sqr),
.dmax = OSSL_NELEM(_nist_p_192_sqr),
.flags = BN_FLG_STATIC_DATA,
};
field = &ossl_bignum_nist_p_192; /* just to make sure */
@ -462,7 +457,7 @@ int BN_nist_mod_192(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
? r_d
: c_d;
nist_cp_bn(r_d, res, BN_NIST_192_TOP);
r->top = BN_NIST_192_TOP;
bn_set_top(r, BN_NIST_192_TOP);
bn_correct_top(r);
return 1;
@ -495,10 +490,10 @@ int BN_nist_mod_224(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
BN_ULONG c_d[BN_NIST_224_TOP], *res;
bn_addsub_f adjust;
static const BIGNUM ossl_bignum_nist_p_224_sqr = {
(BN_ULONG *)_nist_p_224_sqr,
OSSL_NELEM(_nist_p_224_sqr),
OSSL_NELEM(_nist_p_224_sqr),
0, BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_224_sqr,
.top = OSSL_NELEM(_nist_p_224_sqr),
.dmax = OSSL_NELEM(_nist_p_224_sqr),
.flags = BN_FLG_STATIC_DATA,
};
field = &ossl_bignum_nist_p_224; /* just to make sure */
@ -630,7 +625,7 @@ int BN_nist_mod_224(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
? r_d
: c_d;
nist_cp_bn(r_d, res, BN_NIST_224_TOP);
r->top = BN_NIST_224_TOP;
bn_set_top(r, BN_NIST_224_TOP);
bn_correct_top(r);
return 1;
@ -661,10 +656,10 @@ int BN_nist_mod_256(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
BN_ULONG c_d[BN_NIST_256_TOP], *res;
bn_addsub_f adjust;
static const BIGNUM ossl_bignum_nist_p_256_sqr = {
(BN_ULONG *)_nist_p_256_sqr,
OSSL_NELEM(_nist_p_256_sqr),
OSSL_NELEM(_nist_p_256_sqr),
0, BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_256_sqr,
.top = OSSL_NELEM(_nist_p_256_sqr),
.dmax = OSSL_NELEM(_nist_p_256_sqr),
.flags = BN_FLG_STATIC_DATA,
};
field = &ossl_bignum_nist_p_256; /* just to make sure */
@ -859,7 +854,7 @@ int BN_nist_mod_256(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
? r_d
: c_d;
nist_cp_bn(r_d, res, BN_NIST_256_TOP);
r->top = BN_NIST_256_TOP;
bn_set_top(r, BN_NIST_256_TOP);
bn_correct_top(r);
return 1;
@ -894,10 +889,10 @@ int BN_nist_mod_384(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
BN_ULONG c_d[BN_NIST_384_TOP], *res;
bn_addsub_f adjust;
static const BIGNUM ossl_bignum_nist_p_384_sqr = {
(BN_ULONG *)_nist_p_384_sqr,
OSSL_NELEM(_nist_p_384_sqr),
OSSL_NELEM(_nist_p_384_sqr),
0, BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_384_sqr,
.top = OSSL_NELEM(_nist_p_384_sqr),
.dmax = OSSL_NELEM(_nist_p_384_sqr),
.flags = BN_FLG_STATIC_DATA,
};
field = &ossl_bignum_nist_p_384; /* just to make sure */
@ -1126,7 +1121,7 @@ int BN_nist_mod_384(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
? r_d
: c_d;
nist_cp_bn(r_d, res, BN_NIST_384_TOP);
r->top = BN_NIST_384_TOP;
bn_set_top(r, BN_NIST_384_TOP);
bn_correct_top(r);
return 1;
@ -1142,10 +1137,10 @@ int BN_nist_mod_521(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
int top = a->top, i;
BN_ULONG *r_d, *a_d = a->d, t_d[BN_NIST_521_TOP], val, tmp, *res;
static const BIGNUM ossl_bignum_nist_p_521_sqr = {
(BN_ULONG *)_nist_p_521_sqr,
OSSL_NELEM(_nist_p_521_sqr),
OSSL_NELEM(_nist_p_521_sqr),
0, BN_FLG_STATIC_DATA
.d = (BN_ULONG *)_nist_p_521_sqr,
.top = OSSL_NELEM(_nist_p_521_sqr),
.dmax = OSSL_NELEM(_nist_p_521_sqr),
.flags = BN_FLG_STATIC_DATA,
};
field = &ossl_bignum_nist_p_521; /* just to make sure */
@ -1197,7 +1192,7 @@ int BN_nist_mod_521(BIGNUM *r, const BIGNUM *a, const BIGNUM *field,
? r_d
: t_d;
nist_cp_bn(r_d, res, BN_NIST_521_TOP);
r->top = BN_NIST_521_TOP;
bn_set_top(r, BN_NIST_521_TOP);
bn_correct_top(r);
return 1;

View file

@ -55,11 +55,10 @@ static const BN_ULONG small_prime_factors[] = {
#define BN_SMALL_PRIME_FACTORS_TOP OSSL_NELEM(small_prime_factors)
static const BIGNUM _bignum_small_prime_factors = {
(BN_ULONG *)small_prime_factors,
BN_SMALL_PRIME_FACTORS_TOP,
BN_SMALL_PRIME_FACTORS_TOP,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)small_prime_factors,
.top = BN_SMALL_PRIME_FACTORS_TOP,
.dmax = BN_SMALL_PRIME_FACTORS_TOP,
.flags = BN_FLG_STATIC_DATA,
};
const BIGNUM *ossl_bn_get0_small_factors(void)

View file

@ -41,11 +41,10 @@ static const BN_ULONG inv_sqrt_2_val[] = {
};
const BIGNUM ossl_bn_inv_sqrt_2 = {
(BN_ULONG *)inv_sqrt_2_val,
OSSL_NELEM(inv_sqrt_2_val),
OSSL_NELEM(inv_sqrt_2_val),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)inv_sqrt_2_val,
.top = OSSL_NELEM(inv_sqrt_2_val),
.dmax = OSSL_NELEM(inv_sqrt_2_val),
.flags = BN_FLG_STATIC_DATA,
};
/*

View file

@ -23,7 +23,7 @@ int BN_lshift1(BIGNUM *r, const BIGNUM *a)
r->neg = a->neg;
if (bn_wexpand(r, a->top + 1) == NULL)
return 0;
r->top = a->top;
bn_set_top(r, a->top);
} else {
if (bn_wexpand(r, a->top + 1) == NULL)
return 0;
@ -37,7 +37,7 @@ int BN_lshift1(BIGNUM *r, const BIGNUM *a)
c = t >> (BN_BITS2 - 1);
}
*rp = c;
r->top += (int)c;
bn_set_top(r, r->top + (int)c);
bn_check_top(r);
return 1;
}
@ -62,11 +62,10 @@ int BN_rshift1(BIGNUM *r, const BIGNUM *a)
r->neg = a->neg;
}
rp = r->d;
r->top = i;
t = ap[--i];
rp[i] = t >> 1;
c = t << (BN_BITS2 - 1);
r->top -= (t == 1);
bn_set_top(r, i + (t > 1));
while (i > 0) {
t = ap[--i];
rp[i] = ((t >> 1) & BN_MASK2) | c;
@ -141,7 +140,7 @@ int bn_lshift_fixed_top(BIGNUM *r, const BIGNUM *a, int n)
memset(r->d, 0, sizeof(*t) * nw);
r->neg = a->neg;
r->top = a->top + nw + 1;
bn_set_top(r, a->top + nw + 1);
r->flags |= BN_FLG_FIXED_TOP;
return 1;
@ -209,7 +208,7 @@ int bn_rshift_fixed_top(BIGNUM *r, const BIGNUM *a, int n)
t[i] = l >> rb;
r->neg = a->neg;
r->top = top;
bn_set_top(r, top);
r->flags |= BN_FLG_FIXED_TOP;
return 1;

View file

@ -34,7 +34,7 @@ int bn_sqr_fixed_top(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx)
al = a->top;
if (al <= 0) {
r->top = 0;
bn_set_top(r, 0);
r->neg = 0;
return 1;
}
@ -91,8 +91,12 @@ int bn_sqr_fixed_top(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx)
#endif
}
/* Ensure that tmp won't cause any trouble */
tmp->top = tmp->dmax;
tmp->flags |= BN_FLG_FIXED_TOP;
rr->neg = 0;
rr->top = max;
bn_set_top(rr, max);
rr->flags |= BN_FLG_FIXED_TOP;
if (r != rr && BN_copy(r, rr) == NULL)
goto err;
@ -100,7 +104,6 @@ int bn_sqr_fixed_top(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx)
ret = 1;
err:
bn_check_top(rr);
bn_check_top(tmp);
BN_CTX_end(ctx);
return ret;
}

View file

@ -49,11 +49,10 @@ static const BN_ULONG bn_group_1024_value[] = {
};
const BIGNUM ossl_bn_group_1024 = {
(BN_ULONG *)bn_group_1024_value,
OSSL_NELEM(bn_group_1024_value),
OSSL_NELEM(bn_group_1024_value),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_group_1024_value,
.top = OSSL_NELEM(bn_group_1024_value),
.dmax = OSSL_NELEM(bn_group_1024_value),
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_group_1536_value[] = {
@ -84,11 +83,10 @@ static const BN_ULONG bn_group_1536_value[] = {
};
const BIGNUM ossl_bn_group_1536 = {
(BN_ULONG *)bn_group_1536_value,
OSSL_NELEM(bn_group_1536_value),
OSSL_NELEM(bn_group_1536_value),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_group_1536_value,
.top = OSSL_NELEM(bn_group_1536_value),
.dmax = OSSL_NELEM(bn_group_1536_value),
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_group_2048_value[] = {
@ -127,11 +125,10 @@ static const BN_ULONG bn_group_2048_value[] = {
};
const BIGNUM ossl_bn_group_2048 = {
(BN_ULONG *)bn_group_2048_value,
OSSL_NELEM(bn_group_2048_value),
OSSL_NELEM(bn_group_2048_value),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_group_2048_value,
.top = OSSL_NELEM(bn_group_2048_value),
.dmax = OSSL_NELEM(bn_group_2048_value),
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_group_3072_value[] = {
@ -186,11 +183,10 @@ static const BN_ULONG bn_group_3072_value[] = {
};
const BIGNUM ossl_bn_group_3072 = {
(BN_ULONG *)bn_group_3072_value,
OSSL_NELEM(bn_group_3072_value),
OSSL_NELEM(bn_group_3072_value),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_group_3072_value,
.top = OSSL_NELEM(bn_group_3072_value),
.dmax = OSSL_NELEM(bn_group_3072_value),
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_group_4096_value[] = {
@ -261,11 +257,10 @@ static const BN_ULONG bn_group_4096_value[] = {
};
const BIGNUM ossl_bn_group_4096 = {
(BN_ULONG *)bn_group_4096_value,
OSSL_NELEM(bn_group_4096_value),
OSSL_NELEM(bn_group_4096_value),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_group_4096_value,
.top = OSSL_NELEM(bn_group_4096_value),
.dmax = OSSL_NELEM(bn_group_4096_value),
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_group_6144_value[] = {
@ -368,11 +363,10 @@ static const BN_ULONG bn_group_6144_value[] = {
};
const BIGNUM ossl_bn_group_6144 = {
(BN_ULONG *)bn_group_6144_value,
OSSL_NELEM(bn_group_6144_value),
OSSL_NELEM(bn_group_6144_value),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_group_6144_value,
.top = OSSL_NELEM(bn_group_6144_value),
.dmax = OSSL_NELEM(bn_group_6144_value),
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_group_8192_value[] = {
@ -507,39 +501,35 @@ static const BN_ULONG bn_group_8192_value[] = {
};
const BIGNUM ossl_bn_group_8192 = {
(BN_ULONG *)bn_group_8192_value,
OSSL_NELEM(bn_group_8192_value),
OSSL_NELEM(bn_group_8192_value),
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_group_8192_value,
.top = OSSL_NELEM(bn_group_8192_value),
.dmax = OSSL_NELEM(bn_group_8192_value),
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_generator_19_value[] = { 19 };
const BIGNUM ossl_bn_generator_19 = {
(BN_ULONG *)bn_generator_19_value,
1,
1,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_generator_19_value,
.top = 1,
.dmax = 1,
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_generator_5_value[] = { 5 };
const BIGNUM ossl_bn_generator_5 = {
(BN_ULONG *)bn_generator_5_value,
1,
1,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_generator_5_value,
.top = 1,
.dmax = 1,
.flags = BN_FLG_STATIC_DATA,
};
static const BN_ULONG bn_generator_2_value[] = { 2 };
const BIGNUM ossl_bn_generator_2 = {
(BN_ULONG *)bn_generator_2_value,
1,
1,
0,
BN_FLG_STATIC_DATA
.d = (BN_ULONG *)bn_generator_2_value,
.top = 1,
.dmax = 1,
.flags = BN_FLG_STATIC_DATA,
};
#endif

407
crypto/bn/bnw_mul.c Normal file
View file

@ -0,0 +1,407 @@
/*
* Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <assert.h>
#include "bn_local.h"
#ifndef OPENSSL_SMALL_FOOTPRINT
/*
* Karatsuba recursive multiplication algorithm (cf. Knuth, The Art of
* Computer Programming, Vol. 2)
*/
/*-
* r is 2*n2 words in size,
* a and b are both n2 words in size.
* n2 must be a power of 2.
* We multiply and return the result.
* t must be 2*n2 words in size
* We calculate
* a[0]*b[0]
* a[0]*b[0]+a[1]*b[1]+(a[0]-a[1])*(b[1]-b[0])
* a[1]*b[1]
*/
/* dnX may not be positive, but n2/2+dnX has to be */
void bn_mul_recursive(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n2,
int dna, int dnb, BN_ULONG *t)
{
int n = n2 / 2, c1, c2;
int tna = n + dna, tnb = n + dnb;
unsigned int neg, zero;
BN_ULONG ln, lo, *p;
/*
* Only call bn_mul_comba 8 if n2 == 8 and the two arrays are complete
* [steve]
*/
if (n2 == 8 && dna == 0 && dnb == 0) {
bn_mul_comba8(r, a, b);
return;
}
/* Else do normal multiply */
if (n2 < BN_MUL_RECURSIVE_SIZE_NORMAL) {
bn_mul_normal(r, a, n2 + dna, b, n2 + dnb);
if ((dna + dnb) < 0)
memset(&r[2 * n2 + dna + dnb], 0,
sizeof(BN_ULONG) * -(dna + dnb));
return;
}
/* r=(a[0]-a[1])*(b[1]-b[0]) */
c1 = bn_cmp_part_words(a, &(a[n]), tna, n - tna);
c2 = bn_cmp_part_words(&(b[n]), b, tnb, tnb - n);
zero = neg = 0;
switch (c1 * 3 + c2) {
case -4:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
break;
case -3:
zero = 1;
break;
case -2:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n); /* + */
neg = 1;
break;
case -1:
case 0:
case 1:
zero = 1;
break;
case 2:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna); /* + */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
neg = 1;
break;
case 3:
zero = 1;
break;
case 4:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna);
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n);
break;
}
if (n == 4 && dna == 0 && dnb == 0) { /* XXX: bn_mul_comba4 could take
* extra args to do this well */
if (!zero)
bn_mul_comba4(&(t[n2]), t, &(t[n]));
else
memset(&t[n2], 0, sizeof(*t) * 8);
bn_mul_comba4(r, a, b);
bn_mul_comba4(&(r[n2]), &(a[n]), &(b[n]));
} else if (n == 8 && dna == 0 && dnb == 0) { /* XXX: bn_mul_comba8 could
* take extra args to do
* this well */
if (!zero)
bn_mul_comba8(&(t[n2]), t, &(t[n]));
else
memset(&t[n2], 0, sizeof(*t) * 16);
bn_mul_comba8(r, a, b);
bn_mul_comba8(&(r[n2]), &(a[n]), &(b[n]));
} else {
p = &(t[n2 * 2]);
if (!zero)
bn_mul_recursive(&(t[n2]), t, &(t[n]), n, 0, 0, p);
else
memset(&t[n2], 0, sizeof(*t) * n2);
bn_mul_recursive(r, a, b, n, 0, 0, p);
bn_mul_recursive(&(r[n2]), &(a[n]), &(b[n]), n, dna, dnb, p);
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0]), c1 is the sign
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
*/
c1 = (int)(bn_add_words(t, r, &(r[n2]), n2));
if (neg) { /* if t[32] is negative */
c1 -= (int)(bn_sub_words(&(t[n2]), t, &(t[n2]), n2));
} else {
/* Might have a carry */
c1 += (int)(bn_add_words(&(t[n2]), &(t[n2]), t, n2));
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0])+(a[0]*b[0])+(a[1]*b[1])
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
* c1 holds the carry bits
*/
c1 += (int)(bn_add_words(&(r[n]), &(r[n]), &(t[n2]), n2));
if (c1) {
p = &(r[n + n2]);
lo = *p;
ln = (lo + c1) & BN_MASK2;
*p = ln;
/*
* The overflow will stop before we over write words we should not
* overwrite
*/
if (ln < (BN_ULONG)c1) {
do {
p++;
lo = *p;
ln = (lo + 1) & BN_MASK2;
*p = ln;
} while (ln == 0);
}
}
}
/*
* n+tn is the word length t needs to be n*4 is size, as does r
*/
/* tnX may not be negative but less than n */
void bn_mul_part_recursive(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
int n, int tna, int tnb, BN_ULONG *t)
{
int i, j, n2 = n * 2;
int c1, c2, neg;
BN_ULONG ln, lo, *p;
if (n < 8) {
bn_mul_normal(r, a, n + tna, b, n + tnb);
return;
}
/* r=(a[0]-a[1])*(b[1]-b[0]) */
c1 = bn_cmp_part_words(a, &(a[n]), tna, n - tna);
c2 = bn_cmp_part_words(&(b[n]), b, tnb, tnb - n);
neg = 0;
switch (c1 * 3 + c2) {
case -4:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
break;
case -3:
case -2:
bn_sub_part_words(t, &(a[n]), a, tna, tna - n); /* - */
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n); /* + */
neg = 1;
break;
case -1:
case 0:
case 1:
case 2:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna); /* + */
bn_sub_part_words(&(t[n]), b, &(b[n]), tnb, n - tnb); /* - */
neg = 1;
break;
case 3:
case 4:
bn_sub_part_words(t, a, &(a[n]), tna, n - tna);
bn_sub_part_words(&(t[n]), &(b[n]), b, tnb, tnb - n);
break;
}
/*
* The zero case isn't yet implemented here. The speedup would probably
* be negligible.
*/
#if 0
if (n == 4) {
bn_mul_comba4(&(t[n2]), t, &(t[n]));
bn_mul_comba4(r, a, b);
bn_mul_normal(&(r[n2]), &(a[n]), tn, &(b[n]), tn);
memset(&r[n2 + tn * 2], 0, sizeof(*r) * (n2 - tn * 2));
} else
#endif
if (n == 8) {
bn_mul_comba8(&(t[n2]), t, &(t[n]));
bn_mul_comba8(r, a, b);
bn_mul_normal(&(r[n2]), &(a[n]), tna, &(b[n]), tnb);
memset(&r[n2 + tna + tnb], 0, sizeof(*r) * (n2 - tna - tnb));
} else {
p = &(t[n2 * 2]);
bn_mul_recursive(&(t[n2]), t, &(t[n]), n, 0, 0, p);
bn_mul_recursive(r, a, b, n, 0, 0, p);
i = n / 2;
/*
* If there is only a bottom half to the number, just do it
*/
if (tna > tnb)
j = tna - i;
else
j = tnb - i;
if (j == 0) {
bn_mul_recursive(&(r[n2]), &(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
memset(&r[n2 + i * 2], 0, sizeof(*r) * (n2 - i * 2));
} else if (j > 0) { /* eg, n == 16, i == 8 and tn == 11 */
bn_mul_part_recursive(&(r[n2]), &(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
memset(&(r[n2 + tna + tnb]), 0,
sizeof(BN_ULONG) * (n2 - tna - tnb));
} else { /* (j < 0) eg, n == 16, i == 8 and tn == 5 */
memset(&r[n2], 0, sizeof(*r) * n2);
if (tna < BN_MUL_RECURSIVE_SIZE_NORMAL
&& tnb < BN_MUL_RECURSIVE_SIZE_NORMAL) {
bn_mul_normal(&(r[n2]), &(a[n]), tna, &(b[n]), tnb);
} else {
for (;;) {
i /= 2;
/*
* these simplified conditions work exclusively because
* difference between tna and tnb is 1 or 0
*/
if (i < tna || i < tnb) {
bn_mul_part_recursive(&(r[n2]),
&(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
break;
} else if (i == tna || i == tnb) {
bn_mul_recursive(&(r[n2]),
&(a[n]), &(b[n]),
i, tna - i, tnb - i, p);
break;
}
}
}
}
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0]), c1 is the sign
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
*/
c1 = (int)(bn_add_words(t, r, &(r[n2]), n2));
if (neg) { /* if t[32] is negative */
c1 -= (int)(bn_sub_words(&(t[n2]), t, &(t[n2]), n2));
} else {
/* Might have a carry */
c1 += (int)(bn_add_words(&(t[n2]), &(t[n2]), t, n2));
}
/*-
* t[32] holds (a[0]-a[1])*(b[1]-b[0])+(a[0]*b[0])+(a[1]*b[1])
* r[10] holds (a[0]*b[0])
* r[32] holds (b[1]*b[1])
* c1 holds the carry bits
*/
c1 += (int)(bn_add_words(&(r[n]), &(r[n]), &(t[n2]), n2));
if (c1) {
p = &(r[n + n2]);
lo = *p;
ln = (lo + c1) & BN_MASK2;
*p = ln;
/*
* The overflow will stop before we over write words we should not
* overwrite
*/
if (ln < (BN_ULONG)c1) {
do {
p++;
lo = *p;
ln = (lo + 1) & BN_MASK2;
*p = ln;
} while (ln == 0);
}
}
}
/*-
* a and b must be the same size, which is n2.
* r needs to be n2 words and t needs to be n2*2
*/
void bn_mul_low_recursive(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
int n2, BN_ULONG *t)
{
int n = n2 / 2;
bn_mul_recursive(r, a, b, n, 0, 0, &(t[0]));
if (n >= BN_MUL_LOW_RECURSIVE_SIZE_NORMAL) {
bn_mul_low_recursive(&(t[0]), &(a[0]), &(b[n]), n, &(t[n2]));
bn_add_words(&(r[n]), &(r[n]), &(t[0]), n);
bn_mul_low_recursive(&(t[0]), &(a[n]), &(b[0]), n, &(t[n2]));
bn_add_words(&(r[n]), &(r[n]), &(t[0]), n);
} else {
bn_mul_low_normal(&(t[0]), &(a[0]), &(b[n]), n);
bn_mul_low_normal(&(t[n]), &(a[n]), &(b[0]), n);
bn_add_words(&(r[n]), &(r[n]), &(t[0]), n);
bn_add_words(&(r[n]), &(r[n]), &(t[n]), n);
}
}
#endif /* OPENSSL_SMALL_FOOTPRINT */
/*
* This function doesn't zero out the rest of r in case of nr>na+nb;
* it's the calling function's responsibility to do so.
*/
void bn_mul_truncated(BN_ULONG *r, int nr, const BN_ULONG *a, int na,
const BN_ULONG *b, int nb)
{
BN_ULONG tmp, *carryp = NULL;
int n; /* Number of words to use in the current iteration */
if (na < nb) {
int itmp;
const BN_ULONG *ltmp;
itmp = na;
na = nb;
nb = itmp;
ltmp = a;
a = b;
b = ltmp;
}
n = (na < nr) ? na : nr;
if (nb <= 0) {
(void)bn_mul_words(r, a, n, 0);
return;
} else {
carryp = (na < nr) ? &r[na] : &tmp;
*carryp = bn_mul_words(r, a, n, b[0]);
}
for (int i = 1; i < nb && i < nr; i++) {
int rspace = nr - i;
n = (na < rspace) ? na : rspace;
carryp = (na < rspace) ? &r[i + na] : &tmp;
*carryp = bn_mul_add_words(&(r[i]), a, n, b[i]);
}
}
void bn_mul_normal(BN_ULONG *r, const BN_ULONG *a, int na, const BN_ULONG *b,
int nb)
{
bn_mul_truncated(r, na + nb, a, na, b, nb);
}
void bn_mul_low_normal(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n)
{
bn_mul_words(r, a, n, b[0]);
for (;;) {
if (--n <= 0)
return;
bn_mul_add_words(&(r[1]), a, n, b[1]);
if (--n <= 0)
return;
bn_mul_add_words(&(r[2]), a, n, b[2]);
if (--n <= 0)
return;
bn_mul_add_words(&(r[3]), a, n, b[3]);
if (--n <= 0)
return;
bn_mul_add_words(&(r[4]), a, n, b[4]);
r += 4;
b += 4;
}
}

155
crypto/bn/bnw_sub.c Normal file
View file

@ -0,0 +1,155 @@
/*
* Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <assert.h>
#include "bn_local.h"
#if defined(OPENSSL_NO_ASM) || !defined(OPENSSL_BN_ASM_PART_WORDS)
/*
* Here follows specialised variants of bn_add_words() and bn_sub_words().
* They have the property performing operations on arrays of different sizes.
* The sizes of those arrays is expressed through cl, which is the common
* length ( basically, min(len(a),len(b)) ), and dl, which is the delta
* between the two lengths, calculated as len(a)-len(b). All lengths are the
* number of BN_ULONGs... For the operations that require a result array as
* parameter, it must have the length cl+abs(dl).
*
* These functions should probably end up in bn_asm.c as soon as there are
* assembler counterparts for the systems that use assembler files.
*/
BN_ULONG bn_sub_part_words(BN_ULONG *r,
const BN_ULONG *a, const BN_ULONG *b,
int cl, int dl)
{
BN_ULONG c, t;
assert(cl >= 0);
c = bn_sub_words(r, a, b, cl);
if (dl == 0)
return c;
r += cl;
a += cl;
b += cl;
if (dl < 0) {
for (;;) {
t = b[0];
r[0] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
t = b[1];
r[1] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
t = b[2];
r[2] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
t = b[3];
r[3] = (0 - t - c) & BN_MASK2;
if (t != 0)
c = 1;
if (++dl >= 0)
break;
b += 4;
r += 4;
}
} else {
int save_dl = dl;
while (c) {
t = a[0];
r[0] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
t = a[1];
r[1] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
t = a[2];
r[2] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
t = a[3];
r[3] = (t - c) & BN_MASK2;
if (t != 0)
c = 0;
if (--dl <= 0)
break;
save_dl = dl;
a += 4;
r += 4;
}
if (dl > 0) {
if (save_dl > dl) {
switch (save_dl - dl) {
case 1:
r[1] = a[1];
if (--dl <= 0)
break;
/* fall through */
case 2:
r[2] = a[2];
if (--dl <= 0)
break;
/* fall through */
case 3:
r[3] = a[3];
if (--dl <= 0)
break;
}
a += 4;
r += 4;
}
}
if (dl > 0) {
for (;;) {
r[0] = a[0];
if (--dl <= 0)
break;
r[1] = a[1];
if (--dl <= 0)
break;
r[2] = a[2];
if (--dl <= 0)
break;
r[3] = a[3];
if (--dl <= 0)
break;
a += 4;
r += 4;
}
}
}
return c;
}
#endif

View file

@ -105,11 +105,18 @@ IF[{- !$disabled{asm} -}]
ENDIF
ENDIF
$COMMON=bn_add.c bn_div.c bn_exp.c bn_lib.c bn_ctx.c bn_mul.c \
$COMMON_BN=bn_add.c bn_div.c bn_exp.c bn_lib.c bn_ctx.c bn_mul.c \
bn_mod.c bn_conv.c bn_rand.c bn_shift.c bn_word.c bn_blind.c \
bn_kron.c bn_sqrt.c bn_gcd.c bn_prime.c bn_sqr.c \
bn_recp.c bn_mont.c bn_mpi.c bn_exp2.c bn_gf2m.c bn_nist.c \
bn_intern.c bn_dh.c bn_rsa_fips186_5.c bn_const.c
# bnw_*.c is a growing collection of files with routines that operate on
# BN_ULONG only. They were helper routines dispersed in bn_*.c, and are
# often routines that don't have an assembler implementation, and therefore
# didn't fit into bn_asm.c.
$COMMON_BNW=bnw_sub.c bnw_mul.c
$COMMON=$COMMON_BN $COMMON_BNW
SOURCE[../../libcrypto]=$COMMON $BNASM bn_print.c bn_err.c bn_srp.c
DEFINE[../../libcrypto]=$BNDEF
IF[{- !$disabled{'deprecated-0.9.8'} -}]

View file

@ -4,7 +4,7 @@ SUBDIRS=objects buffer bio stack lhash hashtable rand evp asn1 pem x509 conf \
txt_db pkcs7 pkcs12 ui kdf store property \
md2 md4 md5 sha mdc2 ml_kem hmac ripemd whrlpool poly1305 \
siphash sm3 des aes rc2 rc4 rc5 idea aria bf cast camellia \
seed sm4 chacha modes bn ec rsa dsa dh sm2 dso \
seed sm4 chacha modes fn bn ec rsa dsa dh sm2 dso \
err comp http ocsp cms ts srp cmac ct async ess crmf cmp encode_decode \
ffc hpke thread lms ml_dsa slh_dsa
@ -99,7 +99,8 @@ $UTIL_COMMON=\
threads_pthread.c threads_win.c threads_none.c threads_common.c \
initthread.c context.c sparse_array.c asn1_dsa.c packet.c \
param_build.c param_build_set.c der_writer.c threads_lib.c \
params_dup.c time.c array_alloc.c aligned_alloc.c deterministic_nonce.c
params_dup.c time.c array_alloc.c aligned_alloc.c deterministic_nonce.c \
int.c
SOURCE[../libcrypto]=$UTIL_COMMON \
mem.c mem_sec.c \

View file

@ -13,6 +13,7 @@
#include "crypto/cryptoerr.h"
#include "crypto/asn1err.h"
#include "crypto/bnerr.h"
#include "crypto/fnerr.h"
#include "crypto/ecerr.h"
#include "crypto/buffererr.h"
#include "crypto/bioerr.h"
@ -51,6 +52,7 @@ int ossl_err_load_crypto_strings(void)
#ifndef OPENSSL_NO_ERR
|| ossl_err_load_ERR_strings() == 0 /* include error strings for SYSerr */
|| ossl_err_load_BN_strings() == 0
|| ossl_err_load_OSSL_FN_strings() == 0
|| ossl_err_load_RSA_strings() == 0
#ifndef OPENSSL_NO_DH
|| ossl_err_load_DH_strings() == 0

View file

@ -43,6 +43,7 @@ L PROV include/openssl/proverr.h providers/common/provider_err.c
L OSSL_ENCODER include/openssl/encodererr.h crypto/encode_decode/encoder_err.c include/crypto/encodererr.h
L OSSL_DECODER include/openssl/decodererr.h crypto/encode_decode/decoder_err.c include/crypto/decodererr.h
L HTTP include/openssl/httperr.h crypto/http/http_err.c include/crypto/httperr.h
L OSSL_FN NONE crypto/fn/fn_err.c include/crypto/fnerr.h
# SSL/TLS alerts
R SSL_R_TLS_ALERT_UNEXPECTED_MESSAGE 1010

View file

@ -888,6 +888,13 @@ OSSL_ENCODER_R_ENCODER_NOT_FOUND:101:encoder not found
OSSL_ENCODER_R_INCORRECT_PROPERTY_QUERY:100:incorrect property query
OSSL_ENCODER_R_MISSING_GET_PARAMS:102:missing get params
OSSL_ENCODER_R_UNKNOWN_PARAMETER_NAME:104:unknown parameter name
OSSL_FN_R_DIV_BY_ZERO:101:div by zero
OSSL_FN_R_INPUT_NOT_REDUCED:103:input not reduced
OSSL_FN_R_BITS_TOO_SMALL:105:bits too small
OSSL_FN_R_INVALID_RANGE:106:invalid range
OSSL_FN_R_INVALID_SHIFT:102:invalid shift
OSSL_FN_R_RESULT_ARG_TOO_SMALL:100:result arg too small
OSSL_FN_R_TOO_MANY_ITERATIONS:107:too many iterations
OSSL_STORE_R_AMBIGUOUS_CONTENT_TYPE:107:ambiguous content type
OSSL_STORE_R_BAD_PASSWORD_READ:115:bad password read
OSSL_STORE_R_ERROR_VERIFYING_PKCS12_MAC:113:error verifying pkcs12 mac

9
crypto/fn/build.info Normal file
View file

@ -0,0 +1,9 @@
$LIBCRYPTO=../../libcrypto
$LIBFIPS=../../providers/libfips.a
LIBS=$LIBCRYPTO
$COMMON=fn_err.c fn_lib.c fn_ctx.c fn_intern.c fn_addsub.c fn_shift.c fn_mul.c \
fn_sqr.c fn_div.c fn_rand.c fn_mod.c
SOURCE[$LIBCRYPTO]=$COMMON
SOURCE[$LIBFIPS]=$COMMON

343
crypto/fn/fn_addsub.c Normal file
View file

@ -0,0 +1,343 @@
/*
* Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "internal/cryptlib.h"
#include "crypto/fnerr.h"
#include "../bn/bn_local.h" /* For using the low level bignum functions */
#include "fn_local.h"
/*
* ossl_fn_add_words and ossl_fn_sub_words perform fixed-width unsigned
* addition and subtraction of multi-limb integers.
*
* The carry or borrow is always propagated through every limb of both
* operands (and through any extra result limbs when rl exceeds the operand
* sizes). The operation is effectively performed at the precision of the
* wider operand, then truncated to rl limbs analogous to performing an
* unsigned operation in the wider of the operand types and then casting
* the result to a narrower type.
*
* The returned carry (addition) or borrow (subtraction) is the true
* overflow / borrow out of the most significant processed limb:
*
* - When rl <= max(al, bl), it is the carry / borrow out of max(al, bl)
* limbs. Note that this does NOT indicate whether the result fits in
* rl limbs; non-zero high limbs may have been truncated without
* generating a carry. If the caller needs the exact magnitude, rl
* must be at least max(al, bl).
*
* - For addition, when rl > max(al, bl), the carry is absorbed into the
* result (written to r[max(al,bl)], higher limbs zeroed) and the
* function returns 0.
*
* - For subtraction, when rl > max(al, bl), the borrow is propagated
* through all remaining result limbs (two's complement sign extension)
* and the function returns the borrow out of rl limbs.
*/
/* unsigned addition of a and b, returns carry if there is one past the result size */
OSSL_FN_ULONG ossl_fn_add_words(OSSL_FN_ULONG *r, size_t rl,
const OSSL_FN_ULONG *a, size_t al,
const OSSL_FN_ULONG *b, size_t bl)
{
/*
* Addition is commutative, so we switch 'a' and 'b' around to
* ensure that 'a' is physically the largest, so a maximum of
* work is done with 'bn_add_words'
*/
if (al < bl) {
const OSSL_FN_ULONG *tmp;
size_t tmpl;
tmp = a;
tmpl = al;
a = b;
al = bl;
b = tmp;
bl = tmpl;
}
/*
* Four stages.
*
* For each stage, |stage_limbs| is used to hold the number
* of limbs being treated in that stage, |i| is used as an
* index into the arrays, and |carry| is used to transport
* the carry from one stage to the other.
*
* Note: |stage_limbs| is passed cast to 'int' when calling
* bn_add_words(). This is fine because the maximum size of
* any OSSL_FN_ULONG is BN_MAX_WORDS, which is small enough.
* Should that change some day, there's trouble ahead.
*/
size_t stage_limbs;
OSSL_FN_ULONG carry;
size_t i;
/*
* Stage 1: calculate the least min(rl,bl) limbs
*
* This uses bn_add_words, with what performance benefits that gives.
*/
stage_limbs = (bl > rl) ? rl : bl;
carry = bn_add_words(r, a, b, (int)stage_limbs);
/* Record the array position past what bn_add_words calculated */
i = stage_limbs;
/*
* Stage 2: calculate min(rl,bl) to bl limbs
*
* Because this loop only engages when rl < bl, it cannot affect r.
* The only purpose of this loop is to propagate carry in this particular
* scenario.
*/
stage_limbs = bl - stage_limbs;
for (size_t dif = stage_limbs; dif > 0; dif--, i++) {
OSSL_FN_ULONG t1, t2;
t1 = (a[i] + carry) & OSSL_FN_MASK;
carry = (t1 < carry);
t2 = (b[i] + t1) & OSSL_FN_MASK;
carry |= (t2 < t1);
}
assert(i == bl);
/*
* Stage 3: calculate bl to al limbs
*
* Note: at any time, the end of r may be reached. This is solved
* with a temporary pointer that's set appropriately inside the loop.
*/
stage_limbs = al - bl;
for (size_t dif = stage_limbs; dif > 0; dif--, i++) {
OSSL_FN_ULONG tmp = 0;
OSSL_FN_ULONG *rp = (i < rl) ? &r[i] : &tmp;
OSSL_FN_ULONG t1;
t1 = (a[i] + carry) & OSSL_FN_MASK;
carry = (t1 < carry);
*rp = t1;
}
assert(i == al);
/* If |r| is exhausted, there's nothing more to do */
if (i >= rl)
return carry;
/*
* Stage 4: calculate a final carry, for when rl > al
*
* This is relatively simple, compare to earlier loops.
*/
stage_limbs = rl - al;
for (size_t dif = stage_limbs; dif > 0; dif--, i++) {
r[i] = carry;
carry = 0;
}
return carry;
}
int OSSL_FN_add(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b)
{
(void)ossl_fn_add_words(r->d, r->dsize, a->d, a->dsize, b->d, b->dsize);
return 1;
}
/*-
* Adds the single-limb word |w| to |a| in place, propagating the carry
* through |a|'s limbs and truncating any carry out past a->dsize (OSSL_FN is
* fixed-size, so a carry past the last limb is discarded rather than grown
* into). The degenerate w == 0 case is a no-op.
*
* Not constant-time: the carry-propagation loop stops early once the carry
* is exhausted, so the number of limbs touched depends on the operand's
* value.
*/
int OSSL_FN_add_word(OSSL_FN *a, OSSL_FN_ULONG w)
{
size_t i;
size_t dsize = (size_t)a->dsize;
if (w == 0)
return 1;
for (i = 0; i < dsize && w != 0; i++) {
OSSL_FN_ULONG l = (a->d[i] + w) & OSSL_FN_MASK;
a->d[i] = l;
w = (w > l);
}
/* Any remaining carry out past dsize is truncated. */
return 1;
}
/* unsigned subtraction of b from a, returns borrow if there is one past the result size */
OSSL_FN_ULONG ossl_fn_sub_words(OSSL_FN_ULONG *r, size_t rl,
const OSSL_FN_ULONG *a, size_t al,
const OSSL_FN_ULONG *b, size_t bl)
{
size_t max = (al >= bl) ? al : bl;
size_t min = (al <= bl) ? al : bl;
/*
* Four stages.
*
* For each stage, |stage_limbs| is used to hold the number
* of limbs being treated in that stage, |i| is used as an
* index into the arrays, and |borrow| is used to transport
* the borrow from one stage to the other.
*
* Note: |stage_limbs| is passed cast to 'int' when calling
* bn_sub_words(). This is fine because the maximum size of
* any OSSL_FN_ULONG is BN_MAX_WORDS, which is small enough.
* Should that change some day, there's trouble ahead.
*/
size_t stage_limbs;
OSSL_FN_ULONG borrow;
size_t i;
/*
* Stage 1: calculate the least min(rl,al,bl) limbs
*
* This uses bn_sub_words, with what performance benefits that gives.
*/
stage_limbs = (min > rl) ? rl : min;
borrow = bn_sub_words(r, a, b, (int)stage_limbs);
/* Record the array position past what bn_sub_words calculated */
i = stage_limbs;
/*
* Stage 2: calculate the min(rl,al,bl) to min(al,bl) limbs
*
* Because this loop only engages when rl < min(al,bl), it cannot affect r.
* The only purpose of this loop is to propagate borrow in this particular
* scenario.
*/
stage_limbs = min - stage_limbs;
for (size_t dif = stage_limbs; dif > 0; dif--, i++) {
OSSL_FN_ULONG t1, t2;
t1 = a[i];
t2 = (t1 - borrow) & OSSL_FN_MASK;
borrow = (t2 > t1);
t1 = b[i];
t1 = (t2 - t1) & OSSL_FN_MASK;
borrow |= (t1 > t2);
}
assert(i == min);
/*
* Stage 3: calculate the min(al,bl) to max(al,bl) limbs
*
* Note: at any time, the end of r may be reached. This is solved
* with a temporary pointer that's set appropriately inside the loop.
*/
const OSSL_FN_ULONG *maxp = (al >= bl) ? a : b;
const OSSL_FN_ULONG s2_mask1 = (al >= bl) ? OSSL_FN_MASK : 0;
const OSSL_FN_ULONG s2_mask2 = ~s2_mask1;
stage_limbs = max - min;
/* calculate the result of borrowing from more significant limbs */
for (size_t dif = stage_limbs; dif > 0; dif--, i++) {
OSSL_FN_ULONG tmp = 0;
OSSL_FN_ULONG *rp = (i < rl) ? &r[i] : &tmp;
OSSL_FN_ULONG t1, t2;
t1 = maxp[i] & s2_mask1;
t2 = (t1 - borrow) & OSSL_FN_MASK;
borrow = (t2 > t1);
t1 = maxp[i] & s2_mask2;
t1 = (t2 - t1) & OSSL_FN_MASK;
borrow |= (t1 > t2);
*rp = t1;
}
assert(i == max);
/* If |r| is exhausted, there's nothing more to do */
if (i >= rl)
return borrow;
/*
* Stage 4: calculate a final borrow, for when rl > max
*
* This is relatively simple, compare to earlier loops.
*/
stage_limbs = rl - max;
/* Finally, fill in the rest of the result array by borrowing from zeros */
for (size_t dif = stage_limbs; dif > 0; dif--, i++) {
OSSL_FN_ULONG t1 = (0 - borrow) & OSSL_FN_MASK;
borrow = (t1 > 0);
r[i] = t1;
}
return borrow;
}
int OSSL_FN_sub(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b)
{
(void)ossl_fn_sub_words(r->d, r->dsize, a->d, a->dsize, b->d, b->dsize);
return 1;
}
/*-
* Subtracts the single-limb word |w| from |a| in place, propagating the
* borrow through |a|'s limbs. If the borrow runs past a->dsize (i.e. the
* unsigned value of |a| is less than |w|), the result is the 2's-complement
* wrap-around truncated to dsize, per OSSL_FN's fixed-size unsigned
* semantics: there is no sign to record, so the wrapped value is kept. The
* degenerate w == 0 case is a no-op.
*
* Not constant-time: the borrow-propagation loop returns early once the
* borrow is repaid, so the number of limbs touched depends on the operand's
* value.
*/
int OSSL_FN_sub_word(OSSL_FN *a, OSSL_FN_ULONG w)
{
size_t i;
size_t dsize = (size_t)a->dsize;
if (w == 0)
return 1;
for (i = 0; i < dsize; i++) {
if (a->d[i] >= w) {
a->d[i] -= w;
return 1; /* borrow repaid */
}
a->d[i] = (a->d[i] - w) & OSSL_FN_MASK;
w = 1;
}
/* Borrow out past dsize is truncated (2's complement). */
return 1;
}

253
crypto/fn/fn_ctx.c Normal file
View file

@ -0,0 +1,253 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <assert.h>
#include <openssl/crypto.h>
#include "internal/safe_math.h"
#include "crypto/fn.h"
#include "fn_local.h"
OSSL_SAFE_MATH_ADDU(size_t, size_t, OSSL_SAFE_MATH_MAXU(size_t))
OSSL_SAFE_MATH_MULU(size_t, size_t, OSSL_SAFE_MATH_MAXU(size_t))
/*
* An OSSL_FN_CTX is a large pre-allocated chunk of memory that can be used
* to quickly allocate OSSL_FN instances. The organization of memory is as
* a fairly typical arena, where OSSL_FN instances are "stacked" one after
* the other.
*
* However, there is also the concept of frames, which are arenas within an
* arena. This allows easily passing an OSSL_FN_CTX to a function, and for
* that function to allocate such a frame for itself, and easily deallocate
* it when it's done.
*/
size_t OSSL_FN_CTX_size(size_t max_n_frames, size_t max_n_numbers,
size_t max_n_limbs)
{
int err = 0;
size_t frames, numbers, limbs, total;
/*
* A context always needs at least one frame, since every use of an
* OSSL_FN_CTX calls OSSL_FN_CTX_start(), which carves out a frame.
*/
if (max_n_frames == 0)
return 0;
/*
* Number-header and limb budgets must both be present or both absent.
* OSSL_FN_CTX_get_limbs() allocates an OSSL_FN header and its limbs
* together, so a context with one budget but not the other can never
* produce a usable number.
*/
if ((max_n_numbers == 0) != (max_n_limbs == 0))
return 0;
frames = safe_mul_size_t(max_n_frames,
sizeof(struct ossl_fn_ctx_frame_st), &err);
numbers = safe_mul_size_t(max_n_numbers, sizeof(OSSL_FN), &err);
limbs = safe_mul_size_t(max_n_limbs, OSSL_FN_BYTES, &err);
total = safe_add_size_t(frames, numbers, &err);
total = safe_add_size_t(total, limbs, &err);
return err == 0 ? total : 0;
}
OSSL_FN_CTX *OSSL_FN_CTX_new(OSSL_LIB_CTX *libctx, size_t max_n_frames,
size_t max_n_numbers, size_t max_n_limbs)
{
return OSSL_FN_CTX_new_size(libctx,
OSSL_FN_CTX_size(max_n_frames, max_n_numbers, max_n_limbs));
}
OSSL_FN_CTX *OSSL_FN_CTX_new_size(OSSL_LIB_CTX *libctx, size_t size)
{
size_t total_size;
OSSL_FN_CTX *ctx;
int err = 0;
/*
* A size of 0 is the error return of OSSL_FN_CTX_size() (and the
* per-operation ctx-size helpers). Treat it as an error here too, so a
* caller does not get back a context with a zero-size arena that it
* would then hand to an operation expecting usable scratch space.
*/
if (size == 0)
return NULL;
total_size = safe_add_size_t(sizeof(*ctx), size, &err);
if (err != 0)
return NULL;
ctx = OPENSSL_zalloc(total_size);
if (ctx != NULL)
ctx->msize = size;
return ctx;
}
OSSL_FN_CTX *OSSL_FN_CTX_secure_new(OSSL_LIB_CTX *libctx, size_t max_n_frames,
size_t max_n_numbers, size_t max_n_limbs)
{
return OSSL_FN_CTX_secure_new_size(libctx,
OSSL_FN_CTX_size(max_n_frames, max_n_numbers, max_n_limbs));
}
OSSL_FN_CTX *OSSL_FN_CTX_secure_new_size(OSSL_LIB_CTX *libctx, size_t size)
{
size_t total_size;
OSSL_FN_CTX *ctx;
int err = 0;
/* As in OSSL_FN_CTX_new_size(), a size of 0 is an error. */
if (size == 0)
return NULL;
total_size = safe_add_size_t(sizeof(*ctx), size, &err);
if (err != 0)
return NULL;
ctx = OPENSSL_secure_zalloc(total_size);
if (ctx != NULL) {
ctx->msize = size;
ctx->is_securely_allocated = 1;
}
return ctx;
}
void OSSL_FN_CTX_peak_usage(const OSSL_FN_CTX *ctx, size_t *peak_n_frames,
size_t *peak_n_numbers, size_t *peak_n_limbs)
{
if (ctx == NULL) {
if (peak_n_frames != NULL)
*peak_n_frames = 0;
if (peak_n_numbers != NULL)
*peak_n_numbers = 0;
if (peak_n_limbs != NULL)
*peak_n_limbs = 0;
return;
}
if (peak_n_frames != NULL)
*peak_n_frames = ctx->peak_n_frames;
if (peak_n_numbers != NULL)
*peak_n_numbers = ctx->peak_n_numbers;
if (peak_n_limbs != NULL)
*peak_n_limbs = ctx->peak_n_limbs;
}
void OSSL_FN_CTX_free(OSSL_FN_CTX *ctx)
{
if (ctx == NULL)
return;
assert(ctx->last_frame == NULL);
if (ctx->is_securely_allocated)
OPENSSL_secure_free(ctx);
else
OPENSSL_free(ctx);
}
const void *OSSL_FN_CTX_start(OSSL_FN_CTX *ctx)
{
if (!ossl_assert(ctx != NULL))
return NULL;
struct ossl_fn_ctx_frame_st *last_frame = ctx->last_frame;
size_t used = (last_frame == NULL) ? 0 : last_frame->free_memory - ctx->memory;
if (ctx->msize - used < sizeof(struct ossl_fn_ctx_frame_st))
return NULL;
if (ctx->last_frame == NULL)
ctx->last_frame = (struct ossl_fn_ctx_frame_st *)ctx->memory;
else
ctx->last_frame = (struct ossl_fn_ctx_frame_st *)last_frame->free_memory;
struct ossl_fn_ctx_frame_st *frame = ctx->last_frame;
frame->arena = ctx;
frame->previous_frame = last_frame;
frame->free_memory = frame->memory;
frame->msize = ctx->msize - used - sizeof(*frame);
frame->n_numbers = 0;
frame->n_limbs = 0;
ctx->n_frames++;
if (ctx->n_frames > ctx->peak_n_frames)
ctx->peak_n_frames = ctx->n_frames;
return ctx->last_frame;
}
int OSSL_FN_CTX_end(OSSL_FN_CTX *ctx, const void *token)
{
if (!ossl_assert(ctx != NULL) || !ossl_assert(ctx->last_frame != NULL))
return 0;
struct ossl_fn_ctx_frame_st *last_frame = ctx->last_frame;
if (last_frame != token)
return 0;
ctx->n_numbers -= last_frame->n_numbers;
ctx->n_limbs -= last_frame->n_limbs;
ctx->n_frames--;
ctx->last_frame = last_frame->previous_frame;
return 1;
}
OSSL_FN *OSSL_FN_CTX_get_limbs(OSSL_FN_CTX *ctx, size_t limbs)
{
if (!ossl_assert(ctx != NULL))
return NULL;
struct ossl_fn_ctx_frame_st *frame = ctx->last_frame;
if (!ossl_assert(frame != NULL))
return NULL;
size_t totalsize = ossl_fn_totalsize(limbs);
size_t used = frame->free_memory - frame->memory;
if (totalsize == 0 || frame->msize - used < totalsize)
return NULL;
OSSL_FN *fn = (OSSL_FN *)frame->free_memory;
frame->free_memory += totalsize;
frame->n_numbers++;
frame->n_limbs += limbs;
ctx->n_numbers++;
ctx->n_limbs += limbs;
if (ctx->n_numbers > ctx->peak_n_numbers)
ctx->peak_n_numbers = ctx->n_numbers;
if (ctx->n_limbs > ctx->peak_n_limbs)
ctx->peak_n_limbs = ctx->n_limbs;
memset(fn, 0, totalsize);
fn->dsize = (int)limbs;
fn->is_securely_allocated = ctx->is_securely_allocated;
return fn;
}
OSSL_FN *OSSL_FN_CTX_get_bytes(OSSL_FN_CTX *ctx, size_t bytes)
{
return OSSL_FN_CTX_get_limbs(ctx, ossl_fn_bytes_to_limbs(bytes));
}
OSSL_FN *OSSL_FN_CTX_get_bits(OSSL_FN_CTX *ctx, size_t bits)
{
return OSSL_FN_CTX_get_bytes(ctx, ossl_fn_bits_to_bytes(bits));
}

395
crypto/fn/fn_div.c Normal file
View file

@ -0,0 +1,395 @@
/*
* Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <assert.h>
#include <openssl/err.h>
#include "internal/safe_math.h"
#include "crypto/cryptlib.h"
#include "crypto/fnerr.h"
#include "../bn/bn_local.h" /* For using the low level bignum functions */
#include "fn_local.h"
OSSL_SAFE_MATH_ADDU(size_t, size_t, OSSL_SAFE_MATH_MAXU(size_t))
#if !defined(OPENSSL_NO_ASM) && !defined(OPENSSL_NO_INLINE_ASM) \
&& !defined(PEDANTIC) && !defined(BN_DIV3W)
#if defined(__GNUC__) && __GNUC__ >= 2
#if defined(__i386) || defined(__i386__)
/*-
* There were two reasons for implementing this template:
* - GNU C generates a call to a function (__udivdi3 to be exact)
* in reply to ((((BN_ULLONG)n0)<<BN_BITS2)|n1)/d0 (I fail
* to understand why...);
* - divl doesn't only calculate quotient, but also leaves
* remainder in %edx which we can definitely use here:-)
*/
#undef bn_div_words
#define bn_div_words(n0, n1, d0) \
({ \
asm volatile( \
"divl %4" \
: "=a"(quo), "=d"(rem) \
: "a"(n1), "d"(n0), "r"(d0) \
: "cc"); \
quo; \
})
#define REMAINDER_IS_ALREADY_CALCULATED
#elif defined(__x86_64) && defined(SIXTY_FOUR_BIT_LONG)
/*
* Same story here, but it's 128-bit by 64-bit division. Wow!
*/
#undef bn_div_words
#define bn_div_words(n0, n1, d0) \
({ \
asm volatile( \
"divq %4" \
: "=a"(quo), "=d"(rem) \
: "a"(n1), "d"(n0), "r"(d0) \
: "cc"); \
quo; \
})
#define REMAINDER_IS_ALREADY_CALCULATED
#endif /* __<cpu> */
#endif /* __GNUC__ */
#endif /* OPENSSL_NO_ASM */
/*
* Copy src to dst and align it to the left using lshift.
* lshift is assumed to be less than OSSL_FN_BITS.
*/
static inline void copy_align_left(OSSL_FN *dst, const OSSL_FN *src, OSSL_FN_ULONG lshift)
{
OSSL_FN_ULONG rshift = OSSL_FN_BITS - lshift;
OSSL_FN_ULONG rmask;
OSSL_FN_ULONG m;
const OSSL_FN_ULONG *s = src->d;
size_t sl = src->dsize;
OSSL_FN_ULONG *d = dst->d;
size_t dl = dst->dsize;
size_t l = (dl < sl) ? dl : sl;
size_t i;
rshift %= OSSL_FN_BITS;
/* rmask = 0 - (rshift != 0) */
rmask = (OSSL_FN_ULONG)0 - rshift;
rmask |= rmask >> 8;
/* src and dst may be the same, that's why this loop is made this way */
for (i = 0, m = 0; i < l; i++) {
OSSL_FN_ULONG tmp = s[i];
d[i] = ((tmp << lshift) | m) & OSSL_FN_MASK;
m = (tmp >> rshift) & rmask;
}
for (; i < dl; i++) {
d[i] = m;
m = 0;
}
}
/*
* Copy src to dst and align it to the right using rshift.
* rshift is assumed to be less than OSSL_FN_BITS.
*/
static inline void copy_align_right(OSSL_FN *dst, const OSSL_FN *src, OSSL_FN_ULONG rshift)
{
OSSL_FN_ULONG lshift = OSSL_FN_BITS - rshift;
OSSL_FN_ULONG lmask;
const OSSL_FN_ULONG *s = src->d;
size_t sl = src->dsize;
OSSL_FN_ULONG *d = dst->d;
size_t dl = dst->dsize;
size_t i;
size_t l = (dl < sl) ? dl : sl;
lshift %= OSSL_FN_BITS;
/* lmask = 0 - (lshift != 0) */
lmask = (OSSL_FN_ULONG)0 - lshift;
lmask |= lmask >> 8;
/* Just to be safe */
for (i = dl; i-- > l;)
d[i] = 0;
/*
* m is a set of bits passed to the next limb down when shifting,
* and needs proper bootstrapping: if the source is larger than the
* destination, we must consider one source limb beyond the destination
* size. If not, m is simply starts with zero.
*/
OSSL_FN_ULONG m = (dl < sl) ? (s[dl] << lshift) & lmask : 0;
/* src and dst may be the same, that's why this loop is made this way */
for (i = l; i-- > 0;) {
OSSL_FN_ULONG tmp = s[i];
d[i] = m | ((tmp >> rshift) & OSSL_FN_MASK);
m = (tmp << lshift) & lmask;
}
}
static inline OSSL_FN_ULONG div_words(OSSL_FN_ULONG *wnumtop, OSSL_FN_ULONG *wnum,
OSSL_FN_ULONG d1, OSSL_FN_ULONG d0)
{
#if defined(BN_DIV3W)
return bn_div_3_words(wnumtop, d1, d0);
#else
OSSL_FN_ULONG n0 = wnumtop[0], n1 = wnumtop[-1], quo = 0, rem = 0;
if (n0 == d0)
quo = OSSL_FN_MASK;
else { /* n0 < d0 */
OSSL_FN_ULONG n2 = (wnumtop == wnum) ? 0 : wnumtop[-2];
#ifdef BN_LLONG
BN_ULLONG t2;
#if defined(BN_LLONG) && defined(BN_DIV2W) && !defined(bn_div_words)
quo = (OSSL_FN_ULONG)(((((BN_ULLONG)n0) << OSSL_FN_BITS) | n1) / d0);
#else
quo = bn_div_words(n0, n1, d0);
#endif
#ifndef REMAINDER_IS_ALREADY_CALCULATED
/*
* rem doesn't have to be BN_ULLONG. The least we
* know it's less that d0, isn't it?
*/
rem = (n1 - quo * d0) & BN_MASK2;
#endif
t2 = (BN_ULLONG)d1 * quo;
for (;;) {
if (t2 <= ((((BN_ULLONG)rem) << OSSL_FN_BITS) | n2))
break;
quo--;
rem += d0;
if (rem < d0)
break; /* don't let rem overflow */
t2 -= d1;
}
#else /* !BN_LLONG */
OSSL_FN_ULONG t2l, t2h;
quo = bn_div_words(n0, n1, d0);
#ifndef REMAINDER_IS_ALREADY_CALCULATED
rem = (n1 - quo * d0) & OSSL_FN_MASK;
#endif
#if defined(BN_UMULT_LOHI)
BN_UMULT_LOHI(t2l, t2h, d1, quo);
#elif defined(BN_UMULT_HIGH)
t2l = d1 * quo;
t2h = BN_UMULT_HIGH(d1, quo);
#else
{
OSSL_FN_ULONG ql, qh;
t2l = LBITS(d1);
t2h = HBITS(d1);
ql = LBITS(quo);
qh = HBITS(quo);
mul64(t2l, t2h, ql, qh); /* t2=(BN_ULLONG)d1*q; */
}
#endif
for (;;) {
if ((t2h < rem) || ((t2h == rem) && (t2l <= n2)))
break;
quo--;
rem += d0;
if (rem < d0)
break; /* don't let rem overflow */
if (t2l < d1)
t2h--;
t2l -= d1;
}
#endif /* !BN_LLONG */
}
return quo;
#endif /* !BN_DIV3W */
}
size_t OSSL_FN_div_ctx_size(const OSSL_FN *q, const OSSL_FN *r,
const OSSL_FN *n, const OSSL_FN *d)
{
if ((q == NULL && r == NULL) || n == NULL || d == NULL)
return 0;
size_t nl = n->dsize;
size_t dl = d->dsize;
size_t snuml, tmp;
int err = 0;
snuml = safe_add_size_t(nl > dl ? nl : dl, 1, &err);
tmp = safe_add_size_t(dl, 1, &err);
size_t max_ql = nl == 0 ? 1 : nl;
size_t ql = (q == NULL || (size_t)q->dsize < max_ql) ? max_ql : 0;
size_t max_n_numbers = 3 + (ql != 0);
size_t max_n_limbs;
max_n_limbs = safe_add_size_t(snuml, dl, &err);
max_n_limbs = safe_add_size_t(max_n_limbs, tmp, &err);
max_n_limbs = safe_add_size_t(max_n_limbs, ql, &err);
return err == 0 ? OSSL_FN_CTX_size(1, max_n_numbers, max_n_limbs) : 0;
}
/* Trivia: this function implements Knuth's algorithm D */
int OSSL_FN_div(OSSL_FN *q, OSSL_FN *r, const OSSL_FN *n, const OSSL_FN *d, OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
if (token == NULL)
return 0;
size_t nl = n->dsize;
size_t dl = d->dsize;
size_t ql = (q == NULL) ? 0 : q->dsize;
/*
* We need to figure out the significant size of |d|, to avoid division by
* zero if the highest limb(s) are zero.
*
* This doesn't quite give a sense that division can be constant time.
* However, in the use cases where constant time is interesting (cryptosystems),
* it can be argued that the denominator would have a constant enough size
* within each cryptosystem (and size therein), so it's assumed that time
* will be constant because of that.
*/
while (dl > 0 && d->d[dl - 1] == 0)
dl--;
if (dl == 0) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_DIV_BY_ZERO);
goto err;
}
/*
* Because some assembler language instructions have those requirements,
* the denominator need to be shifted "left" so the top bit is always 1.
* To ensure that we still get correct results, the numerator will have
* to be shifted left as many bits. The resulting quotient will end up
* correct, but the remainder will have to be shifted "right" before the
* end of this function.
*/
OSSL_FN_ULONG norm_shift = OSSL_FN_BITS - BN_num_bits_word(d->d[dl - 1]);
/*
* Store a copy the numerator in snum, padded with extra zeros if nl <= dl
* eventually, this will contain the remainder. Because it may be shifted
* up to almost a full limb to the left (worst case scenario), an extra limb
* need to be allocated.
*/
size_t snuml = ((nl <= dl) ? dl : nl) + 1;
OSSL_FN *snum = OSSL_FN_CTX_get_limbs(ctx, snuml);
if (!ossl_assert(snuml <= INT_MAX && snum != NULL))
goto err;
copy_align_left(snum, n, norm_shift);
/*
* Store a copy of the denominator in sdiv, shifted left so that its top bit
* is always 1. This is necessary to avoid gnarly arithmetic exceptions when
* the denominator's highest limb is a very small number.
*/
size_t sdivl = dl;
OSSL_FN *sdiv = OSSL_FN_CTX_get_limbs(ctx, sdivl);
if (!ossl_assert(sdivl <= INT_MAX && sdiv != NULL))
goto err;
copy_align_left(sdiv, d, norm_shift);
/*
* The number of times we will iterate to perform division, i.e.
* how often we will "shift" the divisor "window" over the numerator.
* This also determines the size of the result.
*
* For the math oriented:
*
* snuml - sdivl = ((nl <= dl) ? dl : nl) + 1 - dl
* => snuml - sdivl = ((nl <= dl) ? 0 : nl - dl) + 1
* => snuml - sdivl = (nl <= dl) ? 1 : nl - dl + 1
*/
size_t loop = snuml - sdivl;
/*
* Set up the quotient. It will be stored directly in |q| if it has
* enough space, otherwise temporary storage is allocated.
*/
OSSL_FN *res = (ql < loop) ? OSSL_FN_CTX_get_limbs(ctx, loop) : q;
if (!ossl_assert(res != NULL))
goto err;
/* Position of the next quotient limb to be calculated, plus one */
OSSL_FN_ULONG *resp = &(res->d[loop]);
/* Intermediary storage */
OSSL_FN *tmp = OSSL_FN_CTX_get_limbs(ctx, sdivl + 1);
if (!ossl_assert(tmp != NULL))
goto err;
/* Set up the "window" position in snum. */
OSSL_FN_ULONG *wnum = &(snum->d[loop]);
OSSL_FN_ULONG *wnumtop = &(snum->d[snuml - 1]);
/* Get the top 2 words of the denominator */
OSSL_FN_ULONG d0 = sdiv->d[sdivl - 1];
OSSL_FN_ULONG d1 = (sdivl == 1) ? 0 : sdiv->d[sdivl - 2];
size_t i;
/* If res is larger than the expected result, zero the limbs above */
for (i = res->dsize; i > loop;)
res->d[--i] = 0;
for (i = 0; i < loop; i++, wnumtop--) {
OSSL_FN_ULONG quo, l0;
/*
* the first part of the loop uses the top two words of snum and sdiv
* to calculate a OSSL_FN_ULONG quo such that | wnum - d * q | < d
*/
quo = div_words(wnumtop, wnum, d1, d0);
l0 = bn_mul_words(tmp->d, sdiv->d, (int)sdivl, quo);
tmp->d[sdivl] = l0;
wnum--;
/*
* ignore top values of the bignums just sub the two OSSL_FN_ULONG
* arrays with bn_sub_words
*/
l0 = bn_sub_words(wnum, wnum, tmp->d, (int)sdivl + 1);
quo -= l0;
/*
* Note: As we have considered only the leading two OSSL_FN_ULONGs
* in the calculation of q, d * q might be greater than wnum
* (but then (q-1) * d is less than or equal to wnum)
*/
size_t j;
for (l0 = 0 - l0, j = 0; j < sdivl; j++)
tmp->d[j] = sdiv->d[j] & l0;
l0 = bn_add_words(wnum, wnum, tmp->d, (int)sdivl);
(*wnumtop) += l0;
assert((*wnumtop) == 0);
/* store part of the result */
*--resp = quo;
}
/* snum holds remainder, it's as wide as divisor */
if (r != NULL)
copy_align_right(r, snum, norm_shift);
/* res holds the quotient for a total of loop limbs, and is separate from q if ql < loop */
if (q != NULL && q != res && OSSL_FN_copy_truncate(q, res) == 0)
goto err;
OSSL_FN_CTX_end(ctx, token);
return 1;
err:
OSSL_FN_CTX_end(ctx, token);
return 0;
}

42
crypto/fn/fn_err.c Normal file
View file

@ -0,0 +1,42 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
* Copyright 1995-2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <openssl/err.h>
#include "crypto/fnerr.h"
#ifndef OPENSSL_NO_ERR
static const ERR_STRING_DATA OSSL_FN_str_reasons[] = {
{ ERR_PACK(ERR_LIB_OSSL_FN, 0, OSSL_FN_R_DIV_BY_ZERO), "div by zero" },
{ ERR_PACK(ERR_LIB_OSSL_FN, 0, OSSL_FN_R_INVALID_RANGE),
"invalid range" },
{ ERR_PACK(ERR_LIB_OSSL_FN, 0, OSSL_FN_R_INPUT_NOT_REDUCED),
"input not reduced" },
{ ERR_PACK(ERR_LIB_OSSL_FN, 0, OSSL_FN_R_INVALID_SHIFT),
"invalid shift" },
{ ERR_PACK(ERR_LIB_OSSL_FN, 0, OSSL_FN_R_RESULT_ARG_TOO_SMALL),
"result arg too small" },
{ ERR_PACK(ERR_LIB_OSSL_FN, 0, OSSL_FN_R_BITS_TOO_SMALL),
"bits too small" },
{ ERR_PACK(ERR_LIB_OSSL_FN, 0, OSSL_FN_R_TOO_MANY_ITERATIONS),
"too many iterations" },
{ 0, NULL }
};
#endif
int ossl_err_load_OSSL_FN_strings(void)
{
#ifndef OPENSSL_NO_ERR
if (ERR_reason_error_string(OSSL_FN_str_reasons[0].error) == NULL)
ERR_load_strings_const(OSSL_FN_str_reasons);
#endif
return 1;
}

56
crypto/fn/fn_intern.c Normal file
View file

@ -0,0 +1,56 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "internal/cryptlib.h"
#include "crypto/fn_intern.h"
#include "crypto/fnerr.h"
#include "fn_local.h"
#include <openssl/err.h>
int ossl_fn_set_words(OSSL_FN *f, const OSSL_FN_ULONG *words, size_t limbs)
{
if (ossl_unlikely(f == NULL)) {
ERR_raise(ERR_LIB_OSSL_FN, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
if ((size_t)f->dsize < limbs) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_RESULT_ARG_TOO_SMALL);
return 0;
}
memcpy(f->d, words, sizeof(OSSL_FN_ULONG) * limbs);
memset(f->d + limbs, 0, sizeof(OSSL_FN_ULONG) * (f->dsize - limbs));
return 1;
}
const OSSL_FN_ULONG *ossl_fn_get_words(const OSSL_FN *f)
{
if (ossl_unlikely(f == NULL)) {
ERR_raise(ERR_LIB_OSSL_FN, ERR_R_PASSED_NULL_PARAMETER);
return NULL;
}
return f->d;
}
size_t ossl_fn_get_dsize(const OSSL_FN *f)
{
return f->dsize;
}
bool ossl_fn_is_dynamically_allocated(const OSSL_FN *f)
{
return f->is_dynamically_allocated;
}
bool ossl_fn_is_securely_allocated(const OSSL_FN *f)
{
return f->is_securely_allocated;
}

346
crypto/fn/fn_lib.c Normal file
View file

@ -0,0 +1,346 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <stdbool.h>
#include <limits.h>
#include <openssl/opensslconf.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include "internal/common.h"
#include "crypto/fnerr.h"
#include "fn_local.h"
#include "internal/constant_time.h"
static OSSL_FN *ossl_fn_new_internal(size_t limbs, bool securely)
{
/* Total size of the whole OSSL_FN, in bytes */
size_t totalsize = ossl_fn_totalsize(limbs);
if (totalsize == 0)
return NULL;
OSSL_FN *ret = NULL;
if (securely)
ret = OPENSSL_secure_zalloc(totalsize);
else
ret = OPENSSL_zalloc(totalsize);
if (ret != NULL) {
ret->dsize = (int)limbs;
ret->is_dynamically_allocated = 1;
ret->is_securely_allocated = securely;
}
return ret;
}
static void ossl_fn_free_internal(OSSL_FN *f, bool clear)
{
if (f == NULL)
return;
size_t limbssize = f->dsize * sizeof(OSSL_FN_ULONG);
size_t totalsize = limbssize + sizeof(OSSL_FN);
if (f->is_dynamically_allocated) {
if (f->is_securely_allocated)
OPENSSL_secure_clear_free(f, totalsize);
else if (clear)
OPENSSL_clear_free(f, totalsize);
else
OPENSSL_free(f);
} else if (clear) {
OPENSSL_cleanse(f->d, limbssize);
}
}
OSSL_FN *OSSL_FN_new_limbs(size_t size)
{
return ossl_fn_new_internal(size, false);
}
OSSL_FN *OSSL_FN_secure_new_limbs(size_t size)
{
return ossl_fn_new_internal(size, true);
}
OSSL_FN *OSSL_FN_new_bytes(size_t size)
{
return OSSL_FN_new_limbs(ossl_fn_bytes_to_limbs(size));
}
OSSL_FN *OSSL_FN_secure_new_bytes(size_t size)
{
return OSSL_FN_secure_new_limbs(ossl_fn_bytes_to_limbs(size));
}
OSSL_FN *OSSL_FN_new_bits(size_t size)
{
return OSSL_FN_new_bytes(ossl_fn_bits_to_bytes(size));
}
OSSL_FN *OSSL_FN_secure_new_bits(size_t size)
{
return OSSL_FN_secure_new_bytes(ossl_fn_bits_to_bytes(size));
}
void OSSL_FN_free(OSSL_FN *f)
{
ossl_fn_free_internal(f, false);
}
void OSSL_FN_clear_free(OSSL_FN *f)
{
ossl_fn_free_internal(f, true);
}
void OSSL_FN_clear(OSSL_FN *f)
{
size_t limbssize = f->dsize * sizeof(OSSL_FN_ULONG);
OPENSSL_cleanse(f->d, limbssize);
}
/*-
* Sets a->d[0] to |w| and zeroes the remaining limbs, so the full dsize
* array reflects the value |w|. OSSL_FN is fixed-size: if a->dsize is 0
* there is no limb to write and the call fails with
* OSSL_FN_R_RESULT_ARG_TOO_SMALL (the same reason ossl_fn_set_words() raises
* for an undersized destination).
*
* Constant-time with respect to |w|'s value: there is no value-dependent
* control flow, since the full dsize array always holds the value. The only
* branch is on the operand's public width (dsize).
*/
int OSSL_FN_set_word(OSSL_FN *a, OSSL_FN_ULONG w)
{
size_t dsize = (size_t)a->dsize;
if (ossl_unlikely(dsize < 1)) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_RESULT_ARG_TOO_SMALL);
return 0;
}
a->d[0] = w;
if (dsize > 1)
memset(&a->d[1], 0, sizeof(OSSL_FN_ULONG) * (dsize - 1));
return 1;
}
/*-
* Equivalent to OSSL_FN_set_word(a, 1). Kept as a named function rather
* than a macro or static inline, consistent with the rest of
* crypto/fn/fn_lib.c. Leak profile as for OSSL_FN_set_word().
*/
int OSSL_FN_one(OSSL_FN *a)
{
return OSSL_FN_set_word(a, OSSL_FN_ULONG_C(1));
}
/*-
* Equivalent to OSSL_FN_set_word(a, 0). This is a plain value assignment,
* not a secure wipe: the compiler may optimise the writes away if the value
* is not subsequently observed. Use OSSL_FN_clear() (which calls
* OPENSSL_cleanse()) when the limbs may hold secret data and must be wiped
* irreversibly. Leak profile as for OSSL_FN_set_word().
*/
int OSSL_FN_zero(OSSL_FN *a)
{
return OSSL_FN_set_word(a, OSSL_FN_ULONG_C(0));
}
static size_t ossl_fn_num_bits_word(OSSL_FN_ULONG l)
{
OSSL_FN_ULONG x, mask;
size_t bits = (size_t)constant_time_select_int(
(unsigned int)constant_time_is_zero_bn(l), 0, 1);
#if OSSL_FN_BITS > 32
x = l >> 32;
mask = ~constant_time_is_zero_bn(x);
bits += 32 & (size_t)mask;
l ^= (x ^ l) & mask;
#endif
x = l >> 16;
mask = ~constant_time_is_zero_bn(x);
bits += 16 & (size_t)mask;
l ^= (x ^ l) & mask;
x = l >> 8;
mask = ~constant_time_is_zero_bn(x);
bits += 8 & (size_t)mask;
l ^= (x ^ l) & mask;
x = l >> 4;
mask = ~constant_time_is_zero_bn(x);
bits += 4 & (size_t)mask;
l ^= (x ^ l) & mask;
x = l >> 2;
mask = ~constant_time_is_zero_bn(x);
bits += 2 & (size_t)mask;
l ^= (x ^ l) & mask;
x = l >> 1;
mask = ~constant_time_is_zero_bn(x);
bits += 1 & (size_t)mask;
return bits;
}
size_t OSSL_FN_num_bits(const OSSL_FN *a)
{
size_t i;
size_t dsize = (size_t)a->dsize;
size_t ret = 0;
for (i = 0; i < dsize; i++) {
size_t limb_bits = ossl_fn_num_bits_word(a->d[i]);
size_t bits = i * OSSL_FN_BITS + limb_bits;
size_t mask = (size_t)~constant_time_is_zero_bn(a->d[i]);
ret = constant_time_select_s(mask, bits, ret);
}
return ret;
}
int OSSL_FN_cmp(const OSSL_FN *a, const OSSL_FN *b)
{
size_t i;
size_t asize = (size_t)a->dsize;
size_t bsize = (size_t)b->dsize;
size_t max = asize > bsize ? asize : bsize;
int res = 0;
for (i = 0; i < max; i++) {
OSSL_FN_ULONG aw = i < asize ? a->d[i] : 0;
OSSL_FN_ULONG bw = i < bsize ? b->d[i] : 0;
res = constant_time_select_int(
(unsigned int)constant_time_lt_bn(aw, bw), -1, res);
res = constant_time_select_int(
(unsigned int)constant_time_lt_bn(bw, aw), 1, res);
}
return res;
}
/*-
* Returns bit |n| of |a|. An out-of-range index (n < 0 or n >= the
* operand's width in bits) reads as 0. The only control flow branches on
* the operand's public width (dsize); the returned value is the bit itself,
* which is the information the caller asked for.
*/
int OSSL_FN_is_bit_set(const OSSL_FN *a, int n)
{
size_t limb, off;
if (n < 0)
return 0;
limb = (size_t)n / OSSL_FN_BITS;
off = (size_t)n % OSSL_FN_BITS;
if (limb >= (size_t)a->dsize)
return 0;
return (a->d[limb] >> off) & OSSL_FN_ULONG_C(1);
}
/*-
* Returns 1 if the unsigned value of |a| equals the single-limb word |w|.
* Control flow branches only on the operand's public width (dsize); limb
* values are combined with constant-time selects, so the number of limbs
* inspected depends only on the public width, not on the operand's value.
* The returned value is the equality test the caller asked for.
*/
int OSSL_FN_is_word(const OSSL_FN *a, OSSL_FN_ULONG w)
{
size_t i;
size_t dsize = (size_t)a->dsize;
int res;
if (dsize == 0)
return w == 0;
res = constant_time_select_int(
(unsigned int)constant_time_eq_bn(a->d[0], w), 1, 0);
for (i = 1; i < dsize; i++)
res = constant_time_select_int(
(unsigned int)constant_time_is_zero_bn(a->d[i]), res, 0);
return res;
}
/*-
* Equivalent to OSSL_FN_is_word(a, 0), kept as a named predicate for
* readability at call sites. Leak profile as for OSSL_FN_is_word():
* branches only on the operand's public width (dsize).
*/
int OSSL_FN_is_zero(const OSSL_FN *a)
{
return OSSL_FN_is_word(a, 0);
}
/*-
* Equivalent to OSSL_FN_is_word(a, 1), kept as a named predicate for
* readability at call sites. Leak profile as for OSSL_FN_is_word():
* branches only on the operand's public width (dsize).
*/
int OSSL_FN_is_one(const OSSL_FN *a)
{
return OSSL_FN_is_word(a, 1);
}
/*-
* Returns the least significant bit of |a|, which is the information the
* caller asked for. The only control flow branches on the operand's public
* width (dsize), not on limb values.
*/
int OSSL_FN_is_odd(const OSSL_FN *a)
{
if (a->dsize <= 0)
return 0;
return (int)(a->d[0] & OSSL_FN_ULONG_C(1));
}
OSSL_FN *OSSL_FN_copy(OSSL_FN *a, const OSSL_FN *b)
{
if (ossl_unlikely(a == b))
return a;
size_t al = a->dsize;
size_t bl = b->dsize;
if (al < bl) {
ERR_raise_data(ERR_LIB_OSSL_FN, OSSL_FN_R_RESULT_ARG_TOO_SMALL,
"Needs to be at least %zu bytes, but is only %zu bytes",
bl * sizeof(OSSL_FN_ULONG), al * sizeof(OSSL_FN_ULONG));
return 0;
}
memcpy(a->d, b->d, bl * sizeof(OSSL_FN_ULONG));
memset(a->d + bl, 0, (al - bl) * sizeof(OSSL_FN_ULONG));
return a;
}
OSSL_FN *OSSL_FN_copy_truncate(OSSL_FN *a, const OSSL_FN *b)
{
if (ossl_unlikely(a == b))
return a;
size_t al = a->dsize;
size_t bl = b->dsize;
if (ossl_unlikely(al > bl)) {
memcpy(a->d, b->d, bl * sizeof(OSSL_FN_ULONG));
memset(&a->d[bl], 0, sizeof(OSSL_FN_ULONG) * (al - bl));
} else {
memcpy(a->d, b->d, al * sizeof(OSSL_FN_ULONG));
}
return a;
}

182
crypto/fn/fn_local.h Normal file
View file

@ -0,0 +1,182 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifndef OSSL_CRYPTO_FN_LOCAL_H
#define OSSL_CRYPTO_FN_LOCAL_H
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <openssl/opensslconf.h>
#include <openssl/e_os2.h>
#include "internal/common.h"
#include "crypto/fn.h"
#include "crypto/fn_intern.h"
#if OSSL_FN_BYTES == 4
/* 32-bit systems */
#define OSSL_FN_ULONG_C(n) UINT32_C(n)
#define OSSL_FN_MASK UINT32_MAX
#elif OSSL_FN_BYTES == 8
#define OSSL_FN_ULONG_C(n) UINT64_C(n)
#define OSSL_FN_MASK UINT64_MAX
#else
#error "OpenSSL doesn't support large numbers on this platform"
#endif
#define OSSL_FN_BITS (OSSL_FN_BYTES * 8)
#define OSSL_FN_HIGH_BIT_MASK (OSSL_FN_ULONG_C(1) << (OSSL_FN_BITS - 1))
#define OSSL_FN_LOW_HALF_MASK ((OSSL_FN_ULONG_C(1) << (OSSL_FN_BITS / 2)) - 1)
#define OSSL_FN_HIGH_HALF_MASK (OSSL_FN_LOW_HALF_MASK << (OSSL_FN_BITS / 2))
struct ossl_fn_st {
/* Flag: alloced with OSSL_FN_new() or OSSL_FN_secure_new() */
unsigned int is_dynamically_allocated : 1;
/* Flag: alloced with OSSL_FN_secure_new() */
unsigned int is_securely_allocated : 1;
/*
* The d array, with its size in number of OSSL_FN_ULONG.
* This stores the number itself.
*
* Note: |dsize| is an int, because it turns out that some lower level
* (possibly assembler) functions expect that type (especially, that
* type size).
* This deviates from the design in doc/designs/fixed-size-large-numbers.md
*/
int dsize;
OSSL_FN_ULONG d[];
};
static ossl_inline size_t ossl_fn_totalsize(size_t limbs)
{
/*
* TODO(FIXNUM): Since the number of limbs is currently represented
* as an 'int' in OSSL_FN, we must ensure that the desired size isn't
* larger than can be represented.
*/
if (ossl_unlikely(limbs >= INT_MAX))
return 0;
/*
* sizeof(OSSL_FN) + limbs * sizeof(OSSL_FN_ULONG) > SIZE_MAX
* => limbs * sizeof(OSSL_FN_ULONG) > SIZE_MAX - sizeof(OSSL_FN)
* => limbs > (SIZE_MAX - sizeof(OSSL_FN)) / sizeof(OSSL_FN_ULONG)
*/
if (ossl_unlikely(limbs > (SIZE_MAX - sizeof(OSSL_FN)) / sizeof(OSSL_FN_ULONG)))
return 0;
return sizeof(OSSL_FN) + limbs * sizeof(OSSL_FN_ULONG);
}
static ossl_inline size_t ossl_fn_bytes_to_limbs(size_t size)
{
return (size + sizeof(OSSL_FN_ULONG) - 1) / sizeof(OSSL_FN_ULONG);
}
static ossl_inline size_t ossl_fn_bits_to_bytes(size_t size)
{
return (size + 7) / 8;
}
/*
* Internal functions to support BIGNUM's bn_expand_internal, BN_copy, and
* similar.
* The caller must ensure that src and dest are not NULL.
* With ossl_fn_copy_internal, bn_words may be given -1 to signify that the
* number of BN_ULONG should be found in src.
*/
static ossl_inline OSSL_FN *ossl_fn_copy_internal_limbs(OSSL_FN *dest,
const OSSL_FN_ULONG *src,
int limbs)
{
if (ossl_unlikely(dest->dsize < limbs))
return NULL;
memcpy(dest->d, src, limbs * sizeof(dest->d[0]));
memset(dest->d + limbs, 0, (dest->dsize - limbs) * sizeof(dest->d[0]));
return dest;
}
static ossl_inline OSSL_FN *ossl_fn_copy_internal(OSSL_FN *dest,
const OSSL_FN *src,
int bn_words)
{
int words = bn_words < 0 ? src->dsize : bn_words;
if (ossl_fn_copy_internal_limbs(dest, src->d, words) == NULL)
return NULL;
return dest;
}
/* OSSL_FN_CTX internals */
struct ossl_fn_ctx_st {
/*
* Pointer to the last OSSL_FN_CTX_start() location (a simple pointer into
* the memory area). See the struct ossl_fn_ctx_frame_st definition below
* for details.
*/
struct ossl_fn_ctx_frame_st *last_frame;
/*
* Flags
*/
unsigned int is_securely_allocated : 1;
/*
* Current and peak usage tracking, by allocation components.
* The |n_*| fields hold the currently active counts; the |peak_n_*|
* fields hold the maximum each count has ever reached simultaneously.
* This allows callers to determine suitable arena parameters for a
* given workload without precise up-front prediction.
*/
size_t n_frames;
size_t n_numbers;
size_t n_limbs;
size_t peak_n_frames;
size_t peak_n_numbers;
size_t peak_n_limbs;
/*
* The arena itself.
*/
size_t msize; /* Size of the arena, in bytes */
unsigned char memory[];
};
struct ossl_fn_ctx_frame_st {
/*
* Pointer back to the whole arena where the frame is located,
* for |last_frame| bookkeeping.
*/
struct ossl_fn_ctx_st *arena;
/*
* Pointer to the previous frame in the arena, allowing OSSL_FN_CTX_end()
* to do its job.
*/
struct ossl_fn_ctx_frame_st *previous_frame;
/*
* Tracking for peak usage instrumentation. These count the OSSL_FN
* instances and total limbs allocated within this frame.
*/
size_t n_numbers;
size_t n_limbs;
/*
* Every time OSSL_FN_CTX_get() is called, the current value of
* |free_memory| is returned, and it's updated by incrementing it
* by the number of bytes given by OSSL_FN_CTX_get().
* The available number of bytes is limited by what's left in the arena.
*/
unsigned char *free_memory; /* Pointer to the free area of the frame */
size_t msize; /* Size of the frame, in bytes */
unsigned char memory[];
};
/* end OSSL_FN_CTX internals */
#endif

564
crypto/fn/fn_mod.c Normal file
View file

@ -0,0 +1,564 @@
/*
* Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "internal/cryptlib.h"
#include "internal/nelem.h"
#include "internal/safe_math.h"
#include "crypto/fnerr.h"
#include "fn_local.h"
OSSL_SAFE_MATH_ADDU(size_t, size_t, OSSL_SAFE_MATH_MAXU(size_t))
OSSL_SAFE_MATH_MULU(size_t, size_t, OSSL_SAFE_MATH_MAXU(size_t))
static size_t ctx_add_size(size_t a, size_t b)
{
int err = 0;
size_t r = safe_add_size_t(a, b, &err);
return err == 0 ? r : 0;
}
static size_t ctx_max_size(size_t a, size_t b)
{
return a > b ? a : b;
}
/*
* The *_ctx_size helpers below use local OSSL_FN headers with only |dsize|
* set to represent temporaries that the corresponding operation allocates
* with OSSL_FN_CTX_get_limbs(). This is enough for nested ctx-size helpers,
* which only inspect operand sizes.
*/
size_t OSSL_FN_mod_add_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b, const OSSL_FN *m)
{
size_t tl, own_size, nested_size;
if (r == NULL || a == NULL || b == NULL || m == NULL)
return 0;
int err = 0;
tl = safe_add_size_t(a->dsize > b->dsize ? a->dsize : b->dsize,
1, &err);
if (err != 0 || ossl_fn_totalsize(tl) == 0)
return 0;
OSSL_FN t = { .dsize = (int)tl };
own_size = OSSL_FN_CTX_size(1, 1, tl);
nested_size = OSSL_FN_mod_ctx_size(r, &t, m);
if (own_size == 0 || nested_size == 0)
return 0;
return ctx_add_size(own_size, nested_size);
}
int OSSL_FN_mod_add(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m, OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
OSSL_FN *t;
int ret = 0;
size_t tl = (a->dsize > b->dsize ? a->dsize : b->dsize) + 1;
if (token == NULL)
return 0;
if ((t = OSSL_FN_CTX_get_limbs(ctx, tl)) == NULL)
goto err;
ret = OSSL_FN_add(t, a, b)
&& OSSL_FN_mod(r, t, m, ctx);
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}
/*
* OSSL_FN_mod_add variant that may be used if both a and b are less than m.
* The original formula is:
*
* r' = a + b
* r = r m[r m]
*
* This is replaced with addition, subtracting modulus, and conditional move
* depending on whether or not subtraction borrowed.
*/
int OSSL_FN_mod_add_quick(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m)
{
size_t al = a->dsize;
size_t bl = b->dsize;
size_t rl = r->dsize;
size_t ml = m->dsize;
size_t aw = al < ml ? al : ml;
size_t bw = bl < ml ? bl : ml;
OSSL_FN_ULONG storage[2 * 1024 / OSSL_FN_BITS];
OSSL_FN_ULONG *tp = storage;
OSSL_FN_ULONG *mp = storage + ml;
OSSL_FN_ULONG carry, borrow;
size_t i;
if (2 * ml > OSSL_NELEM(storage)) {
tp = OPENSSL_malloc_array(2 * ml, sizeof(OSSL_FN_ULONG));
if (tp == NULL)
return 0;
mp = tp + ml;
}
/* tp = a + b mod 2^(ml*bits) */
carry = ossl_fn_add_words(tp, ml, a->d, aw, b->d, bw);
/* mp = tp - m mod 2^(ml*bits) */
borrow = ossl_fn_sub_words(mp, ml, tp, ml, m->d, ml);
/*
* Because a, b < m, we have a + b < 2m. Therefore tp < m whenever
* carry = 1, which forces borrow = 1. The mask carry borrow thus
* only produces 0 (select tp2) or ~0 (select tp), matching exactly
* whether a + b m.
*
* Thus, we have the cases:
*
* a + b < m => carry == 0, borrow == 1
* m <= a+b < 2^(ml*bits) => carry == 0, borrow == 0
* 2^(ml*bits) <= a+b < 2m => carry == 1, borrow == 1
*
* If (a + b < m), select tp; otherwise select tp2. Done with the
* help of a mask.
*/
OSSL_FN_ULONG mask = carry - borrow;
size_t end = (rl < ml) ? rl : ml;
for (i = 0; i < end; i++)
r->d[i] = (mask & tp[i]) | (~mask & mp[i]);
/* Make sure to pad r with zeroes when rl > ml */
for (; i < rl; i++)
r->d[i] = 0;
if (tp != storage)
OPENSSL_clear_free(tp, 2 * ml * sizeof(OSSL_FN_ULONG));
else
OPENSSL_cleanse(storage, sizeof(storage));
return 1;
}
size_t OSSL_FN_mod_sub_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b, const OSSL_FN *m)
{
size_t ml, n_numbers, own_size, mod_a_size, mod_b_size, nested_size;
if (r == NULL || a == NULL || b == NULL || m == NULL)
return 0;
ml = m->dsize;
n_numbers = (r == m) ? 3 : 2;
if (ossl_fn_totalsize(ml) == 0)
return 0;
OSSL_FN am = { .dsize = (int)ml };
OSSL_FN bm = { .dsize = (int)ml };
own_size = OSSL_FN_CTX_size(1, n_numbers, n_numbers * ml);
mod_a_size = OSSL_FN_mod_ctx_size(&am, a, m);
mod_b_size = OSSL_FN_mod_ctx_size(&bm, b, m);
nested_size = ctx_max_size(mod_a_size, mod_b_size);
if (own_size == 0 || nested_size == 0)
return 0;
return ctx_add_size(own_size, nested_size);
}
int OSSL_FN_mod_sub(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m, OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
OSSL_FN *am, *bm, *rr = r;
int ret = 0;
if (token == NULL)
return 0;
if ((am = OSSL_FN_CTX_get_limbs(ctx, m->dsize)) == NULL
|| (bm = OSSL_FN_CTX_get_limbs(ctx, m->dsize)) == NULL)
goto err;
if (r == m && (rr = OSSL_FN_CTX_get_limbs(ctx, m->dsize)) == NULL)
goto err;
ret = OSSL_FN_mod(am, a, m, ctx)
&& OSSL_FN_mod(bm, b, m, ctx)
&& OSSL_FN_mod_sub_quick(rr, am, bm, m)
&& (rr == r || OSSL_FN_copy_truncate(r, rr) != NULL);
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}
/*
* OSSL_FN_mod_sub variant that may be used if a is less than m, while b is
* of same bit width as m. It's implemented as subtraction followed by two
* conditional additions.
*
* 0 <= a < m
* 0 <= b < 2^w < 2*m
*
* after subtraction
*
* -2*m < r = a - b < m
*
* The original formula is:
*
* r' = a - b
* r'' = r' + m[r' < 0]
* r = r' + m[r' < 0]
*
* Because masking techniques are used, this is most efficiently
* carried out with local loops rather than calling functions like
* ossl_fn_add_words().
*/
int OSSL_FN_mod_sub_quick(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m)
{
if (r == m) {
ERR_raise(ERR_LIB_OSSL_FN, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
size_t al = a->dsize;
size_t bl = b->dsize;
size_t rl = r->dsize;
size_t ml = m->dsize;
size_t aw = al < ml ? al : ml;
size_t bw = bl < ml ? bl : ml;
OSSL_FN_ULONG storage[1024 / OSSL_FN_BITS];
OSSL_FN_ULONG *tp = storage;
size_t i;
OSSL_FN_ULONG borrow, carry, ta, mask;
if (ml > OSSL_NELEM(storage)) {
tp = OPENSSL_malloc_array(ml, sizeof(OSSL_FN_ULONG));
if (tp == NULL)
return 0;
}
/* tp = a - b mod 2^(ml*bits) */
borrow = ossl_fn_sub_words(tp, ml, a->d, aw, b->d, bw);
/* If borrow, add m */
for (i = 0, mask = 0 - borrow, carry = 0; i < ml; i++) {
ta = ((m->d[i] & mask) + carry) & OSSL_FN_MASK;
carry = (ta < carry);
tp[i] = (tp[i] + ta) & OSSL_FN_MASK;
carry += (tp[i] < ta);
}
/* If still borrow, add m again */
borrow -= carry;
for (i = 0, mask = 0 - borrow, carry = 0; i < ml; i++) {
ta = ((m->d[i] & mask) + carry) & OSSL_FN_MASK;
carry = (ta < carry);
tp[i] = (tp[i] + ta) & OSSL_FN_MASK;
carry += (tp[i] < ta);
}
for (i = 0; i < rl && i < ml; i++)
r->d[i] = tp[i];
/* Make sure to pad r with zeroes when rl > ml */
for (; i < rl; i++)
r->d[i] = 0;
if (tp != storage)
OPENSSL_clear_free(tp, ml * sizeof(OSSL_FN_ULONG));
else
OPENSSL_cleanse(storage, sizeof(storage));
return 1;
}
size_t OSSL_FN_mod_mul_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b, const OSSL_FN *m)
{
size_t tl, own_size, mul_size, mod_size, nested_size;
if (r == NULL || a == NULL || b == NULL || m == NULL)
return 0;
if (a == b) {
int err = 0;
tl = safe_mul_size_t(2, a->dsize, &err);
if (err != 0 || ossl_fn_totalsize(tl) == 0)
return 0;
OSSL_FN t = { .dsize = (int)tl };
mul_size = OSSL_FN_sqr_ctx_size(&t, a);
mod_size = OSSL_FN_mod_ctx_size(r, &t, m);
} else {
int err = 0;
tl = safe_add_size_t(a->dsize, b->dsize, &err);
if (err != 0 || ossl_fn_totalsize(tl) == 0)
return 0;
OSSL_FN t = { .dsize = (int)tl };
mul_size = OSSL_FN_mul_ctx_size(&t, a, b);
mod_size = OSSL_FN_mod_ctx_size(r, &t, m);
}
own_size = OSSL_FN_CTX_size(1, 1, tl);
nested_size = ctx_max_size(mul_size, mod_size);
if (own_size == 0 || nested_size == 0)
return 0;
return ctx_add_size(own_size, nested_size);
}
/* slow but works */
int OSSL_FN_mod_mul(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m, OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
OSSL_FN *t;
int ret = 0;
if (token == NULL)
return 0;
if (a == b) {
size_t tl = 2 * a->dsize;
if ((t = OSSL_FN_CTX_get_limbs(ctx, tl)) == NULL
|| !OSSL_FN_sqr(t, a, ctx))
goto err;
} else {
size_t tl = a->dsize + b->dsize;
if ((t = OSSL_FN_CTX_get_limbs(ctx, tl)) == NULL
|| !OSSL_FN_mul(t, a, b, ctx))
goto err;
}
if (!OSSL_FN_mod(r, t, m, ctx))
goto err;
ret = 1;
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}
size_t OSSL_FN_mod_sqr_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *m)
{
size_t tl, own_size, sqr_size, mod_size, nested_size;
if (r == NULL || a == NULL || m == NULL)
return 0;
int err = 0;
tl = safe_mul_size_t(2, a->dsize, &err);
if (err != 0 || ossl_fn_totalsize(tl) == 0)
return 0;
OSSL_FN t = { .dsize = (int)tl };
own_size = OSSL_FN_CTX_size(1, 1, tl);
sqr_size = OSSL_FN_sqr_ctx_size(&t, a);
mod_size = OSSL_FN_mod_ctx_size(r, &t, m);
nested_size = ctx_max_size(sqr_size, mod_size);
if (own_size == 0 || nested_size == 0)
return 0;
return ctx_add_size(own_size, nested_size);
}
int OSSL_FN_mod_sqr(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *m,
OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
OSSL_FN *t;
int ret = 0;
if (token == NULL)
return 0;
if ((t = OSSL_FN_CTX_get_limbs(ctx, (size_t)(2 * a->dsize))) == NULL)
goto err;
ret = OSSL_FN_sqr(t, a, ctx)
&& OSSL_FN_mod(r, t, m, ctx);
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}
size_t OSSL_FN_mod_lshift1_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *m)
{
size_t tl, own_size, nested_size;
if (r == NULL || a == NULL || m == NULL)
return 0;
int err = 0;
tl = safe_add_size_t(m->dsize, 1, &err);
if (err != 0 || ossl_fn_totalsize(tl) == 0)
return 0;
OSSL_FN t = { .dsize = (int)tl };
own_size = OSSL_FN_CTX_size(1, 1, tl);
nested_size = OSSL_FN_mod_ctx_size(r, &t, m);
if (own_size == 0 || nested_size == 0)
return 0;
return ctx_add_size(own_size, nested_size);
}
int OSSL_FN_mod_lshift1(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *m,
OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
OSSL_FN *t;
int ret = 0;
if (token == NULL)
return 0;
if ((t = OSSL_FN_CTX_get_limbs(ctx, (size_t)(m->dsize + 1))) == NULL)
goto err;
ret = OSSL_FN_lshift1(t, a)
&& OSSL_FN_mod(r, t, m, ctx);
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}
/* OSSL_FN_mod_lshift1 variant that may be used if a is less than m */
int OSSL_FN_mod_lshift1_quick(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *m)
{
OSSL_FN *t = OSSL_FN_new_limbs((size_t)(m->dsize + 1));
int ret = 0;
if (t == NULL)
return 0;
if (!OSSL_FN_lshift1(t, a))
goto err;
if (OSSL_FN_cmp(t, m) >= 0) {
if (!OSSL_FN_sub(t, t, m))
goto err;
}
OSSL_FN_copy_truncate(r, t);
ret = 1;
err:
OSSL_FN_free(t);
return ret;
}
size_t OSSL_FN_mod_lshift_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
int n, const OSSL_FN *m)
{
size_t ml, own_size, nested_size;
if (r == NULL || a == NULL || m == NULL)
return 0;
(void)n;
ml = m->dsize;
if (ossl_fn_totalsize(ml) == 0)
return 0;
OSSL_FN ra = { .dsize = (int)ml };
own_size = OSSL_FN_CTX_size(1, 1, ml);
nested_size = OSSL_FN_mod_ctx_size(&ra, a, m);
if (own_size == 0 || nested_size == 0)
return 0;
return ctx_add_size(own_size, nested_size);
}
int OSSL_FN_mod_lshift(OSSL_FN *r, const OSSL_FN *a, int n, const OSSL_FN *m,
OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
OSSL_FN *ra;
int ret = 0;
if (token == NULL)
return 0;
if ((ra = OSSL_FN_CTX_get_limbs(ctx, m->dsize)) == NULL)
goto err;
ret = OSSL_FN_mod(ra, a, m, ctx)
&& OSSL_FN_mod_lshift_quick(r, ra, n, m);
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}
/* OSSL_FN_mod_lshift variant that may be used if a is less than m */
int OSSL_FN_mod_lshift_quick(OSSL_FN *r, const OSSL_FN *a, int n,
const OSSL_FN *m)
{
OSSL_FN *t = NULL;
int ret = 0;
if (n <= 0)
return n == 0 ? (OSSL_FN_copy_truncate(r, a) != NULL) : 0;
t = OSSL_FN_new_limbs((size_t)(m->dsize + 1));
if (t == NULL)
goto err;
if (OSSL_FN_copy_truncate(t, a) == NULL)
goto err;
while (n > 0) {
size_t m_bits = OSSL_FN_num_bits(m);
size_t t_bits = OSSL_FN_num_bits(t);
size_t max_shift;
/* 0 <= t < m */
if (m_bits < t_bits) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_INPUT_NOT_REDUCED);
goto err;
}
max_shift = m_bits - t_bits;
if (max_shift > (size_t)n)
max_shift = (size_t)n;
if (max_shift) {
int shift = (int)max_shift;
if (!OSSL_FN_lshift(t, t, shift))
goto err;
n -= shift;
} else {
if (!OSSL_FN_lshift1(t, t))
goto err;
n--;
}
if (OSSL_FN_cmp(t, m) >= 0) {
if (!OSSL_FN_sub(t, t, m))
goto err;
}
}
if (OSSL_FN_copy_truncate(r, t) == NULL)
goto err;
ret = 1;
err:
OSSL_FN_free(t);
return ret;
}

78
crypto/fn/fn_mul.c Normal file
View file

@ -0,0 +1,78 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <assert.h>
#include <openssl/err.h>
#include "crypto/cryptlib.h"
#include "crypto/fnerr.h"
#include "../bn/bn_local.h" /* For using the low level bignum functions */
#include "fn_local.h"
size_t OSSL_FN_mul_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b)
{
size_t limbs = 0;
if (r == NULL || a == NULL || b == NULL)
return 0;
if (r == a || r == b)
limbs = r->dsize;
return OSSL_FN_CTX_size(1, limbs == 0 ? 0 : 1, limbs);
}
int OSSL_FN_mul(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b, OSSL_FN_CTX *ctx)
{
size_t al = (size_t)a->dsize;
size_t bl = (size_t)b->dsize;
size_t rl = (size_t)r->dsize;
size_t max = (size_t)(al + bl);
const void *token = OSSL_FN_CTX_start(ctx);
if (token == NULL)
return 0;
int ret = 0;
#ifdef BN_MUL_COMBA
if (al == bl) {
if (rl >= 16 && al == 8) {
bn_mul_comba8(r->d, a->d, b->d);
goto end;
}
}
#endif /* BN_MUL_COMBA */
OSSL_FN *rr = r;
if ((r == a) || (r == b))
if ((rr = OSSL_FN_CTX_get_limbs(ctx, rl)) == NULL)
goto err;
bn_mul_truncated(rr->d, (int)rl, a->d, (int)al, b->d, (int)bl);
if (rr != r)
if (OSSL_FN_copy(r, rr) == NULL)
goto err;
#ifdef BN_MUL_COMBA
end:
#endif
{
size_t dif = (rl > max) ? rl - max : 0;
OSSL_FN_ULONG *rp = &r->d[max];
while (dif > 0) {
*rp++ = 0;
dif--;
}
}
ret = 1;
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}

246
crypto/fn/fn_rand.c Normal file
View file

@ -0,0 +1,246 @@
/*
* Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <openssl/err.h>
#include <openssl/rand.h>
#include "crypto/fnerr.h"
#include "fn_local.h"
/*
* Internal flag selecting which DRBG pool the bytes are drawn from.
* NORMAL routes through RAND_bytes_ex(), PRIVATE through
* RAND_priv_bytes_ex() (a non-forward-linkable source). The same shaping
* code feeds either pool; the flag selects which one, mirroring the
* public/private split exposed by the OSSL_FN_rand() / OSSL_FN_priv_rand()
* entry points.
*/
enum ossl_fn_rand_flag {
NORMAL = 0,
PRIVATE
};
/* Set bit |pos| (0 = least significant) of |a|, by absolute position. */
static void ossl_fn_set_bit(OSSL_FN *a, size_t pos)
{
a->d[pos / OSSL_FN_BITS] |= OSSL_FN_ULONG_C(1) << (pos % OSSL_FN_BITS);
}
/*-
* ossl_fn_rand() fills |rnd| with |bits| random bits, shaping the top and
* bottom bits per the |top|/|bottom| requests. The random bytes are drawn
* directly into rnd->d's byte image (a whole number of limbs, so the result
* is a random value regardless of the machine's byte order), and the
* top/bottom/mask shaping is done directly on rnd->d's limbs as value
* operations -- set bit |bits|-1 for TOP_ONE, bits |bits|-1 and |bits|-2
* for TOP_TWO, clear the high bits of the top limb at |bits| and above,
* set bit 0 for BOTTOM_ODD. No intermediate byte buffer is needed, since
* OSSL_FN's limbs are fixed-size.
*
* A destination too small for |bits| is an error
* (OSSL_FN_R_RESULT_ARG_TOO_SMALL), not an implicit expansion.
*
* The leak profile: control flow branches on |bits|, |top|, |bottom| (all
* caller-chosen, public) and on the byte-draw return value, never on the
* random bytes themselves. The result value of OSSL_FN_rand() /
* OSSL_FN_priv_rand() is, of course, the random number the caller asked for.
*/
static int ossl_fn_rand(enum ossl_fn_rand_flag flag, OSSL_FN *rnd, size_t bits,
int top, int bottom, size_t strength,
OSSL_LIB_CTX *libctx)
{
size_t limbs_needed, top_limb, i;
if (rnd == NULL) {
ERR_raise(ERR_LIB_OSSL_FN, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
if (bits == 0) {
if (top != OSSL_FN_RAND_TOP_ANY || bottom != OSSL_FN_RAND_BOTTOM_ANY)
goto toosmall;
return OSSL_FN_zero(rnd);
}
/* TOP_TWO forces two high bits, so it needs at least two bits. */
if (top == OSSL_FN_RAND_TOP_TWO && bits < 2)
goto toosmall;
limbs_needed = bits / OSSL_FN_BITS;
limbs_needed += (bits % OSSL_FN_BITS != 0) ? 1 : 0;
if (limbs_needed > (size_t)rnd->dsize) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_RESULT_ARG_TOO_SMALL);
return 0;
}
top_limb = limbs_needed - 1;
/*
* Draw random bytes directly into rnd->d's byte image. A whole number
* of limbs is filled so the result is a random value regardless of the
* machine's byte order; the high bits of the top limb beyond |bits| are
* masked off below. The pool is selected by |flag|.
*/
{
size_t fill_bytes = limbs_needed * OSSL_FN_BYTES;
int b = (flag == NORMAL)
? RAND_bytes_ex(libctx, (unsigned char *)rnd->d, fill_bytes,
(unsigned int)strength)
: RAND_priv_bytes_ex(libctx, (unsigned char *)rnd->d, fill_bytes,
(unsigned int)strength);
if (b <= 0)
return 0;
}
/*
* TODO(FIXNUM): a testing variant that mangles the byte buffer to
* generate patterns more likely to trigger library bugs is not wired up
* yet; if an OSSL_FN_bntest_rand() analogue is added for test coverage,
* this is the spot for the mangle step.
*/
/* Zero any limbs above those the bytes filled. */
for (i = limbs_needed; i < (size_t)rnd->dsize; i++)
rnd->d[i] = 0;
/* Clear the high bits of the top limb at |bits| and above. */
if (bits % OSSL_FN_BITS != 0)
rnd->d[top_limb] &= (OSSL_FN_ULONG_C(1) << (bits % OSSL_FN_BITS)) - 1;
/* Set the requested top bit(s); |bits| >= 2 is guaranteed for TOP_TWO. */
if (top != OSSL_FN_RAND_TOP_ANY) {
ossl_fn_set_bit(rnd, bits - 1);
if (top == OSSL_FN_RAND_TOP_TWO)
ossl_fn_set_bit(rnd, bits - 2);
}
/* Set the bottom bit if requested. */
if (bottom == OSSL_FN_RAND_BOTTOM_ODD)
rnd->d[0] |= OSSL_FN_ULONG_C(1);
return 1;
toosmall:
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_BITS_TOO_SMALL);
return 0;
}
/* Draw from the public DRBG pool (NORMAL). */
int OSSL_FN_rand(OSSL_FN *rnd, size_t bits, int top, int bottom,
size_t strength, OSSL_LIB_CTX *libctx)
{
return ossl_fn_rand(NORMAL, rnd, bits, top, bottom, strength, libctx);
}
/* Draw from the private DRBG pool (PRIVATE). */
int OSSL_FN_priv_rand(OSSL_FN *rnd, size_t bits, int top, int bottom,
size_t strength, OSSL_LIB_CTX *libctx)
{
return ossl_fn_rand(PRIVATE, rnd, bits, top, bottom, strength, libctx);
}
/*-
* ossl_fn_rand_range() produces 0 <= r < range by rejection sampling. The
* libctx comes directly as an argument, as in ossl_fn_rand(); sign is never
* considered, since OSSL_FN is unsigned.
*
* The leak profile: control flow branches on |range|'s top bit pattern and on
* |r|'s width (both public), and the loop iteration count leaks the magnitude
* of |range| (via OSSL_FN_num_bits) and the rejection probability.
*
* The destination |r| must be sized to hold at least |num_bits(range)| bits.
* The "range = 100..._2" path draws n + 1 bits and is taken only when |r|
* has room for them; an exactly-sized |r| (room for exactly n bits) uses the
* standard n-bit rejection path instead.
*/
static int ossl_fn_rand_range(enum ossl_fn_rand_flag flag, OSSL_FN *r,
const OSSL_FN *range, size_t strength,
OSSL_LIB_CTX *libctx)
{
size_t n;
int count = 100;
if (r == NULL) {
ERR_raise(ERR_LIB_OSSL_FN, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
if (OSSL_FN_is_zero(range)) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_INVALID_RANGE);
return 0;
}
n = OSSL_FN_num_bits(range); /* n > 0 */
/* OSSL_FN_is_bit_set(range, n - 1) always holds */
if (n == 1) {
return OSSL_FN_zero(r);
} else if (!OSSL_FN_is_bit_set(range, (int)(n - 2))
&& !OSSL_FN_is_bit_set(range, (int)(n - 3))
&& n < (size_t)r->dsize * OSSL_FN_BITS) {
/*
* range = 100..._2, so 3*range (= 11..._2) is exactly one bit longer
* than range. This draws n + 1 bits, so it is taken only when |r| has
* room for them; an exactly-sized |r| (room for exactly n bits) falls
* through to the standard n-bit rejection path below.
*/
do {
if (!ossl_fn_rand(flag, r, n + 1, OSSL_FN_RAND_TOP_ANY,
OSSL_FN_RAND_BOTTOM_ANY, strength, libctx))
return 0;
/*
* If r < 3*range, use r := r MOD range (which is either r, r -
* range, or r - 2*range). Otherwise, iterate once more. Since
* 3*range = 11..._2, each iteration succeeds with probability >=
* .75.
*/
if (OSSL_FN_cmp(r, range) >= 0) {
if (!OSSL_FN_sub(r, r, range))
return 0;
if (OSSL_FN_cmp(r, range) >= 0)
if (!OSSL_FN_sub(r, r, range))
return 0;
}
if (!--count) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_TOO_MANY_ITERATIONS);
return 0;
}
} while (OSSL_FN_cmp(r, range) >= 0);
} else {
do {
/* range = 11..._2 or range = 101..._2 */
if (!ossl_fn_rand(flag, r, n, OSSL_FN_RAND_TOP_ANY,
OSSL_FN_RAND_BOTTOM_ANY, strength, libctx))
return 0;
if (!--count) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_TOO_MANY_ITERATIONS);
return 0;
}
} while (OSSL_FN_cmp(r, range) >= 0);
}
return 1;
}
/* Draw from the public DRBG pool (NORMAL). */
int OSSL_FN_rand_range(OSSL_FN *r, const OSSL_FN *range, size_t strength,
OSSL_LIB_CTX *libctx)
{
return ossl_fn_rand_range(NORMAL, r, range, strength, libctx);
}
/* Draw from the private DRBG pool (PRIVATE). */
int OSSL_FN_priv_rand_range(OSSL_FN *r, const OSSL_FN *range,
size_t strength, OSSL_LIB_CTX *libctx)
{
return ossl_fn_rand_range(PRIVATE, r, range, strength, libctx);
}

161
crypto/fn/fn_shift.c Normal file
View file

@ -0,0 +1,161 @@
/*
* Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <assert.h>
#include <openssl/err.h>
#include "crypto/fnerr.h"
#include "fn_local.h"
/*
* In respect to shift factor the execution time is invariant of
* |n % OSSL_FN_BITS|, but not |n / OSSL_FN_BITS|. Or in other words
* pre-condition for constant-time-ness is |n < OSSL_FN_BITS| or
* |n / OSSL_FN_BITS| being non-secret.
*/
int OSSL_FN_lshift(OSSL_FN *r, const OSSL_FN *a, int n)
{
size_t i, nw;
unsigned int lb, rb;
const OSSL_FN_ULONG *ap = a->d;
OSSL_FN_ULONG *rp = r->d;
size_t rl = (size_t)r->dsize;
size_t al = (size_t)a->dsize;
if (n < 0) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_INVALID_SHIFT);
return 0;
}
nw = (size_t)n / OSSL_FN_BITS;
if (nw >= rl) {
memset(rp, 0, sizeof(*rp) * rl);
return 1;
}
lb = (unsigned int)n % OSSL_FN_BITS;
rb = OSSL_FN_BITS - lb;
/*
* Work from the high end to support r == a. Each result limb only
* depends on the corresponding source limb and the limb just below it,
* neither of which has been overwritten yet when walking downward.
*/
for (i = rl; i > 0; i--) {
size_t r_idx = i - 1;
OSSL_FN_ULONG limb = 0;
if (r_idx >= nw) {
size_t src_idx = r_idx - nw;
if (src_idx < al)
limb = (ap[src_idx] << lb) & OSSL_FN_MASK;
if (lb != 0 && src_idx > 0 && src_idx - 1 < al)
limb |= ap[src_idx - 1] >> rb;
}
rp[r_idx] = limb;
}
return 1;
}
int OSSL_FN_lshift1(OSSL_FN *r, const OSSL_FN *a)
{
OSSL_FN_ULONG *rp = r->d;
const OSSL_FN_ULONG *ap = a->d;
OSSL_FN_ULONG t, c = 0;
size_t rl = (size_t)r->dsize;
size_t al = (size_t)a->dsize;
size_t l = (rl < al) ? rl : al;
size_t i;
for (i = 0; i < l; i++) {
t = ap[i];
rp[i] = ((t << 1) | c) & OSSL_FN_MASK;
c = t >> (OSSL_FN_BITS - 1);
}
if (i < rl) {
rp[i++] = c;
for (; i < rl; i++)
rp[i] = 0;
}
return 1;
}
/*
* In respect to shift factor the execution time is invariant of
* |n % OSSL_FN_BITS|, but not |n / OSSL_FN_BITS|. Or in other words
* pre-condition for constant-time-ness for sufficiently[!] zero-padded
* inputs is |n < OSSL_FN_BITS| or |n / OSSL_FN_BITS| being non-secret.
*/
int OSSL_FN_rshift(OSSL_FN *r, const OSSL_FN *a, int n)
{
size_t i, nw;
unsigned int lb, rb;
const OSSL_FN_ULONG *ap = a->d;
OSSL_FN_ULONG *rp = r->d;
size_t rl = (size_t)r->dsize;
size_t al = (size_t)a->dsize;
if (n < 0) {
ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_INVALID_SHIFT);
return 0;
}
nw = (size_t)n / OSSL_FN_BITS;
rb = (unsigned int)n % OSSL_FN_BITS;
lb = OSSL_FN_BITS - rb;
/*
* Work from the low end to support r == a. Each result limb only
* depends on the corresponding source limb and the limb just above it,
* neither of which has been overwritten yet when walking upward.
*/
for (i = 0; i < rl; i++) {
size_t src_idx = i + nw;
OSSL_FN_ULONG limb = 0;
if (src_idx < al) {
limb = ap[src_idx] >> rb;
if (rb != 0 && src_idx + 1 < al)
limb |= (ap[src_idx + 1] << lb) & OSSL_FN_MASK;
}
rp[i] = limb;
}
return 1;
}
int OSSL_FN_rshift1(OSSL_FN *r, const OSSL_FN *a)
{
OSSL_FN_ULONG *rp = r->d;
const OSSL_FN_ULONG *ap = a->d;
size_t rl = (size_t)r->dsize;
size_t al = (size_t)a->dsize;
size_t i;
/*
* Work from the low end to support r == a. Each result limb only
* depends on the corresponding source limb and the limb just above it,
* neither of which has been overwritten yet when walking upward.
*/
for (i = 0; i < rl; i++) {
OSSL_FN_ULONG limb = 0;
if (i < al) {
limb = ap[i] >> 1;
if (i + 1 < al)
limb |= (ap[i + 1] << (OSSL_FN_BITS - 1)) & OSSL_FN_MASK;
}
rp[i] = limb;
}
return 1;
}

95
crypto/fn/fn_sqr.c Normal file
View file

@ -0,0 +1,95 @@
/*
* Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <assert.h>
#include <openssl/err.h>
#include "internal/safe_math.h"
#include "crypto/cryptlib.h"
#include "crypto/fnerr.h"
#include "../bn/bn_local.h" /* For using the low level bignum functions */
#include "fn_local.h"
OSSL_SAFE_MATH_MULU(size_t, size_t, OSSL_SAFE_MATH_MAXU(size_t))
size_t OSSL_FN_sqr_ctx_size(const OSSL_FN *r, const OSSL_FN *a)
{
size_t max, limbs, n_numbers = 1;
if (r == NULL || a == NULL)
return 0;
int err = 0;
max = safe_mul_size_t(2, a->dsize, &err);
if ((size_t)r->dsize < max)
n_numbers++;
limbs = safe_mul_size_t(n_numbers, max, &err);
return err == 0 ? OSSL_FN_CTX_size(1, n_numbers, limbs) : 0;
}
int OSSL_FN_sqr(OSSL_FN *r, const OSSL_FN *a, OSSL_FN_CTX *ctx)
{
const void *token = OSSL_FN_CTX_start(ctx);
if (token == NULL)
return 0;
size_t al = (size_t)a->dsize;
size_t rl = (size_t)r->dsize;
size_t max = (size_t)(2 * al);
int ret = 0;
#ifdef BN_SQR_COMBA
if (al == 4 && rl >= 8) {
bn_sqr_comba4(r->d, a->d);
goto end;
} else if (al == 8 && rl >= 16) {
bn_sqr_comba8(r->d, a->d);
goto end;
}
#endif
/* rl < max is always true when r == a, so covers that case too */
OSSL_FN *rr = r;
if (rl < max)
if ((rr = OSSL_FN_CTX_get_limbs(ctx, max)) == NULL)
goto err;
OSSL_FN *tmp = NULL;
if ((tmp = OSSL_FN_CTX_get_limbs(ctx, max)) == NULL)
goto err;
if (al != 0)
bn_sqr_normal(rr->d, a->d, (int)al, tmp->d);
if (rr != r) {
/*
* We use OSSL_FN_copy_truncate() here, because OSSL_FN_copy() expects
* to make a full copy, but r may be smaller than rr
*/
OSSL_FN_copy_truncate(r, rr);
}
#ifdef BN_SQR_COMBA
end:
#endif
ret = 1;
/* Zeroise everything above the result, if the r is that large */
size_t dif = (rl > max) ? rl - max : 0;
OSSL_FN_ULONG *rp = &r->d[max];
while (dif > 0) {
*rp++ = 0;
dif--;
}
err:
OSSL_FN_CTX_end(ctx, token);
return ret;
}

29
crypto/int.c Normal file
View file

@ -0,0 +1,29 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include "crypto/cryptlib.h"
size_t ossl_num_bits(size_t value)
{
size_t i;
unsigned long ret = 0;
/*
* It is argued that *on average* constant counter loop performs
* not worse [if not better] than one with conditional break or
* mask-n-table-lookup-style, because of branch misprediction
* penalties.
*/
for (i = 0; i < sizeof(value) * 8; i++) {
ret += (value != 0);
value >>= 1;
}
return (int)ret;
}

View file

@ -0,0 +1,623 @@
Fixed size large numbers
========================
*`BIGNUM` redesign for better constant time calculations*
---------------------------------------------------------
<center>
<p><h3>Abstract</h3></p>
<p>
In this design, we explore and define how OpenSSL's `BIGNUM` library can
be remodelled for constant-size calculations. Furthermore, we explore and
define a fixed size large number library, which never changes the in-memory
size of a number once it has been allocated.
</p>
</center>
### Table of contents:
- [Background][]
- [Goals][]
- [Challenges][]
- [Design][]
- [The `OSSL_FN` type][]
- [The `OSSL_FN_CTX` type][]
- [The `OSSL_FN_CTX` type, with frames][]
- [The `OSSL_FN_CTX` type, without frames][]
- [The `BIGNUM` type][]
- [Mutability][]
- [Memory functionality for `OSSL_FN`][]
- [Memory functionality for `OSSL_FN_CTX`][]
- [Failures][]
- [Repurposing existing code][]
- [How to apply `OSSL_FN`][]
- [Where to apply `OSSL_FN`][]
- [How to apply `OSSL_FN_CTX`][]
- [The variant with frames][]
- [The variant without frames][]
- [Testing][]
- [Appendix][]
- [Using the C99 flexible array member feature][]
Background
==========
[Background]: #background
The current internal definition of OpenSSL's `BIGNUM` looks like this:
```c
struct bignum_st {
BN_ULONG *d; /*
* Pointer to an array of 'BN_BITS2' bit
* chunks. These chunks are organised in
* a least significant chunk first order.
*/
int top; /* Index of last used d +1. */
/* The next are internal book keeping for bn_expand. */
int dmax; /* Size of the d array. */
int neg; /* one if the number is negative */
int flags;
};
```
The fields `d`, `top` and `dmax` allow the numbers to be quite dynamic in
terms of its memory footprint, as it can both increase in size[^1] and
decrease in size.[^2]
Furthermore, the result of any `BIGNUM` operation may be the same `BIGNUM`
instance as any of the operands, which means that any `BIGNUM` may have its
memory footprint adjusted at any time.
While this is very flexible, it leaves uncertainties about the time any
calculation may take, following any earlier calculation, which is a security
vulnerability.
[^1]: the array `d` is reallocated to a larger size and `dmax` as well as top
are updated
[^2]: `top` is diminished
Goals
=====
[Goals]: #goals
Overall goal: Introduce a new type and API that are inherently constant size
to replace the existing `BIGNUM` usage.
The intention is to enhance this one aspect of constant time calculations.
Other aspects are considered out of scope for this design.
The included sub-goals are:
* To define a new large number type and accompanying API, that doesn't allow
size adjustments of the large numbers once their individual size has been
established
* To define that large number type and API in such a way it's compatible
with the `BIGNUM` type, so that a `BIGNUM` may use an `OSSL_FN` as its
backing storage and selected call sites may acquire an `OSSL_FN` view of
a `BIGNUM`.
* To ensure that the new large number API is constant-size
* To repurpose as much as possible of our current `BIGNUM` code for the new
large number API, especially our assembler code (with the assumption that
everything that doesn't change the `BIGNUM` sizes can be repurposed as is)
* To replace all security critical large number calculations so that they
are not just constant-size in themselves, but that the whole set of
calculations remains constant-size, within OpenSSL code
Challenges
==========
[Challenges]: #challenges
The challenges we have are:
- **`BIGNUM` usage**
Because `BIGNUM` is a public facing API, it's likely to be used by OpenSSL
users. This existing API needs to be backward compatible, but performance
isn't necessarily critical.
- **constant-time through constant-size**
To make calculation time predictable on a broader scale than on a
per-operation basis, there's a need to ensure that each large number being
used in the calculations involved has a fixed size, i.e. to avoid the sort
of dynamic sizing that the `BIGNUM` functionality does.
Design
======
[Design]: #design
The overall design defines a new type, `OSSL_FN` (where FN is short for
"FIXNUM"), which can somehow be made compatible with `BIGNUM`, but yet be
distinct. Early thoughts on this was to make them essentially the same type
internally, and cast between them, but unfortunately, a compliant C compiler
is very likely to auto-cast between them, making it difficult to keep them
separate yet castable back and forth.
To allow a stricter or more explicit way to remedy the flexibility of the C
language, this design therefore defines a `OSSL_FN` which is separate from
the `BIGNUM`, yet compatible with BIGNUM insofar that the `BIGNUM` type
wraps around the `OSSL_FN` type.
The compatibility is primarily at the storage and acquisition boundary. A
`BIGNUM` may use an `OSSL_FN` as its backing storage, and selected internal
crypto call sites that already receive `BIGNUM` values may acquire the
embedded `OSSL_FN` and perform security-critical calculations with `OSSL_FN`
functions.
This does not mean that ordinary `BIGNUM` (`BN_`) operation functions are
wrappers around corresponding `OSSL_FN` operation functions. `BN_`
functions retain their dynamic `BIGNUM` semantics. Conversely, once
execution has entered an `OSSL_FN` operation, that operation must remain
inside the "`OSSL_FN` only" bubble and must not call functions that take
`BIGNUM` arguments.
This restriction does not apply to low-level helpers that operate only on
`BN_ULONG` arrays or primitive limb values, such as existing `bn_` word
functions. `BN_ULONG` and `OSSL_FN_ULONG` are compatible, so such helpers
may be reused by `OSSL_FN` code as long as they do not allocate, resize, or
otherwise operate on `BIGNUM` objects.
The overall design also defines new associated types to replace their
`BIGNUM` counterparts: `OSSL_FN_CTX`, `OSSL_FN_BLINDING`, `OSSL_FN_MONT_CTX`,
and `OSSL_FN_RECP_CTX`. Notably, however, the callback type `BN_GENCB`
isn't replaced, as it contains nothing `BIGNUM`, and can therefore be reused
unchanged with an `OSSL_FN` API.
The `OSSL_FN` type and API will be designed in such a way to enable it to
become public at some point in the future. *The initial version will not be
public and will only be used internally within OpenSSL.*
Let's go over the details
The `OSSL_FN` type
------------------
[The `OSSL_FN` type]: #the-ossl_fn-type
The `OSSL_FN` type would be a structure derived from the existing `BIGNUM`
type, retaining a minimum amount of data. Just as was previously with
`BIGNUM`, the absolute value of the number is stored in a `BN_ULONG` array.
`OSSL_FN` itself is unsigned; sign handling remains with `BIGNUM` when
`BIGNUM` values are used as carriers.
```c
typedef struct ossl_fn_st OSSL_FN;
struct ossl_fn_st {
/* Flag: alloced with OSSL_FN_new() or OSSL_FN_secure_new() */
unsigned int is_dynamically_allocated : 1;
/* Flag: alloced with OSSL_FN_secure_new() */
unsigned int is_securely_allocated : 1;
/*
* The d array, with its size in number of BN_ULONG.
* This stores the number itself
*/
size_t dsize;
BN_ULONG d[];
};
```
The `OSSL_FN_CTX` type
----------------------
[The `OSSL_FN_CTX` type]: #the-ossl_fn_ctx-type
The `OSSL_FN_CTX` type is made to replace the `BN_CTX` type where `OSSL_FN`
type is used instead of `BIGNUM`.
The `OSSL_FN_CTX` type is to be implemented as an arena (a large enough chunk
of memory) in which `OSSL_FN` instances are allocated. More in detail, there
are two possibilities.
### The `OSSL_FN_CTX` type, with frames
[The `OSSL_FN_CTX` type, with frames]: #the-ossl_fn_ctx-type-with-frames
This variant is intended to mimic all `BN_CTX` functionality. The idea is to
create a large `OSSL_FN_CTX` at the top function of a complex calculation, and
pass it around to all sub-function calls.
Each sub-function would begin with starting a frame (using `OSSL_FN_CTX_begin()`,
similar in spirit to `BN_CTX_start()`) in the passed `OSSL_FN_CTX` arena,
obtain what temporary `OSSL_FN`s it needs from it (using `OSSL_FN_CTX_get()`,
similar in spirit to `BN_CTX_get()`), perform what calculations it needs, and
finish with ending the frame (using `OSSL_FN_CTX_end()`, similar in spirit to
`BN_CTX_end()`), which relinquishing that frame's space in the `OSSL_FN_CTX`
arena, and thereby making that same space available for the next sub-function.
```c
typedef struct ossl_fn_ctx_st OSSL_FN_CTX;
struct ossl_fn_ctx_frame_st; /* forwarding, see below */
struct ossl_fn_ctx_st {
/*
* Pointer to the last OSSL_FN_CTX_start() location (a simple pointer into
* the memory area). See the struct ossl_fn_ctx_frame_st definition below
* for details.
*/
struct ossl_fn_ctx_frame_st *last_frame;
/*
* The arena itself.
*/
size_t msize; /* bytes */
unsigned char memory[];
};
struct ossl_fn_ctx_frame_st {
/*
* Pointer back to the whole arena where the frame is located, to access
* |msize| and |memory| from it.
*/
struct ossl_fn_ctx_st *arena;
/*
* Pointer to the previous frame in the arena, allowing OSSL_FN_CTX_end()
* to do its job.
*/
struct ossl_fn_ctx_frame_st *previous_frame;
/*
* Pointer to the free area of the frame. Every time OSSL_FN_CTX_get() is
* called, the current value of this pointer is returned, and it's updated
* by incrementing it by the number of bytes given by OSSL_FN_CTX_get().
* The available number of bytes is limited by what's left in the arena.
*/
unsigned char *free_memory;
unsigned char memory[];
};
```
The arena design requires that the total use of the arena can be predicted
at the point of allocating the arena. There is an inherent uncertainty how
large an arena should be to accommodate the needs of a large tree of
function calls using it. A possible solution is to allocate a very large
arena (could 32kB be considered enough?), but it may require some
investigation to find out what's reasonable.
Looking at the current use of `BN_CTX_new`, it can be noted that they are
allocated all over current OpenSSL code, so it's easy to assume that each is
used in a fairly limited fashion. Furthermore, the `BN_CTX` internals allow
for a maximum of 16 `BIGNUM`s. A corresponding arena could the reasonably
have the size 16 \* *size of largest fixed number* plus a little extra for
bookkeeping purposes.
### The `OSSL_FN_CTX` type, without frames
[The `OSSL_FN_CTX` type, without frames]: #the-ossl_fn_ctx-type-without-frames
Compared to the variant with frames, this `OSSL_FN_CTX` variant is much
simpler, but also more heap allocation intense.
The idea with this one is that each function that needs to obtain temporary
`OSSL_FN`s would also create their own `OSSL_FN_CTX`, independently from any
other function.
```c
typedef struct ossl_fn_ctx_st OSSL_FN_CTX;
struct ossl_fn_ctx_st {
/*
* Pointer to the free area of the arena. Every time OSSL_FN_CTX_get() is
* called, the current value of this pointer is returned, and it's updated
* by incrementing it by the number of bytes given by OSSL_FN_CTX_get().
* The available number of bytes is limited by what's left in the arena.
*/
unsigned char *free_memory;
/*
* The arena itself.
*/
size_t msize; /* bytes */
unsigned char memory[];
};
```
Other associated types
----------------------
[Other associated types]: #other-associated-types
There are a few types that, like `BN_CTX` / `OSSL_FN_CTX`, are used to hold
a context around some more complicated calculations. Just like `OSSL_FN_CTX`,
The `OSSL_FN` variants of these types are made into strictly separate types,
not compatible with their `BIGNUM` counterparts.
| `BIGNUM` types | `OSSL_FN` types |
|----------------|--------------------|
| `BN_BLINDING` | `OSSL_FN_BLINDING` |
| `BN_MONT_CTX` | `OSSL_FN_MONT_CTX` |
| `BN_RECP_CTX` | `OSSL_FN_RECP_CTX` |
Their `OSSL_FN` APIs for these types should be possible to create by
repurposing the corresponding `BIGNUM` APIs, with adjustments for the
constant-size requirements of all `OSSL_FN` functions.
The `BIGNUM` type
-----------------
[The `BIGNUM` type]: #the-bignum-type
The `BIGNUM` type is changed to include a `OSSL_FN` for its data, while
retaining the fields that support the dynamic `BIGNUM` semantics:
```c
struct bignum_st {
OSSL_FN *data;
/* Some of these flags are replicated in OSSL_FN, some are not */
int flags;
BN_ULONG *d; /* Pointer to |data->d| */
int top; /* Index of last used d +1. */
int dmax; /* Copy of |data->dsize| */
int neg; /* One if the number is negative */
};
```
In normal public use, `data` is non-NULL and `d` points at `data->d`.
Certain internal or otherwise special `BIGNUM`s may still have `data` set to
NULL, in which case `d` points directly at a `BN_ULONG` array. Such cases
must not be forced to grow an `OSSL_FN` backing object solely to fit this
structure. How `OSSL_FN` code should use the `BN_ULONG` data from such
`BIGNUM`s remains an implementation detail.
When structured this way, it's easy to get an `OSSL_FN` out of a `BIGNUM`:
```c
/*
* BN_acquire_fn() and BN_release_fn() function together. It is done this
* way as a safety measure, to make sure that a BIGNUM doesn't expand an OSSL_FN
* that the caller currently has a handle on. However, it's possible to
* adjust the size of the OSSL_FN while acquiring it.
*/
OSSL_FN *BN_acquire_fn(BIGNUM *a, size_t bits)
{
if ((bn_expand(a, bits)) <= 0)
return NULL;
/* Implementation may do further acquisition bookkeeping here. */
return a->data;
}
void BN_release_fn(BIGNUM *a)
{
/* Implementation may do further release bookkeeping here. */
}
```
Note that these functions are not designed to be thread-safe. By design,
holding pointers to a `BIGNUM` and its wrapped `OSSL_FN` at the same time
should only happen in a very short term.
Mutability
----------
[Mutability]: #mutability
The understanding is that within a `OSSL_FN` API, `dsize` is immutable as
soon as a `OSSL_FN` has been allocated to its target size, except for when
the `OSSL_FN` instance is freed.
When accessed through the `BIGNUM` type (i.e. by the `BIGNUM` API), the
`OSSL_FN` size may be reallocated to allow a larger size than initially
allocated, and `dsize` may be modified accordingly. The exception is an
acquired `OSSL_FN` view. Once a caller has acquired the backing `OSSL_FN`
from a `BIGNUM`, that backing size must be treated as immutable until
release. The owning `BIGNUM` must not simultaneously be used through `BN_`
operations that could resize or otherwise reinterpret the same storage.
The `OSSL_FN_CTX` API is much more strict. The size of an `OSSL_FN_CTX`
instance is immutable after it has been allocated, except when it is freed.
Memory functionality for `OSSL_FN`
----------------------------------
[Memory functionality for `OSSL_FN`]: #memory-functionality-for-ossl_fn
We anticipate that we will need the following functions to allocate and
deallocate `OSSL_FN`s:
```c
OSSL_FN *OSSL_FN_new(size_t size);
void OSSL_FN_free(OSSL_FN *f);
```
Memory functionality for `OSSL_FN_CTX`
--------------------------------------
[Memory functionality for `OSSL_FN_CTX`]: #memory-functionality-for-ossl_fn_ctx
We anticipate that the `OSSL_FN_CTX` API will look very much like the
`BN_CTX` API, except for the allocation functionality:
```c
OSSL_FN_CTX *OSSL_FN_CTX_new(OSSL_LIB_CTX *libctx, size_t arena_size);
OSSL_FN_CTX *OSSL_FN_CTX_secure_new(OSSL_LIB_CTX *libctx, size_t arena_size);
void OSSL_FN_CTX_free(OSSL_FN_CTX *ctx);
OSSL_FN *OSSL_FN_CTX_get(OSSL_FN_CTX *ctx, size_t size);
```
*Something to be noted is that `OSSL_FN_CTX_secure_new()` allocates the
whole arena in secure memory. The impact compared to allocating individual
`OSSL_FN` instances in secure memory is considered minimal.*
If the variant of `OSSL_FN_CTX` *with frames* is chosen, the following
functions will also have to be defined:
```c
int OSSL_FN_CTX_start(OSSL_FN_CTX *ctx);
int OSSL_FN_CTX_end(OSSL_FN_CTX *ctx);
```
Failures
--------
[Failures]: #failures
A fixed size large number introduces new problems, which introduces new ways
that the `OSSL_FN` API can fail:
* Overflow: this will happen when the caller has allocated an improperly
sized `OSSL_FN` to store future calculation results in. *This is akin to
memory allocation failures in so far that there isn't enough memory space*
Repurposing existing code
=========================
[Repurposing existing code]: #repurposing-existing-code
A majority of existing internal `BIGNUM` code operates directly on the `d`
array of the existing `BIGNUM` structure, with the size of that array given
separately, and are already essentially operating on fixed size numbers.
This design assumes that such functions can be repurposed for `OSSL_FN`
functionality with zero change, apart from function name changes.
Furthermore, the remaining functions, which do manipulate the size of the
`BIGNUM`, or are public facing `BIGNUM` functions, retain their current
`BIGNUM` functionality, including size manipulation within the `BIGNUM`
"bubble". They are not wrapped around `OSSL_FN` functions; conversion
between `BIGNUM` and `OSSL_FN` happens at top-level crypto call sites,
where the embedded `OSSL_FN` is acquired and passed to `OSSL_FN`
functions, during whose execution the `OSSL_FN` size is immutable (see
[Mutability][]).
How to apply `OSSL_FN`
======================
[How to apply `OSSL_FN`]: #how-to-apply-ossl_fn
The purpose of `OSSL_FN` is to make the number constant size (implying
enhanced constant time) for a crypto system. To guarantee this with high
confidence, any function that performs some sort of numeric operation on a
set of input `OSSL_FN`s must only use other functions that only affect the
contents of their `d` array, but not its size. Those are typically other
`OSSL_FN` functions, or reused bignum functions that receive the `d` array
and its size directly.
Where to apply `OSSL_FN`
========================
[Where to apply `OSSL_FN`]: #where-to-apply-ossl_fn
`OSSL_FN` should primarily be used instead of `BIGNUM` in internal
calculations throughout OpenSSLs libraries. Exceptions can be made where
calculations aren't security critical.
In this, "calculations" is meant in a mathematical sense, i.e. whatever what
would be expressed as a mathematical formula is considered a "calculation".
However, `BIGNUM` has other uses than mere calculations. For example,
`BIGNUM` is used as storage of numbers that were originally ASN.1 INTEGERs,
and while individual ASN.1 INTEGERs always have a known size, they are
usually just one number in a set, and it's often only known at a later time
what are the size requirements of the cryptosystem that use them.
For example, the size of an RSA key can only be determined when a known
number in that key - usually *n* - has been seen by code, and this affects
what size all numbers in an RSA key should be adjusted to before doing
calculations on them.
How to apply `OSSL_FN_CTX`
==========================
[How to apply `OSSL_FN_CTX`]: #how-to-apply-ossl_fn_ctx
The variant with frames
-----------------------
[The variant with frames]: #the-variant-with-frames
All internal uses of `BN_CTX_new()` and `BN_CTX_new_ex()` are to be replaced
with calls of `OSSL_FN_CTX_new()`.
All internal uses of `BN_CTX_secure_new()` and `BN_CTX_secure_new_ex()` are to
be replaced with calls of `OSSL_FN_CTX_secure_new()`.
All internal uses of `BN_CTX_free()` are to be replaced with calls of
`OSSL_FN_CTX_free()`.
All internal uses of `BN_CTX_start()` are to be replaced with calls of
`OSSL_FN_CTX_start()`.
All internal uses of `BN_CTX_get()` are to be replaced with calls of
`OSSL_FN_CTX_get()`.
All internal uses of `BN_CTX_end()` are to be replaced with calls of
`OSSL_FN_CTX_end()`.
The variant without frames
--------------------------
[The variant without frames]: #the-variant-without-frames
All internal uses of `BN_CTX_new()`, `BN_CTX_new_ex()`, `BN_CTX_secure_new()`,
`BN_CTX_secure_new_ex()`, and `BN_CTX_free()` are to be dropped.
All internal uses of `BN_CTX_start()` are to be replaced with calls of
`OSSL_FN_CTX_new()`.
All internal uses of `BN_CTX_get()` are to be replaced with calls of
`OSSL_FN_CTX_get()`.
All internal uses of `BN_CTX_end()` are to be replaced with calls of
`OSSL_FN_CTX_free()`.
Testing
=======
[Testing]: #testing
Functional tests similar to `test/recipes/10-test_bn.t` must be added.
Timing tests to compare operations on a variety of inputs of different sizes
must also be added. These tests should perform operations based on a given
fixed number size.
It should also prove interesting to collect timing statistics for a set of
operations using `BIGNUM` in previous OpenSSL versions and compare them with
similar timing statistics using `BIGNUM` when reimplemented according to this
design.
Appendix
========
[Appendix]: #appendix
Using the C99 flexible array member feature
-------------------------------------------
[Using the C99 flexible array member feature]: #using-the-c99-flexible-array-member-feature
In this design, the C99 feature that's dubbed "flexible array member" is used
extensively. This a `struct` member that's an array, that must come last in
the struct, and that is incomplete in so far that no array size is given. It
can look like this:
``` C
struct t {
size_t a;
char b;
char c[]; /**< flexible array member */
};
```
Some attention must be paid to how it's arranged in memory. It's debated
whether the offset of a flexible array member's offset from the start of the
`struct` is set to be before or after the `struct`'s end padding, i.e. whether
`sizeof(struct t) == offsetof(struct t, c)` is true or not in all circumstances.
Here's how that would differ on a 64-bit system:
| location of `c` | `offsetof(struct t, a)` | `offsetof(struct t, b)` | `offsetof(struct t, c)` | `sizeof(struct t)` |
|-----------------|:-----------------------:|:-----------------------:|:-----------------------:|:------------------:|
| before padding | 0 | 8 | 9 | 16 |
| after padding | 0 | 8 | 16 | 16 |
To be noted, `gcc` and `clang` favor "before padding".
For consistent placement of the flexible array member, one therefore needs to
pay attention to possible `struct` padding. Among other methods, one chosen
here is to precede the flexible array member with a member whose type is
assumed to be large enough that no padding is needed after it, such as
`size_t` or a pointer.

View file

@ -4,7 +4,7 @@
bn_mul_words, bn_mul_add_words, bn_sqr_words, bn_div_words,
bn_add_words, bn_sub_words, bn_mul_comba4, bn_mul_comba8,
bn_sqr_comba4, bn_sqr_comba8, bn_cmp_words, bn_mul_normal,
bn_sqr_comba4, bn_sqr_comba8, bn_cmp_words, bn_mul_truncated, bn_mul_normal,
bn_mul_low_normal, bn_mul_recursive, bn_mul_part_recursive,
bn_mul_low_recursive, bn_sqr_normal, bn_sqr_recursive,
bn_expand, bn_wexpand, bn_expand2, bn_fix_top, bn_check_top,
@ -32,6 +32,8 @@ library internal functions
int bn_cmp_words(BN_ULONG *a, BN_ULONG *b, int n);
void bn_mul_truncated(BN_ULONG *r, int rn, BN_ULONG *a, int na,
BN_ULONG *b, int nb);
void bn_mul_normal(BN_ULONG *r, BN_ULONG *a, int na, BN_ULONG *b,
int nb);
void bn_mul_low_normal(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n);
@ -151,6 +153,11 @@ bn_cmp_words(B<a>, B<b>, B<n>) operates on the B<n> word arrays B<a>
and B<b>. It returns 1, 0 and -1 if B<a> is greater than, equal and
less than B<b>.
bn_mul_truncated(B<r>, B<nr>, B<a>, B<na>, B<b>, B<nb>) operates on the
B<na> word array B<a>, the B<nb> word array B<b> and the B<nr> word array
B<r>. It computes B<a>*B<b> and places the result in B<r>, truncated to
B<nr> words.
bn_mul_normal(B<r>, B<a>, B<na>, B<b>, B<nb>) operates on the B<na>
word array B<a>, the B<nb> word array B<b> and the B<na>+B<nb> word
array B<r>. It computes B<a>*B<b> and places the result in B<r>.

View file

@ -13,12 +13,58 @@
#include <openssl/bn.h>
#include <limits.h>
#include "types.h"
BIGNUM *bn_wexpand(BIGNUM *a, int words);
BIGNUM *bn_expand2(BIGNUM *a, int words);
void bn_correct_top(BIGNUM *a);
/*
* bn_acquire_ossl_fn() and bn_release() work in tandem, and are
* most useful at call sites that temporarily operate on a BIGNUM's
* fixed-width OSSL_FN representation.
*/
/**
* Acquire the OSSL_FN from a BIGNUM. The BIGNUM should no longer be
* used until bn_release() is called.
*
* @param[in] b The BIGNUM instance to get an OSSL_FN from
* @param[in] limbs The minimum number of limbs 'b' should be expanded to.
* Note that this doesn't set 'top', that's done by
* bn_release().
* @returns the OSSL_FN instance of the BIGNUM, if there is one
* @pre b must not be NULL
*/
OSSL_FN *bn_acquire_ossl_fn(BIGNUM *b, int limbs);
/**
* Release the BIGNUM from which the OSSL_FN was acquired. This will
* adjust the BIGNUM to what was done with its OSSL_FN, and the BIGNUM
* can again be used with BN_ functions.
*
* @param[in] b The BIGNUM instance to release
* @param[in] limbs The maximum number of significant limbs. This sets
* 'top'.
* @pre b and b->data must not be NULL
*/
void bn_release(BIGNUM *b, int limbs);
/**
* Get the OSSL_FN from a BIGNUM for read-only access. Unlike
* bn_acquire_ossl_fn(), this does not acquire or resize the BIGNUM:
* the BIGNUM remains fully usable with BN_ functions, no bn_release()
* call is needed, and the returned OSSL_FN keeps the BIGNUM's current
* allocated width without any expansion. Use this for read-only
* operands; use bn_acquire_ossl_fn() for writable results.
*
* @param[in] bn The BIGNUM instance to get an OSSL_FN from
* @returns the OSSL_FN instance of the BIGNUM, or NULL if it has none
* @pre bn must not be NULL
*/
OSSL_FN *bn_get_ossl_fn(const BIGNUM *bn);
/*
* Determine the modified width-(w+1) Non-Adjacent Form (wNAF) of 'scalar'.
* This is an array r[] of values that are either zero or odd with an

View file

@ -36,4 +36,6 @@ void ossl_malloc_setup_failures(void);
int ossl_crypto_alloc_ex_data_intern(int class_index, void *obj,
CRYPTO_EX_DATA *ad, int idx);
size_t ossl_num_bits(size_t value);
#endif /* OSSL_CRYPTO_CRYPTLIB_H */

992
include/crypto/fn.h Normal file
View file

@ -0,0 +1,992 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifndef OPENSSL_FN_H
#define OPENSSL_FN_H
#pragma once
#include <stddef.h>
#include <openssl/opensslconf.h>
#include <openssl/bn_limbs.h>
#include <openssl/types.h>
#include "crypto/types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* @type OSSL_FN_ULONG is the type for the OSSL_FN limb. It's made to be
* compatible with BN_ULONG (quite literally).
*
* @def OSSL_FN_BYTES is defined with the size of OSSL_FN_ULONG, measured in
* bytes. This is mainly useful where 'sizeof(OSSL_FN_ULONG)' isn't suitable,
* such as the C pre-processor.
*/
#ifdef BN_BYTES
typedef BN_ULONG OSSL_FN_ULONG;
#define OSSL_FN_BYTES BN_BYTES
#endif
#ifndef OSSL_FN_BYTES
#error "OpenSSL doesn't support large numbers on this platform"
#endif
/*
* For practical reasons, we allow allocating OSSL_FNs in terms of limbs (what
* the BIGNUM library calls "words"), bytes and bits. The number of bytes and
* bits are rounded up to the number of limbs that can fit them.
*/
/**
* Allocate an OSSL_FN in memory.
*
* @param[in] size The number of limbs for the number itself.
* There's an additional few bytes allocated for bookkeeping.
* @returns an OSSL_FN instance.
* @retval NULL on error.
*/
OSSL_FN *OSSL_FN_new_limbs(size_t size);
/**
* Allocate an OSSL_FN in secure memory.
*
* @param[in] size The number of limbs for the number itself.
* There's an additional few bytes allocated for bookkeeping.
* @returns an OSSL_FN instance.
* @retval NULL on error.
*/
OSSL_FN *OSSL_FN_secure_new_limbs(size_t size);
/**
* Allocate an OSSL_FN in memory.
*
* @param[in] size The number of bytes for the number itself.
* There's an additional few bytes allocated for bookkeeping.
* @returns an OSSL_FN instance.
* @retval NULL on error.
*/
OSSL_FN *OSSL_FN_new_bytes(size_t size);
/**
* Allocate an OSSL_FN in secure memory.
*
* @param[in] size The number of bytes for the number itself.
* There's an additional few bytes allocated for bookkeeping.
* @returns an OSSL_FN instance.
* @retval NULL on error.
*/
OSSL_FN *OSSL_FN_secure_new_bytes(size_t size);
/**
* Allocate an OSSL_FN in memory.
*
* @param[in] size The number of bits for the number itself.
* There's an additional few bytes allocated for bookkeeping.
* @returns an OSSL_FN instance.
* @retval NULL on error.
*/
OSSL_FN *OSSL_FN_new_bits(size_t size);
/**
* Allocate an OSSL_FN in secure memory.
*
* @param[in] size The number of bits for the number itself.
* There's an additional few bytes allocated for bookkeeping.
* @returns an OSSL_FN instance.
* @retval NULL on error.
*/
OSSL_FN *OSSL_FN_secure_new_bits(size_t size);
/**
* Free an OSSL_FN instance if it was dynamically allocated.
* Free it securely if it was allocated securely.
*
* @param[in] f The OSSL_FN instance to be freed.
*/
void OSSL_FN_free(OSSL_FN *f);
/**
* Cleanse and free an OSSL_FN instance if it was dynamically allocated.
* Cleanse and free it securely if it was allocated securely.
* Merely cleanse it if it was not dynamically allocated.
*
* @param[in] f The OSSL_FN instance to be freed.
*/
void OSSL_FN_clear_free(OSSL_FN *f);
/**
* Cleanse the data of an OSSL_FN instance, effectively making it zero.
*
* @param[in] f The OSSL_FN instance to be cleared.
*/
void OSSL_FN_clear(OSSL_FN *f);
/**
* Set an OSSL_FN to a single-limb word value.
*
* @param[out] a The destination OSSL_FN
* @param[in] w The OSSL_FN_ULONG word
* @returns 1 on success, 0 on error
*
* @note Sets a->d[0] to @p w and zeroes the remaining limbs, so the full
* dsize array reflects the value @p w. If a->dsize is 0 there is no
* limb to write and the call fails with
* OSSL_FN_R_RESULT_ARG_TOO_SMALL (OSSL_FN is fixed-size, so the
* destination cannot be grown). The operation is constant-time with
* respect to @p w's value; the only branch is on the operand's public
* width (dsize).
*/
int OSSL_FN_set_word(OSSL_FN *a, OSSL_FN_ULONG w);
/**
* Set an OSSL_FN to one.
*
* @param[out] a The destination OSSL_FN
* @returns 1 on success, 0 on error
*
* @note Equivalent to OSSL_FN_set_word(a, 1), provided as a named function
* for readability at call sites. Leak profile as for
* OSSL_FN_set_word().
*/
int OSSL_FN_one(OSSL_FN *a);
/**
* Set an OSSL_FN to zero.
*
* @param[out] a The destination OSSL_FN
* @returns 1 on success, 0 on error
*
* @note This is a plain value assignment, not a secure wipe; use
* OSSL_FN_clear() when the limbs may hold secret data and must be wiped
* irreversibly. Equivalent to OSSL_FN_set_word(a, 0). Leak profile as
* for OSSL_FN_set_word().
*/
int OSSL_FN_zero(OSSL_FN *a);
/**
* Copy the contents of one OSSL_FN instance to another.
*
* @param[out] a The destination OSSL_FN
* @param[in] b The source OSSL_FN
* @returns The destination on success, NULL on error.
*
* @note The destination must be at least as large as the source.
* Any limbs beyond the source size are zeroed.
*/
OSSL_FN *OSSL_FN_copy(OSSL_FN *a, const OSSL_FN *b);
/**
* Copy the contents of one OSSL_FN instance to another,
* normally the shorter one, truncating the high bytes.
*
* @param[out] a The destination OSSL_FN
* @param[in] b The source OSSL_FN
* @returns the destination.
*/
OSSL_FN *OSSL_FN_copy_truncate(OSSL_FN *a, const OSSL_FN *b);
/**
* Calculate the arena payload size for an OSSL_FN_CTX.
*
* @param[in] max_n_frames Maximum number of simultaneously active frames.
* This indicates the expected depth of call stack
* that the resulting OSSL_FN_CTX will be used in.
* Must be at least 1.
* @param[in] max_n_numbers Maximum number of simultaneously active OSSL_FN.
* Must be 0 if and only if @p max_n_limbs is 0.
* @param[in] max_n_limbs Maximum number of simultaneously active OSSL_FN
* limbs. Must be 0 if and only if
* @p max_n_numbers is 0.
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid argument.
*
* The returned size is the value to pass to OSSL_FN_CTX_new_size() or
* OSSL_FN_CTX_secure_new_size(). It does not include sizeof(OSSL_FN_CTX).
*/
size_t OSSL_FN_CTX_size(size_t max_n_frames, size_t max_n_numbers,
size_t max_n_limbs);
/**
* Allocate a new OSSL_FN_CTX, given a set of input numbers.
*
* @param[in] libctx OpenSSL library context (currently unused)
* @param[in] max_n_frames Maximum number of simultaneously active frames.
* This indicates the expected depth of call stack
* that the resulting OSSL_FN_CTX will be used in.
* @param[in] max_n_numbers Maximum number of simultaneously active OSSL_FN.
* @param[in] max_n_limbs Maximum number of simultaneously active OSSL_FN
* limbs.
* @returns An allocated OSSL_FN_CTX, or NULL on error.
*/
OSSL_FN_CTX *OSSL_FN_CTX_new(OSSL_LIB_CTX *libctx, size_t max_n_frames,
size_t max_n_numbers, size_t max_n_limbs);
/**
* Allocate a new OSSL_FN_CTX with a given arena payload size.
*
* @param[in] libctx OpenSSL library context (currently unused)
* @param[in] size Arena payload size in bytes, typically from
* OSSL_FN_CTX_size(). A size of 0 is the error
* return of OSSL_FN_CTX_size() and is treated as
* an error here too.
* @returns An allocated OSSL_FN_CTX, or NULL on error.
*/
OSSL_FN_CTX *OSSL_FN_CTX_new_size(OSSL_LIB_CTX *libctx, size_t size);
/**
* Allocate a new OSSL_FN_CTX in secure memory, given a set of input numbers.
* Other than allocating in secure memory, this function does exactly the same
* thing as OSSL_FN_CTX_new().
*/
OSSL_FN_CTX *OSSL_FN_CTX_secure_new(OSSL_LIB_CTX *libctx, size_t max_n_frames,
size_t max_n_numbers, size_t max_n_limbs);
/**
* Allocate a new OSSL_FN_CTX in secure memory with a given arena payload size.
*
* @param[in] libctx OpenSSL library context (currently unused)
* @param[in] size Arena payload size in bytes, typically from
* OSSL_FN_CTX_size(). A size of 0 is treated as
* an error, as in OSSL_FN_CTX_new_size().
* @returns An allocated OSSL_FN_CTX, or NULL on error.
*/
OSSL_FN_CTX *OSSL_FN_CTX_secure_new_size(OSSL_LIB_CTX *libctx, size_t size);
/**
* Report the peak number of frames, numbers, and limbs that were
* simultaneously active during the lifetime of the OSSL_FN_CTX.
* This can be used to determine suitable arena parameters for a
* given workload.
*
* @param[in] ctx The OSSL_FN_CTX to query. This may be NULL.
* @param[out] peak_n_frames Peak number of simultaneously active frames
* @param[out] peak_n_numbers Peak number of simultaneously active OSSL_FNs
* @param[out] peak_n_limbs Peak total limbs across all active OSSL_FNs
*
* Any of the out parameters may be NULL. If ctx is NULL, all out
* parameters that are non-NULL are set to 0.
*/
void OSSL_FN_CTX_peak_usage(const OSSL_FN_CTX *ctx, size_t *peak_n_frames,
size_t *peak_n_numbers, size_t *peak_n_limbs);
/**
* Free an OSSL_FN_CTX.
*
* @param[in] ctx The OSSL_FN_CTX to be freed. This may be NULL.
*/
void OSSL_FN_CTX_free(OSSL_FN_CTX *ctx);
/**
* Start a new OSSL_FN_CTX frame. This *must* be called by any function
* that wants to get a temporary OSSL_FN from the OSSL_FN_CTX. The function
* call this must also clean up with a OSSL_FN_CTX_end() call.
*
* @param[in] ctx The OSSL_FN_CTX to start the frame in.
* @returns Ownership token of the started frame, NULL on error.
* This token must be passed to OSSL_FN_CTX_end().
*/
const void *OSSL_FN_CTX_start(OSSL_FN_CTX *ctx);
/**
* End the last OSSL_FN_CTX frame, resetting back to the previous
* frame. If a function called OSSL_FN_CTX_start(), it *must* call
* this function before returning.
*
* @param[in] ctx The OSSL_FN_CTX to start the frame in.
* @param[in] token Ownership token returned by OSSL_FN_CTX_start().
* @returns 1 on success, 0 on error.
*
* @note The token parameter is validated but not used for choosing a
* frame; only the most recent frame can be ended. Passing an incorrect
* token indicates a programming error and the function will fail.
* If NULL is passed, nothing will be done but the function will return 1.
*/
int OSSL_FN_CTX_end(OSSL_FN_CTX *ctx, const void *token);
/**
* Get a suitably sized OSSL_FN from an OSSL_FN_CTX.
*
* @param[in] ctx The OSSL_FN_CTX
* @param[in] limbs The desired size of the resulting OSSL_FN,
* in number of limbs.
* @returns an OSSL_FN pointer on success, NULL on error.
*/
OSSL_FN *OSSL_FN_CTX_get_limbs(OSSL_FN_CTX *ctx, size_t limbs);
/**
* Get a suitably sized OSSL_FN from an OSSL_FN_CTX.
*
* @param[in] ctx The OSSL_FN_CTX
* @param[in] limbs The desired size of the resulting OSSL_FN,
* in number of bytes.
* @returns an OSSL_FN pointer on success, NULL on error.
*/
OSSL_FN *OSSL_FN_CTX_get_bytes(OSSL_FN_CTX *ctx, size_t bytes);
/**
* Get a suitably sized OSSL_FN from an OSSL_FN_CTX.
*
* @param[in] ctx The OSSL_FN_CTX
* @param[in] limbs The desired size of the resulting OSSL_FN,
* in number of bits.
* @returns an OSSL_FN pointer on success, NULL on error.
*/
OSSL_FN *OSSL_FN_CTX_get_bits(OSSL_FN_CTX *ctx, size_t bits);
/*
* Arithmetic functions treat the OSSL_FN 'd' array as a large 2's complement
* unsigned integer, least significant limb first. All carrys or borrows are
* extended in the result and otherwise ignored. This makes OSSL_FN functions
* act just like operations on C unsigned integer types, but at a larger scale.
*/
/**
* Return the number of significant bits in an OSSL_FN number.
*
* @param[in] a The operand
* @returns The number of significant bits, or zero if a is zero
*/
size_t OSSL_FN_num_bits(const OSSL_FN *a);
/**
* Compare two OSSL_FN numbers as unsigned integers.
*
* @param[in] a The first operand
* @param[in] b The second operand
* @returns 1 if a > b, -1 if a < b, 0 if a == b
*/
int OSSL_FN_cmp(const OSSL_FN *a, const OSSL_FN *b);
/**
* Test whether bit @p n is set in @p a.
*
* @param[in] a The operand
* @param[in] n The bit index (0 = least significant)
* @returns 1 if bit @p n of @p a is set, 0 otherwise.
*
* @note An out-of-range index (n < 0 or n >= the operand's width in bits)
* reads as 0. The only control flow branches on the operand's public
* width (its dsize), not on limb values; the returned value is the bit
* itself, which is the information the caller asked for.
*/
int OSSL_FN_is_bit_set(const OSSL_FN *a, int n);
/**
* Test whether the unsigned value of @p a equals the single-limb word @p w.
*
* @param[in] a The operand
* @param[in] w The OSSL_FN_ULONG word to compare against
* @returns 1 if the unsigned value of @p a equals @p w, 0 otherwise
*
* @note Control flow branches only on the operand's public width (its dsize),
* not on limb values; the returned value is the equality test the caller
* asked for.
*/
int OSSL_FN_is_word(const OSSL_FN *a, OSSL_FN_ULONG w);
/**
* Test whether @p a is zero.
*
* @param[in] a The operand
* @returns 1 if @p a is zero, 0 otherwise
*
* @note Equivalent to OSSL_FN_is_word(a, 0), provided as a named predicate for
* readability at call sites. Leak profile as for OSSL_FN_is_word():
* branches only on the operand's public width (its dsize).
*/
int OSSL_FN_is_zero(const OSSL_FN *a);
/**
* Test whether @p a is one.
*
* @param[in] a The operand
* @returns 1 if @p a is one, 0 otherwise
*
* @note Equivalent to OSSL_FN_is_word(a, 1), provided as a named predicate for
* readability at call sites. Leak profile as for OSSL_FN_is_word():
* branches only on the operand's public width (its dsize).
*/
int OSSL_FN_is_one(const OSSL_FN *a);
/**
* Test whether @p a is odd.
*
* @param[in] a The operand
* @returns the least significant bit of @p a (1 if odd, 0 if even)
*
* @note The only control flow branches on the operand's public width (its
* dsize), not on limb values; the returned value is the bit itself,
* which is the information the caller asked for.
*/
int OSSL_FN_is_odd(const OSSL_FN *a);
/*-
* Top/bottom selectors for OSSL_FN_rand() / OSSL_FN_priv_rand(). These are
* caller-chosen public parameters (not secrets); OSSL_FN_rand() branches on
* them to shape the top and bottom bits of the result. Each TOP_* value is
* the number of high bits to force to 1 (0 = unconstrained, 1, 2), matching
* the BOTTOM_* numbering (0 = unconstrained, 1 = force the low bit).
*/
#define OSSL_FN_RAND_TOP_ANY 0
#define OSSL_FN_RAND_TOP_ONE 1
#define OSSL_FN_RAND_TOP_TWO 2
#define OSSL_FN_RAND_BOTTOM_ANY 0
#define OSSL_FN_RAND_BOTTOM_ODD 1
/**
* Fill @p rnd with @p bits random bits.
*
* @param[out] rnd The OSSL_FN for the result
* @param[in] bits The number of random bits to generate
* @param[in] top Top-bit selector (OSSL_FN_RAND_TOP_*)
* @param[in] bottom Bottom-bit selector (OSSL_FN_RAND_BOTTOM_*)
* @param[in] strength The private strength of the generated bytes
* @param[in] libctx The OpenSSL library context (for the DRBG)
* @returns 1 on success, 0 on error
*
* Draws from the public DRBG pool via RAND_bytes_ex(). The library context
* is taken directly as @p libctx. The random bytes are drawn straight into
* rnd->d (no intermediate buffer) and the top/bottom/mask constraints are
* applied as limb value operations; a @p rnd too small for @p bits is
* reported as OSSL_FN_R_RESULT_ARG_TOO_SMALL rather than grown. Leak
* profile as for OSSL_FN_priv_rand().
*/
int OSSL_FN_rand(OSSL_FN *rnd, size_t bits, int top, int bottom,
size_t strength, OSSL_LIB_CTX *libctx);
/**
* Fill @p rnd with @p bits random bits from the private DRBG pool.
*
* @param[out] rnd The OSSL_FN for the result
* @param[in] bits The number of random bits to generate
* @param[in] top Top-bit selector (OSSL_FN_RAND_TOP_*)
* @param[in] bottom Bottom-bit selector (OSSL_FN_RAND_BOTTOM_*)
* @param[in] strength The private strength of the generated bytes
* @param[in] libctx The OpenSSL library context (for the DRBG)
* @returns 1 on success, 0 on error
*
* Draws from the private (non-forward-linkable) DRBG pool via
* RAND_priv_bytes_ex(). This is the private counterpart of OSSL_FN_rand();
* the public/private pool selection is exposed as separate functions rather
* than a flag argument. Control flow branches only on @p bits, @p top,
* @p bottom (all caller-chosen, public) and on the byte-draw return value,
* never on the random bytes themselves; the returned value is the random
* number the caller asked for.
*
* @note See ossl_fn_rand() in crypto/fn/fn_rand.c for the byte-to-limb
* shaping mechanics.
*/
int OSSL_FN_priv_rand(OSSL_FN *rnd, size_t bits, int top, int bottom,
size_t strength, OSSL_LIB_CTX *libctx);
/**
* Generate 0 <= r < range.
*
* @param[out] r The OSSL_FN for the result
* @param[in] range The exclusive upper bound (must be non-zero)
* @param[in] strength The private strength of the generated bytes
* @param[in] libctx The OpenSSL library context (for the DRBG)
* @returns 1 on success, 0 on error
*
* Draws from the public DRBG pool. OSSL_FN is unsigned, so no sign
* rejection is performed. The loop iteration count leaks the magnitude of
* @p range via OSSL_FN_num_bits() and the rejection probability.
*
* The destination @p r must be sized to hold at least
* OSSL_FN_num_bits(@p range) bits. Sizing @p r to hold one extra bit
* (OSSL_FN_num_bits(@p range) + 1) additionally enables the optimized
* "range = 100..._2" path, which draws n + 1 bits; an exactly-sized @p r
* (room for exactly OSSL_FN_num_bits(@p range) bits) uses standard n-bit
* rejection sampling instead. An @p r too small for
* OSSL_FN_num_bits(@p range) bits fails with OSSL_FN_R_RESULT_ARG_TOO_SMALL.
*/
int OSSL_FN_rand_range(OSSL_FN *r, const OSSL_FN *range, size_t strength,
OSSL_LIB_CTX *libctx);
/**
* Generate 0 <= r < range from the private DRBG pool.
*
* @param[out] r The OSSL_FN for the result
* @param[in] range The exclusive upper bound (must be non-zero)
* @param[in] strength The private strength of the generated bytes
* @param[in] libctx The OpenSSL library context (for the DRBG)
* @returns 1 on success, 0 on error
*
* Draws from the private DRBG pool. Leak profile and destination sizing
* as for OSSL_FN_rand_range().
*/
int OSSL_FN_priv_rand_range(OSSL_FN *r, const OSSL_FN *range,
size_t strength, OSSL_LIB_CTX *libctx);
/**
* Shift an OSSL_FN number left by n bits. Truncates the result to fit in r.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] n The number of bits to shift
* @returns 1 on success, 0 on error
*/
int OSSL_FN_lshift(OSSL_FN *r, const OSSL_FN *a, int n);
/**
* Shift an OSSL_FN number left by one bit. Truncates the result to fit in r.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @returns 1 on success, 0 on error
*/
int OSSL_FN_lshift1(OSSL_FN *r, const OSSL_FN *a);
/**
* Shift an OSSL_FN number right by n bits. Truncates the result to fit in r.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] n The number of bits to shift
* @returns 1 on success, 0 on error
*/
int OSSL_FN_rshift(OSSL_FN *r, const OSSL_FN *a, int n);
/**
* Shift an OSSL_FN number right by one bit. Truncates the result to fit in r.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @returns 1 on success, 0 on error
*/
int OSSL_FN_rshift1(OSSL_FN *r, const OSSL_FN *a);
/**
* Add two OSSL_FN numbers.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @returns 1 on success, 0 on error
*/
int OSSL_FN_add(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b);
/**
* Add an OSSL_FN_ULONG word to an OSSL_FN numbers.
*
* @param[in,out] a The OSSL_FN to add the word to
* @param[in] w The OSSL_FN_ULONG word
* @returns 1 on success, 0 on error
*/
int OSSL_FN_add_word(OSSL_FN *a, OSSL_FN_ULONG w);
/**
* Subtract two OSSL_FN numbers.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @returns 1 on success, 0 on error
*/
int OSSL_FN_sub(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b);
/**
* Subtract an OSSL_FN_ULONG word from an OSSL_FN numbers.
*
* @param[in,out] a The OSSL_FN to subtract the word from
* @param[in] w The OSSL_FN_ULONG word
* @returns 1 on success, 0 on error
*/
int OSSL_FN_sub_word(OSSL_FN *a, OSSL_FN_ULONG w);
/**
* Multiply two OSSL_FN numbers. Truncates the result to fit in r.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for one temporary OSSL_FN with res->dsize limbs, plus one frame
* (currently 32 bytes).
*/
int OSSL_FN_mul(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_mul() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_mul().
*/
size_t OSSL_FN_mul_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b);
/**
* Divide two OSSL_FN numbers. Truncates the result to fit in q and r.
*
* @param[out] q The OSSL_FN for the quotient
* @param[out] r The OSSL_FN for the remainder
* @param[in] n The first operand (numerator)
* @param[in] d The second operand (denominator)
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for 1 frame, 4 numbers, and 4 * max(n->dsize, d->dsize) + 2 limbs.
* Note that this provides an upper bound. Actual use of the arena may be
* smaller - see OSSL_FN_div_ctx_size() for an exact, conditional value.
*/
int OSSL_FN_div(OSSL_FN *q, OSSL_FN *r, const OSSL_FN *n, const OSSL_FN *d,
OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_div() needs.
*
* @param[in] q The OSSL_FN for the quotient, or NULL when
* only the remainder is of interest.
* @param[in] r The OSSL_FN for the remainder
* @param[in] n The first operand (numerator)
* @param[in] d The second operand (denominator)
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_div().
* When q is NULL, the size is computed for the modulo case, i.e. as if
* only the remainder is produced.
*/
size_t OSSL_FN_div_ctx_size(const OSSL_FN *q, const OSSL_FN *r,
const OSSL_FN *n, const OSSL_FN *d);
/**
* Add two OSSL_FN numbers modulo m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for one temporary OSSL_FN with max(a->dsize, b->dsize) + 1
* limbs, plus the requirements of OSSL_FN_mod(), plus one frame
* (currently 32 bytes).
*/
int OSSL_FN_mod_add(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m, OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_mod_add() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_mod_add().
*/
size_t OSSL_FN_mod_add_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b, const OSSL_FN *m);
/**
* Add two OSSL_FN numbers modulo m. This is a quick variant that may be
* used if both a and b are less than m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @returns 1 on success, 0 on error
*/
int OSSL_FN_mod_add_quick(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m);
/**
* Subtract two OSSL_FN numbers modulo m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for two temporary OSSL_FNs with m->dsize limbs each, plus an
* additional temporary OSSL_FN with m->dsize limbs if r == m, plus the
* requirements of OSSL_FN_mod(), plus one frame (currently 32 bytes).
*/
int OSSL_FN_mod_sub(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m, OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_mod_sub() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_mod_sub().
*/
size_t OSSL_FN_mod_sub_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b, const OSSL_FN *m);
/**
* Subtract two OSSL_FN numbers modulo m. This is a quick variant that may
* be used if a is less than m and b is of the same bit width as m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @returns 1 on success, 0 on error
*/
int OSSL_FN_mod_sub_quick(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m);
/**
* Multiply two OSSL_FN numbers modulo m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for one temporary OSSL_FN with a->dsize + b->dsize limbs
* (or 2 * a->dsize limbs if a == b), plus the largest of the
* requirements of OSSL_FN_mul(), OSSL_FN_sqr(), and OSSL_FN_mod(),
* plus one frame (currently 32 bytes).
*/
int OSSL_FN_mod_mul(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
const OSSL_FN *m, OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_mod_mul() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The first operand
* @param[in] b The second operand
* @param[in] m The modulus
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_mod_mul().
*/
size_t OSSL_FN_mod_mul_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *b, const OSSL_FN *m);
/**
* Square an OSSL_FN number modulo m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] m The modulus
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for one temporary OSSL_FN with 2 * a->dsize limbs, plus the
* larger of the requirements of OSSL_FN_sqr() and OSSL_FN_mod(),
* plus one frame (currently 32 bytes).
*/
int OSSL_FN_mod_sqr(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *m,
OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_mod_sqr() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] m The modulus
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_mod_sqr().
*/
size_t OSSL_FN_mod_sqr_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *m);
/**
* Left shift an OSSL_FN number by 1 bit, modulo m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] m The modulus
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for one temporary OSSL_FN with m->dsize + 1 limbs, plus the
* requirements of OSSL_FN_mod(), plus one frame (currently 32 bytes).
*/
int OSSL_FN_mod_lshift1(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *m,
OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_mod_lshift1() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] m The modulus
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by
* OSSL_FN_mod_lshift1().
*/
size_t OSSL_FN_mod_lshift1_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
const OSSL_FN *m);
/**
* Left shift an OSSL_FN number by 1 bit, modulo m. This is a quick
* variant that may be used if a is less than m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] m The modulus
* @returns 1 on success, 0 on error
*/
int OSSL_FN_mod_lshift1_quick(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *m);
/**
* Left shift an OSSL_FN number by n bits, modulo m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] n The number of bits to shift
* @param[in] m The modulus
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for one temporary OSSL_FN with m->dsize limbs, plus the
* requirements of OSSL_FN_mod(), plus one frame (currently 32 bytes).
*/
int OSSL_FN_mod_lshift(OSSL_FN *r, const OSSL_FN *a, int n, const OSSL_FN *m,
OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_mod_lshift() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] n The number of bits to shift
* @param[in] m The modulus
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_mod_lshift().
*/
size_t OSSL_FN_mod_lshift_ctx_size(const OSSL_FN *r, const OSSL_FN *a,
int n, const OSSL_FN *m);
/**
* Left shift an OSSL_FN number by n bits, modulo m. This is a quick
* variant that may be used if a is less than m.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] n The number of bits to shift
* @param[in] m The modulus
* @returns 1 on success, 0 on error
*/
int OSSL_FN_mod_lshift_quick(OSSL_FN *r, const OSSL_FN *a, int n,
const OSSL_FN *m);
/**
* Calculate modulo of two OSSL_FN numbers. Truncates the result to fit in r.
*
* @param[out] r The OSSL_FN for the remainder
* @param[in] n The first operand (numerator)
* @param[in] d The second operand (denominator)
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function has the same requirements on ctx as OSSL_FN_div().
* It is equivalent to OSSL_FN_div(NULL, r, n, d, ctx).
*/
static inline int OSSL_FN_mod(OSSL_FN *r, const OSSL_FN *n, const OSSL_FN *d,
OSSL_FN_CTX *ctx)
{
return OSSL_FN_div(NULL, r, n, d, ctx);
}
/**
* Calculate the arena payload size that OSSL_FN_mod() needs.
*
* @param[in] r The OSSL_FN for the remainder
* @param[in] n The numerator
* @param[in] d The denominator
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_mod().
* This is a thin wrapper around OSSL_FN_div_ctx_size() with a NULL
* quotient, since OSSL_FN_mod() is itself a wrapper around OSSL_FN_div().
*/
static inline size_t OSSL_FN_mod_ctx_size(const OSSL_FN *r,
const OSSL_FN *n, const OSSL_FN *d)
{
return OSSL_FN_div_ctx_size(NULL, r, n, d);
}
/**
* Calculate the square of one OSSL_FN number. Truncates the result to fit in r.
*
* @param[out] r The OSSL_FN for the result
* @param[in] a The operand
* @param[in] ctx A context to get temporary OSSL_FN
* instances from.
* @returns 1 on success, 0 on error
*
* @note This function currently requires that the OSSL_FN_CTX has free
* space for two temporary OSSL_FNs, a->dsize * 2 limbs each, plus one
* frame (currently 32 bytes).
*/
int OSSL_FN_sqr(OSSL_FN *r, const OSSL_FN *a, OSSL_FN_CTX *ctx);
/**
* Calculate the arena payload size that OSSL_FN_sqr() needs.
*
* @param[in] r The OSSL_FN for the result
* @param[in] a The operand
* @returns The arena payload size, in bytes.
* @retval 0 on arithmetic overflow or invalid input.
*
* The returned size includes any frame budget needed by OSSL_FN_sqr().
*/
size_t OSSL_FN_sqr_ctx_size(const OSSL_FN *r, const OSSL_FN *a);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,60 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
/**
* @file A set of internal functions to manipulate the OSSL_FN d array, and
* for introspection.
*/
#ifndef OPENSSL_FN_INTERN_H
#define OPENSSL_FN_INTERN_H
#pragma once
#include <stdbool.h>
#include "crypto/fn.h"
#ifdef __cplusplus
extern "C" {
#endif
#if OSSL_FN_BYTES == 4
/* 32-bit systems */
#define OSSL_FN_ULONG_C(n) UINT32_C(n)
#define OSSL_FN_ULONG64_C(hi32, lo32) OSSL_FN_ULONG_C(lo32), OSSL_FN_ULONG_C(hi32)
#elif OSSL_FN_BYTES == 8
/* 64-bit systems */
#define OSSL_FN_ULONG_C(n) UINT64_C(n)
#define OSSL_FN_ULONG64_C(hi32, lo32) (OSSL_FN_ULONG_C(hi32) << 32 | OSSL_FN_ULONG_C(lo32))
#else
#error "OpenSSL doesn't support large numbers on this platform"
#endif
int ossl_fn_set_words(OSSL_FN *f, const OSSL_FN_ULONG *words, size_t limbs);
const OSSL_FN_ULONG *ossl_fn_get_words(const OSSL_FN *f);
size_t ossl_fn_get_dsize(const OSSL_FN *f);
void ossl_fn_set_negative(OSSL_FN *f, bool neg);
bool ossl_fn_is_negative(const OSSL_FN *f);
bool ossl_fn_is_dynamically_allocated(const OSSL_FN *f);
bool ossl_fn_is_securely_allocated(const OSSL_FN *f);
OSSL_FN_ULONG ossl_fn_add_words(OSSL_FN_ULONG *r, size_t rl,
const OSSL_FN_ULONG *a, size_t al,
const OSSL_FN_ULONG *b, size_t bl);
OSSL_FN_ULONG ossl_fn_sub_words(OSSL_FN_ULONG *r, size_t rl,
const OSSL_FN_ULONG *a, size_t al,
const OSSL_FN_ULONG *b, size_t bl);
#ifdef __cplusplus
}
#endif
#endif

38
include/crypto/fnerr.h Normal file
View file

@ -0,0 +1,38 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
* Copyright 2020-2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifndef OSSL_CRYPTO_FNERR_H
#define OSSL_CRYPTO_FNERR_H
#pragma once
#include <openssl/opensslconf.h>
#include <openssl/symhacks.h>
#ifdef __cplusplus
extern "C" {
#endif
int ossl_err_load_OSSL_FN_strings(void);
/*
* OSSL_FN reason codes.
*/
#define OSSL_FN_R_DIV_BY_ZERO 101
#define OSSL_FN_R_INPUT_NOT_REDUCED 103
#define OSSL_FN_R_BITS_TOO_SMALL 105
#define OSSL_FN_R_INVALID_RANGE 106
#define OSSL_FN_R_INVALID_SHIFT 102
#define OSSL_FN_R_RESULT_ARG_TOO_SMALL 100
#define OSSL_FN_R_TOO_MANY_ITERATIONS 107
#ifdef __cplusplus
}
#endif
#endif

View file

@ -13,6 +13,10 @@
#define OSSL_CRYPTO_TYPES_H
#pragma once
/* At some point in the future, this may move to include/openssl/types.h */
typedef struct ossl_fn_st OSSL_FN;
typedef struct ossl_fn_ctx_st OSSL_FN_CTX;
#ifdef OPENSSL_NO_DEPRECATED_3_0
typedef struct rsa_st RSA;
typedef struct rsa_meth_st RSA_METHOD;

View file

@ -21,36 +21,15 @@
#ifndef OPENSSL_NO_STDIO
#include <stdio.h>
#endif
#include <openssl/opensslconf.h>
#include <openssl/types.h>
#include <openssl/crypto.h>
#include <openssl/bnerr.h>
#include <openssl/bn_limbs.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* 64-bit processor with LP64 ABI
*/
#ifdef SIXTY_FOUR_BIT_LONG
typedef unsigned long BN_ULONG;
#define BN_BYTES 8
#endif
/*
* 64-bit processor other than LP64 ABI
*/
#ifdef SIXTY_FOUR_BIT
typedef unsigned long long BN_ULONG;
#define BN_BYTES 8
#endif
#ifdef THIRTY_TWO_BIT
typedef unsigned int BN_ULONG;
#define BN_BYTES 4
#endif
#define BN_BITS2 (BN_BYTES * 8)
#define BN_BITS (BN_BITS2 * 2)
#define BN_TBIT ((BN_ULONG)1 << (BN_BITS2 - 1))

View file

@ -0,0 +1,48 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
/**
* @file Defines the type of large integer limbs.
*
* The large number is composed of words, the size of which is assumed to
* be optimal for the platform it's built for. In many large number texts,
* these words are called "limb". The BIGNUM library also calls this "word".
*
* In OpenSSL code, the BIGNUM "limb" is represented with the type macro
* BN_ULONG.
*/
#ifndef OPENSSL_BN_LIMBS_H
#define OPENSSL_BN_LIMBS_H
#pragma once
#include <openssl/opensslconf.h>
/*
* 64-bit processor with LP64 ABI
*/
#ifdef SIXTY_FOUR_BIT_LONG
typedef unsigned long BN_ULONG;
#define BN_BYTES 8
#endif
/*
* 64-bit processor other than LP64 ABI
*/
#ifdef SIXTY_FOUR_BIT
typedef unsigned long long BN_ULONG;
#define BN_BYTES 8
#endif
#ifdef THIRTY_TWO_BIT
typedef unsigned int BN_ULONG;
#define BN_BYTES 4
#endif
#endif

View file

@ -105,6 +105,7 @@ extern "C" {
#define ERR_LIB_OSSL_ENCODER 59
#define ERR_LIB_OSSL_DECODER 60
#define ERR_LIB_HTTP 61
#define ERR_LIB_OSSL_FN 62
#define ERR_LIB_USER 128

View file

@ -25,7 +25,7 @@ The number `{nn}` is (somewhat loosely) grouped as follows:
00-04 sanity, internal and essential API tests
05-09 individual symmetric cipher algorithms
10-14 math (bignum)
10-14 math (bignum and ossl_fn)
15-19 individual asymmetric cipher algorithms
20-24 openssl commands (some otherwise not tested)
25-29 certificate forms, generation and verification

View file

@ -1830,6 +1830,49 @@ err:
return st;
}
static int file_modinv(STANZA *s)
{
BIGNUM *a = NULL, *m = NULL, *ainv_check = NULL, *ainv = NULL;
int st = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(m = getBN(s, "M"))
|| !TEST_ptr(ainv_check = getBN(s, "ModInv")))
goto err;
if (BN_is_negative(ainv_check)) {
/* a negative testcase */
ERR_set_mark();
if (!TEST_ptr_null(BN_mod_inverse(NULL, a, m, ctx))
|| !TEST_int_eq(ERR_GET_LIB(ERR_peek_last_error()), ERR_LIB_BN)
|| !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
BN_R_NO_INVERSE))
goto err;
ERR_pop_to_mark();
st = 1;
goto err;
}
/* the inverse is unique in [1,M) */
if (!TEST_ptr(ainv = BN_mod_inverse(NULL, a, m, ctx))
|| !equalBN("inv(A) (mod M)", ainv_check, ainv))
goto err;
/* verify A * ainv == 1 (mod M) */
if (!TEST_true(BN_mod_mul(ainv, a, ainv, m, ctx))
|| !TEST_true(BN_is_one(ainv)))
goto err;
st = 1;
err:
BN_free(a);
BN_free(m);
BN_free(ainv_check);
BN_free(ainv);
return st;
}
static int file_gcd(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *gcd = NULL, *ret = NULL;
@ -3324,6 +3367,7 @@ static int file_test_run(STANZA *s)
{ "ModExp", file_modexp },
{ "Exp", file_exp },
{ "ModSqrt", file_modsqrt },
{ "ModInv", file_modinv },
{ "GCD", file_gcd },
};
int numtests = OSSL_NELEM(filetests);

View file

@ -1127,6 +1127,21 @@ IF[{- !$disabled{tests} -}]
DEPEND[rsa_x931_test]=../libcrypto.a libtestutil.a
ENDIF
PROGRAMS{noinst}=fn_internal_test
SOURCE[fn_internal_test]=fn_internal_test.c
INCLUDE[fn_internal_test]=.. ../include ../crypto/fn ../apps/include
DEPEND[fn_internal_test]=../libcrypto.a libtestutil.a
PROGRAMS{noinst}=fn_api_test
SOURCE[fn_api_test]=fn_api_test.c
INCLUDE[fn_api_test]=.. ../include ../crypto/fn ../apps/include
DEPEND[fn_api_test]=../libcrypto.a libtestutil.a
PROGRAMS{noinst}=fntest
SOURCE[fntest]=fntest.c
INCLUDE[fntest]=.. ../include ../crypto/fn ../apps/include
DEPEND[fntest]=../libcrypto.a libtestutil.a
SOURCE[bn_internal_test]=bn_internal_test.c
INCLUDE[bn_internal_test]=.. ../include ../crypto/bn ../apps/include
DEPEND[bn_internal_test]=../libcrypto.a libtestutil.a

3598
test/fn_api_test.c Normal file

File diff suppressed because it is too large Load diff

457
test/fn_internal_test.c Normal file
View file

@ -0,0 +1,457 @@
/*
* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
/**
* @file Internal tests of OSSL_FN
*
* This tests OSSL_FN internals only, i.e. anything that requires including
* ../crypto/fn/fn_local.h, such as introspection.
*/
#include "crypto/fn.h"
#include "crypto/fn_intern.h"
#include "fn_local.h"
#include "testutil.h"
static int test_struct(void)
{
TEST_note("OSSL_FN struct is %zu bytes\n", sizeof(OSSL_FN));
TEST_note("OSSL_FN 'd' array starts at offset %zu\n", offsetof(OSSL_FN, d));
/*
* Note: The working theory for the moment is that the 'd' array *must*
* align with the end of the OSSL_FN struct.
* If it turns out that this isn't the case, we can choose to run
* TEST_size_t_eq() for display purposes, but ignore its result and
* return 1.
*/
return TEST_size_t_eq(sizeof(OSSL_FN), offsetof(OSSL_FN, d));
}
static int test_alloc(void)
{
int ret = 1;
OSSL_FN *f = NULL;
const OSSL_FN_ULONG *u = NULL;
/*
* OSSL_FN_new_bits() calls OSSL_FN_new_bytes(), which calls
* OSSL_FN_new_limbs(), so we're exercising all three in one go.
*
* The curious size formula is there to check that the number of bits that
* is passed in gets properly rounded up to the number of limbs they fit
* into.
* This formula aims for two limbs (each of which is at least 32 bits),
* shaving off 17 bits for demonstration purposes.
*/
if (!TEST_ptr(f = OSSL_FN_new_bits(sizeof(OSSL_FN_ULONG) * 16 - 17))
|| !TEST_true(ossl_fn_is_dynamically_allocated(f))
|| !TEST_false(ossl_fn_is_securely_allocated(f))
|| !TEST_size_t_eq(ossl_fn_get_dsize(f), 2)
|| !TEST_ptr(u = ossl_fn_get_words(f))
|| !TEST_size_t_eq(u[0], 0)
|| !TEST_size_t_eq(u[1], 0))
ret = 0;
OSSL_FN_free(f);
return ret;
}
static int test_secure_alloc(void)
{
int ret = 1;
OSSL_FN *f = NULL;
const OSSL_FN_ULONG *u = NULL;
/*
* OSSL_FN_secure_new_bits() calls OSSL_FN_secure_new_bytes(), which calls
* OSSL_FN_secure_new_limbs(), so we're exercising all three in one go.
*
* The curious size formula is there to check that the number of bits that
* is passed in gets properly rounded up to the number of limbs they fit
* into.
* This formula aims for two limbs (each of which is at least 32 bits),
* shaving off 17 bits for demonstration purposes.
*/
if (!TEST_ptr(f = OSSL_FN_secure_new_bits(sizeof(OSSL_FN_ULONG) * 16 - 17))
|| !TEST_true(ossl_fn_is_dynamically_allocated(f))
|| !TEST_true(ossl_fn_is_securely_allocated(f))
|| !TEST_size_t_eq(ossl_fn_get_dsize(f), 2)
|| !TEST_ptr(u = ossl_fn_get_words(f))
|| !TEST_size_t_eq(u[0], 0)
|| !TEST_size_t_eq(u[1], 0))
ret = 0;
OSSL_FN_free(f);
return ret;
}
static int test_ctx(void)
{
int ret = 1;
OSSL_FN_CTX *ctx = NULL;
OSSL_FN *f = NULL;
const void *token = NULL;
/*
* Make a CTX that is likely to contain two 2048-bit or one 4096-bit OSSL_FN
* and one frame (let's overestimate its size to 128 bytes).
* Note that OSSL_FN_CTX_new() takes a maximum number of limbs in the last
* parameter, so we must ensure that we get that number right.
*/
if (!TEST_ptr(ctx = OSSL_FN_CTX_new(NULL, 1, 2, 4096 / OSSL_FN_BITS))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
/* Check that we can get 1 2048-bit OSSL_FN instance, and check its metadata */
if (!TEST_ptr(token = OSSL_FN_CTX_start(ctx))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048))
|| !TEST_false(ossl_fn_is_dynamically_allocated(f))
|| !TEST_false(ossl_fn_is_securely_allocated(f)))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
/* Check that we can get 2 2048-bit OSSL_FN instances, but not 3 */
if (!TEST_ptr(token = OSSL_FN_CTX_start(ctx))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048))
|| !TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048))
|| !TEST_ptr_null(f = OSSL_FN_CTX_get_bits(ctx, 2048)))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
/* Check that we can get 1 4096-bit OSSL_FN instance, but not 2 */
if (!TEST_ptr(token = OSSL_FN_CTX_start(ctx))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 4096))
|| !TEST_ptr_null(f = OSSL_FN_CTX_get_bits(ctx, 2048)))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
end:
OSSL_FN_CTX_free(ctx);
return ret;
}
static int test_ctx_size(void)
{
int ret = 1;
OSSL_FN_CTX *ctx = NULL;
OSSL_FN *f = NULL;
const void *token = NULL;
size_t size = OSSL_FN_CTX_size(1, 2, 4096 / OSSL_FN_BITS);
if (!TEST_size_t_ne(size, 0)
|| !TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL, size))) {
ret = 0;
goto end;
}
if (!TEST_ptr(token = OSSL_FN_CTX_start(ctx))) {
ret = 0;
goto end;
}
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048))
|| !TEST_false(ossl_fn_is_dynamically_allocated(f))
|| !TEST_false(ossl_fn_is_securely_allocated(f))
|| !TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048))
|| !TEST_ptr_null(f = OSSL_FN_CTX_get_bits(ctx, 2048)))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token)))
ret = 0;
end:
OSSL_FN_CTX_free(ctx);
/*
* Force each term of OSSL_FN_CTX_size() to overflow in turn,
* keeping the others at a minimal valid context (1 frame, 1
* number, 1 limb) so only the overflowing term is unrealistic.
*/
if (!TEST_size_t_eq(OSSL_FN_CTX_size(SIZE_MAX, 1, 1), 0))
ret = 0;
if (!TEST_size_t_eq(OSSL_FN_CTX_size(1, SIZE_MAX, 1), 0))
ret = 0;
if (!TEST_size_t_eq(OSSL_FN_CTX_size(1, 1, SIZE_MAX), 0))
ret = 0;
/* A context must have at least one frame. */
if (!TEST_size_t_eq(OSSL_FN_CTX_size(0, 1, 1), 0))
ret = 0;
/*
* Numbers and limbs must both be present or both absent: a context
* with one budget but not the other can never allocate a usable
* number, since OSSL_FN_CTX_get_limbs() allocates the OSSL_FN header
* and its limbs together.
*/
if (!TEST_size_t_eq(OSSL_FN_CTX_size(1, 0, 1), 0))
ret = 0;
if (!TEST_size_t_eq(OSSL_FN_CTX_size(1, 1, 0), 0))
ret = 0;
if (!TEST_ptr_null(OSSL_FN_CTX_new_size(NULL, SIZE_MAX)))
ret = 0;
/* A size of 0 is the error return of OSSL_FN_CTX_size(). */
if (!TEST_ptr_null(OSSL_FN_CTX_new_size(NULL, 0)))
ret = 0;
return ret;
}
static int test_secure_ctx(void)
{
int ret = 1;
OSSL_FN_CTX *ctx = NULL;
OSSL_FN *f = NULL;
const void *token = NULL;
/*
* Make a CTX that is likely to contain two 2048-bit OSSL_FN and one frame
* (let's overestimate its size to 128 bytes).
* Note that OSSL_FN_CTX_new() takes a maximum number of limbs in the last
* parameter, so we must ensure that we get that number right.
*/
if (!TEST_ptr(ctx = OSSL_FN_CTX_secure_new(NULL, 1, 2, 2048 / OSSL_FN_BITS))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
/* Check that we can get 1 2048-bit OSSL_FN instance, and check its metadata */
if (!TEST_ptr(token = OSSL_FN_CTX_start(ctx))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048))
|| !TEST_false(ossl_fn_is_dynamically_allocated(f))
|| !TEST_true(ossl_fn_is_securely_allocated(f)))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token))) {
ret = 0;
/* It's pointless to try more tests after this failure */
goto end;
}
end:
OSSL_FN_CTX_free(ctx);
return ret;
}
static int test_secure_ctx_size(void)
{
int ret = 1;
OSSL_FN_CTX *ctx = NULL;
OSSL_FN *f = NULL;
const void *token = NULL;
size_t size = OSSL_FN_CTX_size(1, 1, 2048 / OSSL_FN_BITS);
if (!TEST_size_t_ne(size, 0)
|| !TEST_ptr(ctx = OSSL_FN_CTX_secure_new_size(NULL, size))) {
ret = 0;
goto end;
}
if (!TEST_ptr(token = OSSL_FN_CTX_start(ctx))) {
ret = 0;
goto end;
}
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048))
|| !TEST_false(ossl_fn_is_dynamically_allocated(f))
|| !TEST_true(ossl_fn_is_securely_allocated(f))
|| !TEST_ptr_null(f = OSSL_FN_CTX_get_bits(ctx, 2048)))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token)))
ret = 0;
end:
OSSL_FN_CTX_free(ctx);
if (!TEST_ptr_null(OSSL_FN_CTX_secure_new_size(NULL, SIZE_MAX)))
ret = 0;
/* A size of 0 is the error return of OSSL_FN_CTX_size(). */
if (!TEST_ptr_null(OSSL_FN_CTX_secure_new_size(NULL, 0)))
ret = 0;
return ret;
}
static int test_ctx_peak_used(void)
{
int ret = 1;
OSSL_FN_CTX *ctx = NULL;
OSSL_FN *f = NULL;
const void *token1 = NULL;
const void *token2 = NULL;
size_t frames, numbers, limbs;
size_t limbs_2048 = 2048 / 8 / OSSL_FN_BYTES;
size_t limbs_4096 = 4096 / 8 / OSSL_FN_BYTES;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new(NULL, 2, 4, 256))) {
ret = 0;
goto end;
}
/*
* Fresh context.
*/
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 0)
|| !TEST_size_t_eq(numbers, 0)
|| !TEST_size_t_eq(limbs, 0))
ret = 0;
/*
* NULL context: all out parameters set to 0.
*/
OSSL_FN_CTX_peak_usage(NULL, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 0)
|| !TEST_size_t_eq(numbers, 0)
|| !TEST_size_t_eq(limbs, 0))
ret = 0;
/*
* NULL out parameters are tolerated.
*/
OSSL_FN_CTX_peak_usage(ctx, NULL, NULL, NULL);
/*
* Start frame 1.
*/
if (!TEST_ptr(token1 = OSSL_FN_CTX_start(ctx))) {
ret = 0;
goto end;
}
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 1)
|| !TEST_size_t_eq(numbers, 0)
|| !TEST_size_t_eq(limbs, 0))
ret = 0;
/*
* Allocate one number in frame 1.
*/
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048)))
ret = 0;
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 1)
|| !TEST_size_t_eq(numbers, 1)
|| !TEST_size_t_eq(limbs, limbs_2048))
ret = 0;
/*
* Start frame 2 (nested inside frame 1).
*/
if (!TEST_ptr(token2 = OSSL_FN_CTX_start(ctx))) {
ret = 0;
goto end;
}
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 2)
|| !TEST_size_t_eq(numbers, 1)
|| !TEST_size_t_eq(limbs, limbs_2048))
ret = 0;
/*
* Allocate one number in frame 2.
*/
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 4096)))
ret = 0;
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 2)
|| !TEST_size_t_eq(numbers, 2)
|| !TEST_size_t_eq(limbs, limbs_2048 + limbs_4096))
ret = 0;
/*
* Allocate a second number in frame 2.
*/
if (!TEST_ptr(f = OSSL_FN_CTX_get_bits(ctx, 2048)))
ret = 0;
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 2)
|| !TEST_size_t_eq(numbers, 3)
|| !TEST_size_t_eq(limbs, limbs_2048 + limbs_4096 + limbs_2048))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token2))) {
ret = 0;
goto end;
}
/*
* After ending frame 2: peaks must not decrease.
*/
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 2)
|| !TEST_size_t_eq(numbers, 3)
|| !TEST_size_t_eq(limbs, limbs_2048 + limbs_4096 + limbs_2048))
ret = 0;
if (!TEST_true(OSSL_FN_CTX_end(ctx, token1))) {
ret = 0;
goto end;
}
/*
* After ending frame 1: peaks still preserved.
*/
OSSL_FN_CTX_peak_usage(ctx, &frames, &numbers, &limbs);
if (!TEST_size_t_eq(frames, 2)
|| !TEST_size_t_eq(numbers, 3)
|| !TEST_size_t_eq(limbs, limbs_2048 + limbs_4096 + limbs_2048))
ret = 0;
end:
OSSL_FN_CTX_free(ctx);
return ret;
}
int setup_tests(void)
{
ADD_TEST(test_struct);
ADD_TEST(test_alloc);
ADD_TEST(test_secure_alloc);
ADD_TEST(test_ctx);
ADD_TEST(test_ctx_size);
ADD_TEST(test_secure_ctx);
ADD_TEST(test_secure_ctx_size);
ADD_TEST(test_ctx_peak_used);
return 1;
}

766
test/fntest.c Normal file
View file

@ -0,0 +1,766 @@
/*
* Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include "crypto/fn.h"
#include "crypto/bn.h"
#include "crypto/fn_intern.h"
#include "internal/nelem.h"
#include "testutil.h"
/*
* This is a stanza-driven arithmetic test for OSSL_FN. Non-stanza API tests
* belong in fn_api_test.
*/
typedef struct filetest_st {
const char *name;
int (*func)(STANZA *s);
int skipped;
} FILETEST;
static const char *findattr(STANZA *s, const char *key)
{
int i = s->numpairs;
PAIR *pp = s->pairs;
for (; --i >= 0; pp++)
if (OPENSSL_strcasecmp(pp->key, key) == 0)
return pp->value;
return NULL;
}
static int parseBN(BIGNUM **out, const char *in)
{
*out = NULL;
return BN_hex2bn(out, in);
}
static BIGNUM *getBN(STANZA *s, const char *attribute)
{
const char *hex;
BIGNUM *ret = NULL;
if ((hex = findattr(s, attribute)) == NULL) {
TEST_error("%s:%d: Can't find %s", s->test_file, s->start, attribute);
return NULL;
}
if (parseBN(&ret, hex) != (int)strlen(hex)) {
TEST_error("Could not decode '%s'", hex);
BN_free(ret);
return NULL;
}
return ret;
}
static int getint(STANZA *s, int *out, const char *attribute)
{
BIGNUM *ret;
BN_ULONG word;
int st = 0;
if (!TEST_ptr(ret = getBN(s, attribute))
|| !TEST_uint64_t_le(word = BN_get_word(ret), INT_MAX))
goto err;
*out = (int)word;
st = 1;
err:
BN_free(ret);
return st;
}
static int equalBN(const char *op, const BIGNUM *expected, const BIGNUM *actual)
{
if (!TEST_BN_eq(expected, actual)) {
TEST_error("unexpected %s value", op);
return 0;
}
return 1;
}
/*
* Number of limbs needed to hold bn's value, equivalent to the (properly
* adjusted) BIGNUM 'top' field, which is not directly accessible here.
*/
static int limbs(const BIGNUM *bn)
{
int ret = (BN_num_bits(bn) + BN_BITS2 - 1) / BN_BITS2;
return ret > 0 ? ret : 1;
}
static int set_result_addsub(OSSL_FN *r, OSSL_FN *a, int a_neg,
OSSL_FN *b, int b_neg, int *neg)
{
int cmp;
*neg = 0;
if (a_neg == b_neg) {
*neg = a_neg;
return OSSL_FN_add(r, a, b);
}
cmp = OSSL_FN_cmp(a, b);
if (cmp >= 0) {
*neg = a_neg;
return OSSL_FN_sub(r, a, b);
}
*neg = b_neg;
return OSSL_FN_sub(r, b, a);
}
static int file_sum(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *sum = NULL, *ret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *rf = NULL;
int a_neg = 0, b_neg = 0, r_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(sum = getBN(s, "Sum"))
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
nlimbs = limbs(sum);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(set_result_addsub(rf, af, a_neg, bf, b_neg, &r_neg)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, r_neg);
r_acq = 0;
if (!equalBN("A + B", sum, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
BN_free(a);
BN_free(b);
BN_free(sum);
BN_free(ret);
return st;
}
static int file_product(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *product = NULL, *ret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int a_neg = 0, b_neg = 0, r_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(product = getBN(s, "Product"))
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
r_neg = a_neg ^ b_neg;
nlimbs = limbs(product);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mul_ctx_size(rf, af, bf))))
goto err;
if (!TEST_true(OSSL_FN_mul(rf, af, bf, ctx)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, r_neg);
r_acq = 0;
if (!equalBN("A * B", product, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(b);
BN_free(product);
BN_free(ret);
return st;
}
static int file_square(STANZA *s)
{
BIGNUM *a = NULL, *square = NULL, *ret = NULL;
OSSL_FN *af = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(square = getBN(s, "Square"))
|| !TEST_ptr(ret = BN_new()))
goto err;
nlimbs = limbs(square);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_sqr_ctx_size(rf, af))))
goto err;
if (!TEST_true(OSSL_FN_sqr(rf, af, ctx)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, 0);
r_acq = 0;
if (!equalBN("A^2", square, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(square);
BN_free(ret);
return st;
}
static int file_quotient(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *quotient = NULL, *remainder = NULL;
BIGNUM *qret = NULL, *rret = NULL, *mret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *qf = NULL, *rf = NULL, *mf = NULL;
OSSL_FN_CTX *ctx = NULL, *mod_ctx = NULL;
int a_neg = 0, b_neg = 0, q_neg = 0, r_neg = 0, st = 0;
int q_acq = 0, r_acq = 0, m_acq = 0;
int q_limbs = 0, r_limbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(quotient = getBN(s, "Quotient"))
|| !TEST_ptr(remainder = getBN(s, "Remainder"))
|| !TEST_ptr(qret = BN_new())
|| !TEST_ptr(rret = BN_new())
|| !TEST_ptr(mret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
q_neg = a_neg ^ b_neg;
r_neg = a_neg;
q_limbs = limbs(quotient);
r_limbs = limbs(remainder);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(qf = bn_acquire_ossl_fn(qret, q_limbs)))
goto err;
q_acq = 1;
if (!TEST_ptr(rf = bn_acquire_ossl_fn(rret, r_limbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(mf = bn_acquire_ossl_fn(mret, r_limbs)))
goto err;
m_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_div_ctx_size(qf, rf, af, bf)))
|| !TEST_ptr(mod_ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_ctx_size(mf, af, bf))))
goto err;
if (!TEST_true(OSSL_FN_div(qf, rf, af, bf, ctx)))
goto err;
bn_release(qret, q_limbs);
if (!BN_is_zero(qret))
BN_set_negative(qret, q_neg);
q_acq = 0;
bn_release(rret, r_limbs);
if (!BN_is_zero(rret))
BN_set_negative(rret, r_neg);
r_acq = 0;
if (!equalBN("A / B", quotient, qret)
|| !equalBN("A % B", remainder, rret))
goto err;
if (!TEST_true(OSSL_FN_mod(mf, af, bf, mod_ctx)))
goto err;
bn_release(mret, r_limbs);
if (!BN_is_zero(mret))
BN_set_negative(mret, r_neg);
m_acq = 0;
if (!equalBN("A % B (mod)", remainder, mret))
goto err;
st = 1;
err:
if (m_acq)
bn_release(mret, r_limbs);
if (r_acq)
bn_release(rret, r_limbs);
if (q_acq)
bn_release(qret, q_limbs);
OSSL_FN_CTX_free(mod_ctx);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(b);
BN_free(quotient);
BN_free(remainder);
BN_free(qret);
BN_free(rret);
BN_free(mret);
return st;
}
static int file_lshift1(STANZA *s)
{
BIGNUM *a = NULL, *lshift1 = NULL, *two = NULL, *ret = NULL;
OSSL_FN *af = NULL, *lf = NULL, *tf = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int a_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(lshift1 = getBN(s, "LShift1"))
|| !TEST_ptr(two = BN_new())
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
nlimbs = limbs(lshift1);
BN_set_word(two, 2);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(lf = bn_get_ossl_fn(lshift1))
|| !TEST_ptr(tf = bn_get_ossl_fn(two))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mul_ctx_size(rf, af, tf))))
goto err;
/* A + A == LShift1 */
if (!TEST_true(OSSL_FN_add(rf, af, af)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A + A", lshift1, ret))
goto err;
/* A * 2 == LShift1 */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_mul(rf, af, tf, ctx)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A * 2", lshift1, ret))
goto err;
/*
* TODO(FIXNUM): bntest.c also checks the division/modulus identities
* LShift1 / 2 == A ("LShift1 / 2")
* LShift1 % 2 == 0 ("LShift1 % 2")
* using BN_div(). These cannot be ported yet: there is no OSSL_FN_div().
*/
/* A << 1 == LShift1 */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_lshift1(rf, af)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg);
r_acq = 0;
if (!equalBN("A << 1", lshift1, ret))
goto err;
/*
* Round-trip: LShift1 >> 1 == A, done twice to match bntest.c's
* double-check structure.
*
* TODO(FIXNUM): bntest.c's second iteration forces the LSB of LShift1
* to 1 (BN_set_bit(lshift1, 0)) and then checks
* (LShift1 | 1) / 2 == A ("(LShift1 | 1) / 2")
* (LShift | 1) >> 1 == A ("(LShift | 1) >> 1")
* to exercise rshift1's flooring of an odd operand. This cannot be
* ported yet: there is no OSSL_FN_set_bit(), and the division variant
* also needs OSSL_FN_div(). For now we just repeat the even case to
* preserve the two-iteration shape.
*/
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift1(rf, lf)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("LShift >> 1", a, ret))
goto err;
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift1(rf, lf)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("LShift >> 1", a, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(lshift1);
BN_free(two);
BN_free(ret);
return st;
}
static int file_lshift(STANZA *s)
{
BIGNUM *a = NULL, *lshift = NULL, *ret = NULL;
OSSL_FN *af = NULL, *lf = NULL, *rf = NULL;
int a_neg = 0, n = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(lshift = getBN(s, "LShift"))
|| !TEST_ptr(ret = BN_new())
|| !getint(s, &n, "N"))
goto err;
a_neg = BN_is_negative(a);
nlimbs = limbs(lshift);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(lf = bn_get_ossl_fn(lshift))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_lshift(rf, af, n)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg);
r_acq = 0;
if (!equalBN("A << N", lshift, ret))
goto err;
/* Round-trip: shift the result back and recover A */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift(rf, lf, n)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A >> N", a, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
BN_free(a);
BN_free(lshift);
BN_free(ret);
return st;
}
static int file_rshift(STANZA *s)
{
BIGNUM *a = NULL, *rshift = NULL, *ret = NULL;
OSSL_FN *af = NULL, *rf = NULL;
int a_neg = 0, n = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(rshift = getBN(s, "RShift"))
|| !TEST_ptr(ret = BN_new())
|| !getint(s, &n, "N"))
goto err;
a_neg = BN_is_negative(a);
nlimbs = limbs(rshift);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift(rf, af, n)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A >> N", rshift, ret))
goto err;
/* If N == 1, try with rshift1 as well */
if (n == 1) {
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift1(rf, af)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A >> 1 (rshift1)", rshift, ret))
goto err;
}
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
BN_free(a);
BN_free(rshift);
BN_free(ret);
return st;
}
static int file_modmul(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *m = NULL, *mod_mul = NULL, *ret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *rf = NULL, *mf = NULL;
OSSL_FN_CTX *ctx = NULL;
int a_neg = 0, b_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(m = getBN(s, "M"))
|| !TEST_ptr(mod_mul = getBN(s, "ModMul"))
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
nlimbs = limbs(m);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(mf = bn_get_ossl_fn(m))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_mul_ctx_size(rf, af, bf, mf))))
goto err;
/*
* OSSL_FN is unsigned, so the multiplication is on absolute values.
* If the operands have different signs, the non-negative modular
* residue of A * B is M - ((|A| * |B|) mod M), unless that is zero.
*/
if (!TEST_true(OSSL_FN_mod_mul(rf, af, bf, mf, ctx)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
if ((a_neg ^ b_neg) && !BN_is_zero(ret)) {
if (!TEST_true(BN_sub(ret, m, ret)))
goto err;
}
if (!equalBN("A * B (mod M)", mod_mul, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(b);
BN_free(m);
BN_free(mod_mul);
BN_free(ret);
return st;
}
static int file_modsqr(STANZA *s)
{
BIGNUM *a = NULL, *m = NULL, *mod_sqr = NULL, *ret = NULL;
OSSL_FN *af = NULL, *rf = NULL, *mf = NULL;
OSSL_FN_CTX *ctx = NULL;
int st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(m = getBN(s, "M"))
|| !TEST_ptr(mod_sqr = getBN(s, "ModSqr"))
|| !TEST_ptr(ret = BN_new()))
goto err;
nlimbs = limbs(m);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(mf = bn_get_ossl_fn(m))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_sqr_ctx_size(rf, af, mf))))
goto err;
/*
* Squaring is always non-negative, so no sign fixup is needed.
*/
if (!TEST_true(OSSL_FN_mod_sqr(rf, af, mf, ctx)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
if (!equalBN("A^2 (mod M)", mod_sqr, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(m);
BN_free(mod_sqr);
BN_free(ret);
return st;
}
static FILETEST filetests[] = {
{ "Sum", file_sum, 0 },
{ "LShift1", file_lshift1, 0 },
{ "LShift", file_lshift, 0 },
{ "RShift", file_rshift, 0 },
{ "Square", file_square, 0 },
{ "Product", file_product, 0 },
{ "Quotient", file_quotient, 0 },
{ "ModMul", file_modmul, 0 },
{ "ModSqr", file_modsqr, 0 },
{ "ModExp", NULL, 0 },
{ "Exp", NULL, 0 },
{ "ModSqrt", NULL, 0 },
{ "GCD", NULL, 0 },
};
static int file_test_run(STANZA *s)
{
const FILETEST *tp = filetests;
size_t i;
for (i = 0; i < OSSL_NELEM(filetests); i++, tp++) {
if (findattr(s, tp->name) != NULL) {
if (tp->func == NULL) {
filetests[i].skipped++;
return 1;
}
if (!tp->func(s)) {
TEST_info("%s:%d: Failed %s test",
s->test_file, s->start, tp->name);
return 0;
}
return 1;
}
}
TEST_info("%s:%d: Unknown test, skipped", s->test_file, s->start);
return 1;
}
static int run_file_tests(int i)
{
STANZA *s = NULL;
char *testfile = test_get_argument(i);
int c;
size_t j;
for (j = 0; j < OSSL_NELEM(filetests); j++)
filetests[j].skipped = 0;
if (!TEST_ptr(s = OPENSSL_zalloc(sizeof(*s))))
return 0;
if (!test_start_file(s, testfile)) {
OPENSSL_free(s);
return 0;
}
while (!BIO_eof(s->fp) && test_readstanza(s)) {
if (s->numpairs == 0)
continue;
if (!file_test_run(s))
s->errors++;
s->numtests++;
test_clearstanza(s);
}
test_end_file(s);
for (j = 0; j < OSSL_NELEM(filetests); j++)
if (filetests[j].skipped > 0)
TEST_info("%s: skipped %d unsupported %s stanzas",
testfile, filetests[j].skipped, filetests[j].name);
c = s->errors;
OPENSSL_free(s);
return c == 0;
}
OPT_TEST_DECLARE_USAGE("file...\n")
int setup_tests(void)
{
size_t n = test_get_argument_count();
if (!TEST_size_t_gt(n, 0))
return 0;
ADD_ALL_TESTS(run_file_tests, (int)n);
return 1;
}

View file

@ -0,0 +1,19 @@
#! /usr/bin/env perl
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
use strict;
use OpenSSL::Test; # get 'plan'
use OpenSSL::Test::Simple;
use OpenSSL::Test::Utils;
setup("test_internal_fn");
plan skip_all => "This test is unsupported in a shared library build on Windows"
if $^O eq 'MSWin32' && !disabled("shared");
simple_test("test_internal_fn", "fn_internal_test");

View file

@ -16,7 +16,7 @@ use OpenSSL::Test qw/:DEFAULT data_file/;
setup("test_bn");
my @files = qw( bnexp.txt bnmod.txt bnmul.txt bnshift.txt bnsum.txt bngcd.txt );
my @files = qw( bnexp.txt bnmod.txt bnmodinv.txt bnmul.txt bnshift.txt bnsum.txt bngcd.txt );
plan tests => 1 + scalar(@files);

File diff suppressed because it is too large Load diff

22
test/recipes/11-test_fn.t Normal file
View file

@ -0,0 +1,22 @@
#! /usr/bin/env perl
# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
use strict;
use warnings;
use OpenSSL::Test qw/:DEFAULT srctop_file/;
setup("test_fn");
my @files = qw( bnmul.txt bnshift.txt bnsum.txt bnmod.txt );
plan tests => scalar(@files);
foreach my $f (@files) {
ok(run(test(["fntest", srctop_file("test", "recipes", "10-test_bn_data", $f)])),
"running fntest $f");
}

View file

@ -0,0 +1,14 @@
#! /usr/bin/env perl
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
use strict;
use OpenSSL::Test; # get 'plan'
use OpenSSL::Test::Simple;
setup("test_fn_api");
simple_test("test_fn_api", "fn_api_test");