From: Richard Levitte Date: Thu, 16 Oct 2025 12:58:43 +0000 (+0200) Subject: OSSL_FN: Add 'add' and 'sub' functions X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=22a9b22d84abaec894da22c9e349c60dd40565bd;p=thirdparty%2Fopenssl.git 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 Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/29028) --- diff --git a/crypto/fn/build.info b/crypto/fn/build.info index 694acce2e4d..a7dc915760f 100644 --- a/crypto/fn/build.info +++ b/crypto/fn/build.info @@ -2,7 +2,7 @@ $LIBCRYPTO=../../libcrypto $LIBFIPS=../../providers/libfips.a LIBS=$LIBCRYPTO -$COMMON=fn_err.c fn_lib.c fn_intern.c +$COMMON=fn_err.c fn_lib.c fn_intern.c fn_addsub.c SOURCE[$LIBCRYPTO]=$COMMON SOURCE[$LIBFIPS]=$COMMON diff --git a/crypto/fn/fn_addsub.c b/crypto/fn/fn_addsub.c new file mode 100644 index 00000000000..6c81c3c7330 --- /dev/null +++ b/crypto/fn/fn_addsub.c @@ -0,0 +1,126 @@ +/* + * 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" + +/* add of b to a. */ +/* unsigned add of b to a, r can be equal to a or b. */ +int OSSL_FN_add(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b) +{ + /* + * 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 (a->dsize < b->dsize) { + const OSSL_FN *tmp; + + tmp = a; + a = b; + b = tmp; + } + + /* At this point, we know that a->dsize >= b->dsize */ + size_t max = a->dsize; + size_t min = b->dsize; + size_t rs = r->dsize; + + /* + * 'r' must be able to contain the result. This is on the caller. + */ + if (!ossl_assert(max <= rs)) { + ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_RESULT_ARG_TOO_SMALL); + return 0; + } + + const OSSL_FN_ULONG *ap = a->d; + const OSSL_FN_ULONG *bp = b->d; + + OSSL_FN_ULONG *rp = r->d; + OSSL_FN_ULONG carry = bn_add_words(rp, ap, bp, (int)min); + + rp += min; + ap += min; + + for (size_t dif = max - min; dif > 0; dif--, ap++, rp++) { + OSSL_FN_ULONG t1 = *ap; + OSSL_FN_ULONG t2 = (t1 + carry) & OSSL_FN_MASK; + + *rp = t2; + carry &= (t2 == 0); + } + + for (size_t dif = r->dsize - max; dif > 0; dif--, rp++) { + OSSL_FN_ULONG t1 = 0; + OSSL_FN_ULONG t2 = (t1 + carry) & OSSL_FN_MASK; + + *rp = carry; + carry &= (t2 == 0); + } + + return 1; +} + +/* unsigned subtraction of b from a */ +int OSSL_FN_sub(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b) +{ + size_t max = (a->dsize >= b->dsize) ? a->dsize : b->dsize; + size_t min = (a->dsize <= b->dsize) ? a->dsize : b->dsize; + size_t rs = r->dsize; + + /* + * 'r' must be able to contain the result. This is on the caller. + */ + if (!ossl_assert(max <= rs)) { + ERR_raise(ERR_LIB_OSSL_FN, OSSL_FN_R_RESULT_ARG_TOO_SMALL); + return 0; + } + + const OSSL_FN_ULONG *ap = a->d; + const OSSL_FN_ULONG *bp = b->d; + OSSL_FN_ULONG *rp = r->d; + OSSL_FN_ULONG borrow = bn_sub_words(rp, ap, bp, (int)min); + + /* + * TODO(FIXNUM): everything following isn't strictly constant-time, + * and could use improvement in that regard. + */ + + ap += min; + bp += min; + rp += min; + + const OSSL_FN_ULONG *maxp = (a->dsize >= b->dsize) ? ap : bp; + + /* "sign" borrow, depending on if maxp == ap or maxp == bp */ + borrow *= (OSSL_FN_ULONG)((a->dsize >= b->dsize) ? 1 : -1); + + /* calculate the result of borrowing from more significant limbs */ + for (size_t dif = max - min; dif > 0; dif--, maxp++, rp++) { + OSSL_FN_ULONG t1 = *maxp; + OSSL_FN_ULONG t2 = (t1 - borrow) & OSSL_FN_MASK; + + *rp = t2; + borrow &= (t1 == 0); + } + + /* Finally, fill in the rest of the result array by borrowing from zeros */ + for (size_t dif = rs - max; dif > 0; dif--, rp++) { + OSSL_FN_ULONG t1 = 0; + OSSL_FN_ULONG t2 = (t1 - borrow) & OSSL_FN_MASK; + + *rp = t2; + borrow &= (t1 == 0); + } + + return 1; +} diff --git a/crypto/fn/fn_local.h b/crypto/fn/fn_local.h index c30ab723044..522548dac41 100644 --- a/crypto/fn/fn_local.h +++ b/crypto/fn/fn_local.h @@ -11,6 +11,8 @@ #define OSSL_CRYPTO_FN_LOCAL_H #include +#include +#include #include #include #include "internal/common.h" @@ -29,6 +31,19 @@ #define OSSL_FN_HIGH_BIT_MASK (OSSL_FN_ULONG_C(1) << (OSSL_FN_BYTES * 8 - 1)) +#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_HIGH_BIT_MASK (OSSL_FN_ULONG_C(1) << (OSSL_FN_BYTES * 8 - 1)) + struct ossl_fn_st { /* Flag: alloced with OSSL_FN_new() or OSSL_FN_secure_new() */ unsigned int is_dynamically_allocated : 1; diff --git a/include/crypto/fn.h b/include/crypto/fn.h index 1270856194f..e21038b9b41 100644 --- a/include/crypto/fn.h +++ b/include/crypto/fn.h @@ -128,6 +128,51 @@ void OSSL_FN_clear_free(OSSL_FN *f); */ void OSSL_FN_clear(OSSL_FN *f); +/* + * 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. + */ + +/** + * 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, const 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, const OSSL_FN_ULONG *w); + #ifdef __cplusplus } #endif diff --git a/test/README-dev.md b/test/README-dev.md index 3b855d8519a..3c29fe28940 100644 --- a/test/README-dev.md +++ b/test/README-dev.md @@ -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 diff --git a/test/build.info b/test/build.info index b8aab8a4cd4..73785cd8645 100644 --- a/test/build.info +++ b/test/build.info @@ -1043,6 +1043,11 @@ IF[{- !$disabled{tests} -}] 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 + 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 diff --git a/test/fn_api_test.c b/test/fn_api_test.c new file mode 100644 index 00000000000..949901d55bb --- /dev/null +++ b/test/fn_api_test.c @@ -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 + */ + +/** + * @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 +#include "crypto/fn.h" +#include "crypto/fn_intern.h" +#include "testutil.h" + +/* + * Helper to pollute a number before writing to it. + * This is a destructive function, use with care! + */ +static int pollute(OSSL_FN *f, size_t start, size_t end) +{ + /* Constness deliberately violated here */ + OSSL_FN_ULONG *u = (OSSL_FN_ULONG *)ossl_fn_get_words(f); + size_t l = ossl_fn_get_dsize(f); + + if (end > l) + end = l; + if (start > end) + start = end; + + unsigned char tmp_char; + (void)RAND_bytes(&tmp_char, 1); + memset(u + start, tmp_char, sizeof(OSSL_FN_ULONG) * (end - start)); + return 1; +} + +static const OSSL_FN_ULONG num[][8 / OSSL_FN_BYTES] = { + /* num[0] */ { OSSL_FN_ULONG64_C(0x80000000, 0x00000001) }, + /* num[1] */ { OSSL_FN_ULONG64_C(0x00000001, 0x80000000) }, + /* num[2] */ { OSSL_FN_ULONG64_C(0x01234567, 0x89abcdef) }, + /* num[3] */ { OSSL_FN_ULONG64_C(0xfedcba98, 0x76543210) }, +}; + +static const OSSL_FN_ULONG expected_add_num_num[4][4][3] = { + { + /* num[0] + num[0] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000002), OSSL_FN_ULONG_C(0x1) }, + /* num[0] + num[1] */ { OSSL_FN_ULONG64_C(0x80000001, 0x80000001) }, + /* num[0] + num[2] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf0) }, + /* num[0] + num[3] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x76543211), OSSL_FN_ULONG_C(0x1) }, + }, + { + /* num[1] + num[0] */ { OSSL_FN_ULONG64_C(0x80000001, 0x80000001) }, + /* num[1] + num[1] */ { OSSL_FN_ULONG64_C(0x00000003, 0x00000000) }, + /* num[1] + num[2] */ { OSSL_FN_ULONG64_C(0x01234569, 0x09abcdef) }, + /* num[1] + num[3] */ { OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543210) }, + }, + { + /* num[2] + num[0] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf0) }, + /* num[2] + num[1] */ { OSSL_FN_ULONG64_C(0x01234569, 0x09abcdef) }, + /* num[2] + num[2] */ { OSSL_FN_ULONG64_C(0x02468acf, 0x13579bde) }, + /* num[2] + num[3] */ { OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + }, + { + /* num[3] + num[0] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x76543211), OSSL_FN_ULONG_C(0x1) }, + /* num[3] + num[1] */ { OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543210) }, + /* num[3] + num[2] */ { OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + /* num[3] + num[3] */ { OSSL_FN_ULONG64_C(0xfdb97530, 0xeca86420), OSSL_FN_ULONG_C(0x1) }, + }, +}; + +static int test_add(int i) +{ + size_t i1 = i / 4; + size_t i2 = i % 4; + const OSSL_FN_ULONG *n1 = num[i1]; + size_t n1_limbs = sizeof(num[i1]) / OSSL_FN_BYTES; + const OSSL_FN_ULONG *n2 = num[i2]; + size_t n2_limbs = sizeof(num[i2]) / OSSL_FN_BYTES; + const OSSL_FN_ULONG *ex = expected_add_num_num[i1][i2]; + size_t ex_limbs = sizeof(expected_add_num_num[i1][i2]) / OSSL_FN_BYTES; + int ret = 1; + OSSL_FN *num1 = NULL, *num2 = NULL, *res = NULL; + const OSSL_FN_ULONG *u = NULL; + + /* To test that OSSL_FN_add() does a complete job, 'res' is pre-polluted */ + + if (!TEST_ptr(num1 = OSSL_FN_new_limbs(n1_limbs)) + || !TEST_ptr(num2 = OSSL_FN_new_limbs(n2_limbs)) + || !TEST_ptr(res = OSSL_FN_new_limbs(ex_limbs)) + || !TEST_true(pollute(res, 0, ex_limbs)) + || !TEST_true(ossl_fn_set_words(num1, n1, n1_limbs)) + || !TEST_true(ossl_fn_set_words(num2, n2, n2_limbs)) + || !TEST_true(OSSL_FN_add(res, num1, num2)) + || !TEST_ptr(u = ossl_fn_get_words(res)) + || !TEST_mem_eq(u, ossl_fn_get_dsize(res) * OSSL_FN_BYTES, + ex, ex_limbs * OSSL_FN_BYTES)) + ret = 0; + OSSL_FN_free(num1); + OSSL_FN_free(num2); + OSSL_FN_free(res); + + return ret; +} + +/* Dimension the expected arrays to fit 4 32-bit limbs */ +#define EXPECTED_LIMBS ((16 + OSSL_FN_BYTES - 1) / OSSL_FN_BYTES) +static OSSL_FN_ULONG expected_sub_num_num[4][4][EXPECTED_LIMBS] = { + { + /* num[0] - num[0] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) }, + /* num[0] - num[1] */ { OSSL_FN_ULONG64_C(0x7ffffffe, 0x80000001) }, + /* num[0] - num[2] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x76543212) }, + /* num[0] - num[3] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf1), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + }, + { + /* num[1] - num[0] */ { OSSL_FN_ULONG64_C(0x80000001, 0x7fffffff), + OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + /* num[1] - num[1] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) }, + /* num[1] - num[2] */ { OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543211), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + /* num[1] - num[3] */ { OSSL_FN_ULONG64_C(0x01234569, 0x09abcdf0), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + }, + { + /* num[2] - num[0] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdee), + OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + /* num[2] - num[1] */ { OSSL_FN_ULONG64_C(0x01234566, 0x09abcdef) }, + /* num[2] - num[2] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) }, + /* num[2] - num[3] */ { OSSL_FN_ULONG64_C(0x02468acf, 0x13579bdf), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) }, + }, + { + /* num[3] - num[0] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x7654320f) }, + /* num[3] - num[1] */ { OSSL_FN_ULONG64_C(0xfedcba96, 0xf6543210) }, + /* num[3] - num[2] */ { OSSL_FN_ULONG64_C(0xfdb97530, 0xeca86421) }, + /* num[3] - num[3] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) }, + }, +}; + +static int test_sub(int i) +{ + size_t i1 = i / 4; + size_t i2 = i % 4; + const OSSL_FN_ULONG *n1 = num[i1]; + size_t n1_limbs = sizeof(num[i1]) / OSSL_FN_BYTES; + const OSSL_FN_ULONG *n2 = num[i2]; + size_t n2_limbs = sizeof(num[i2]) / OSSL_FN_BYTES; + const OSSL_FN_ULONG *ex = expected_sub_num_num[i1][i2]; + size_t ex_limbs = sizeof(expected_sub_num_num[i1][i2]) / OSSL_FN_BYTES; + int ret = 1; + OSSL_FN *num1 = NULL, *num2 = NULL, *res = NULL; + const OSSL_FN_ULONG *u = NULL; + + /* To test that OSSL_FN_sub() does a complete job, 'res' is pre-polluted */ + + if (!TEST_ptr(num1 = OSSL_FN_new_limbs(n1_limbs)) + || !TEST_ptr(num2 = OSSL_FN_new_limbs(n2_limbs)) + || !TEST_ptr(res = OSSL_FN_new_limbs(ex_limbs)) + || !TEST_true(pollute(res, 0, ex_limbs)) + || !TEST_true(ossl_fn_set_words(num1, n1, n1_limbs)) + || !TEST_true(ossl_fn_set_words(num2, n2, n2_limbs)) + || !TEST_true(OSSL_FN_sub(res, num1, num2)) + || !TEST_ptr(u = ossl_fn_get_words(res)) + || !TEST_mem_eq(u, ossl_fn_get_dsize(res) * OSSL_FN_BYTES, + ex, ex_limbs * OSSL_FN_BYTES)) + ret = 0; + OSSL_FN_free(num1); + OSSL_FN_free(num2); + OSSL_FN_free(res); + + return ret; +} + +int setup_tests(void) +{ + ADD_ALL_TESTS(test_add, 16); + ADD_ALL_TESTS(test_sub, 16); + + return 1; +} diff --git a/test/recipes/11-test_fn_api.t b/test/recipes/11-test_fn_api.t new file mode 100644 index 00000000000..0bec5d2a83f --- /dev/null +++ b/test/recipes/11-test_fn_api.t @@ -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_fn_api"); + +plan skip_all => "This test is unsupported in a shared library build on Windows" + if $^O eq 'MSWin32' && !disabled("shared"); + +simple_test("test_fn_api", "fn_api_test");