]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add the fixed number type (OSSL_FN) and its allocators and deallocator
authorRichard Levitte <levitte@openssl.org>
Wed, 8 Oct 2025 12:56:34 +0000 (14:56 +0200)
committerRichard Levitte <levitte@openssl.org>
Thu, 16 Oct 2025 13:11:00 +0000 (15:11 +0200)
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)

crypto/build.info
crypto/fn/build.info [new file with mode: 0644]
crypto/fn/fn_lib.c [new file with mode: 0644]
crypto/fn/fn_local.h [new file with mode: 0644]
include/crypto/fn.h [new file with mode: 0644]
include/crypto/types.h
include/openssl/bn.h
include/openssl/bn_limbs.h [new file with mode: 0644]
test/build.info
test/fn_internal_test.c [new file with mode: 0644]
test/recipes/03-test_internal_fn.t [new file with mode: 0644]

index 1f522a729f8929d8a71b675f983998b48bf4ca93..01cdde03e4b6bcb221eff8858462b924f547bd5e 100644 (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 engine \
+        seed sm4 chacha modes fn bn ec rsa dsa dh sm2 dso engine \
         err comp http ocsp cms ts srp cmac ct async ess crmf cmp encode_decode \
         ffc hpke thread lms ml_dsa slh_dsa
 
diff --git a/crypto/fn/build.info b/crypto/fn/build.info
new file mode 100644 (file)
index 0000000..218e4b0
--- /dev/null
@@ -0,0 +1,8 @@
+$LIBCRYPTO=../../libcrypto
+$LIBFIPS=../../providers/libfips.a
+LIBS=$LIBCRYPTO
+
+$COMMON=fn_lib.c
+
+SOURCE[$LIBCRYPTO]=$COMMON
+SOURCE[$LIBFIPS]=$COMMON
diff --git a/crypto/fn/fn_lib.c b/crypto/fn/fn_lib.c
new file mode 100644 (file)
index 0000000..87da4c2
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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 "internal/common.h"
+#include "fn_local.h"
+
+static OSSL_FN *ossl_fn_new_internal(size_t limbs, bool securely)
+{
+    /*
+     * Since the number of limbs is 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 NULL;
+
+    /* Total size of the whole OSSL_FN, in bytes */
+    size_t totalsize = sizeof(OSSL_FN) + limbs * sizeof(OSSL_FN_ULONG);
+
+    /*
+     * If the size ends up being smaller than the bookkeeping struct,
+     * we know that the size calculation has wrapped around and is
+     * therefore invalid.  Also, we don't allow zero byte numbers.
+     */
+    if (totalsize <= sizeof(OSSL_FN))
+        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);
+}
+
+static size_t bytes_to_limbs(size_t size)
+{
+    return (size + sizeof(OSSL_FN_ULONG) - 1) / sizeof(OSSL_FN_ULONG);
+}
+
+OSSL_FN *OSSL_FN_new_bytes(size_t size)
+{
+    return OSSL_FN_new_limbs(bytes_to_limbs(size));
+}
+
+OSSL_FN *OSSL_FN_secure_new_bytes(size_t size)
+{
+    return OSSL_FN_secure_new_limbs(bytes_to_limbs(size));
+}
+
+static size_t bits_to_bytes(size_t size)
+{
+    return (size + 7) / 8;
+}
+
+OSSL_FN *OSSL_FN_new_bits(size_t size)
+{
+    return OSSL_FN_new_bytes(bits_to_bytes(size));
+}
+
+OSSL_FN *OSSL_FN_secure_new_bits(size_t size)
+{
+    return OSSL_FN_secure_new_bytes(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);
+}
diff --git a/crypto/fn/fn_local.h b/crypto/fn/fn_local.h
new file mode 100644 (file)
index 0000000..3d9d33e
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 <openssl/opensslconf.h>
+# include "crypto/fn.h"
+
+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[];
+};
+
+#endif
diff --git a/include/crypto/fn.h b/include/crypto/fn.h
new file mode 100644 (file)
index 0000000..a181a0b
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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 "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_ULONG
+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);
+
+# ifdef  __cplusplus
+}
+# endif
+
+#endif
index 2bdd4a38be77e5490d2f7e7052d1f6b32076f94e..81fde80fbaa84fc95494cced668ada1b2aeab7a2 100644 (file)
@@ -13,6 +13,9 @@
 # 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;
+
 # ifdef OPENSSL_NO_DEPRECATED_3_0
 typedef struct rsa_st RSA;
 typedef struct rsa_meth_st RSA_METHOD;
index ea706dca7f2f091fc92f5f7f6c3ee40082e0eee5..3bf1cac13d180d3e3eec63b07453cacc2e373149 100644 (file)
 # 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
-#  define BN_ULONG        unsigned long
-#  define BN_BYTES        8
-# endif
-
-/*
- * 64-bit processor other than LP64 ABI
- */
-# ifdef SIXTY_FOUR_BIT
-#  define BN_ULONG        unsigned long long
-#  define BN_BYTES        8
-# endif
-
-# ifdef THIRTY_TWO_BIT
-#  define BN_ULONG        unsigned int
-#  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))
diff --git a/include/openssl/bn_limbs.h b/include/openssl/bn_limbs.h
new file mode 100644 (file)
index 0000000..d3302ae
--- /dev/null
@@ -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
+#  define BN_ULONG        unsigned long
+#  define BN_BYTES        8
+# endif
+
+/*
+ * 64-bit processor other than LP64 ABI
+ */
+# ifdef SIXTY_FOUR_BIT
+#  define BN_ULONG        unsigned long long
+#  define BN_BYTES        8
+# endif
+
+# ifdef THIRTY_TWO_BIT
+#  define BN_ULONG        unsigned int
+#  define BN_BYTES        4
+# endif
+
+#endif
index 57ee94071a82209d16fd04e71385f7c7724d421b..a3d1af20b03214e3340751391fa21a0164f42483 100644 (file)
@@ -1042,6 +1042,11 @@ 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
+
     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_internal_test.c b/test/fn_internal_test.c
new file mode 100644 (file)
index 0000000..fd3f17f
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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 "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;
+
+    /*
+     * 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_uint_eq(f->is_dynamically_allocated, 1)
+        || !TEST_uint_eq(f->is_securely_allocated, 0)
+        || !TEST_int_eq(f->dsize, 2)
+        || !TEST_size_t_eq(f->d[0], 0)
+        || !TEST_size_t_eq(f->d[1], 0))
+        ret = 0;
+    OSSL_FN_free(f);
+
+    return ret;
+}
+
+static int test_secure_alloc(void)
+{
+    int ret = 1;
+    OSSL_FN *f = 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_uint_eq(f->is_dynamically_allocated, 1)
+        || !TEST_uint_eq(f->is_securely_allocated, 1)
+        || !TEST_int_eq(f->dsize, 2)
+        || !TEST_size_t_eq(f->d[0], 0)
+        || !TEST_size_t_eq(f->d[1], 0))
+        ret = 0;
+    OSSL_FN_free(f);
+
+    return ret;
+}
+
+int setup_tests(void)
+{
+    ADD_TEST(test_struct);
+    ADD_TEST(test_alloc);
+    ADD_TEST(test_secure_alloc);
+
+    return 1;
+}
diff --git a/test/recipes/03-test_internal_fn.t b/test/recipes/03-test_internal_fn.t
new file mode 100644 (file)
index 0000000..46a477f
--- /dev/null
@@ -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");