From: Niels Möller Date: Wed, 1 Jul 2026 06:29:04 +0000 (+0200) Subject: Return success/failure from slh-dsa signing functions. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5dbdcef3d03195baf4d5871d43fa87e8f7c081e6;p=thirdparty%2Fnettle.git Return success/failure from slh-dsa signing functions. --- diff --git a/ChangeLog b/ChangeLog index 6346a872..beecc55b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2026-06-30 Niels Möller + + * slh-dsa.c (_slh_dsa_sign): Change return type from void to int. + After checking expected root hash with memeql_sec, return result + rather than asserting that it is true. + * slh-dsa-sha2-128f.c (slh_dsa_sha2_128f_sign): Add return value, + return success/failure from _slh_dsa_sign, and on failure, clear + output signature to all zeros. + * slh-dsa-sha2-128s.c (slh_dsa_sha2_128s_sign): Likewise. + * slh-dsa-shake-128f.c (slh_dsa_shake_128f_sign): Likewise. + * slh-dsa-shake-128s.c (slh_dsa_shake_128s_sign): Likewise. + * testsuite/slh-dsa-test.c (test_slh_dsa): Check that signing + function returns failure, if public or private key is corrupted. + * nettle.texinfo (SLH-DSA): Update documentation. + 2026-06-05 Niels Möller * ml-kem-internal.c (compress, reduce, mod_sub, mod_add): Use diff --git a/examples/hogweed-benchmark.c b/examples/hogweed-benchmark.c index 58547d53..246efd7a 100644 --- a/examples/hogweed-benchmark.c +++ b/examples/hogweed-benchmark.c @@ -901,7 +901,7 @@ struct slh_dsa_ctx uint8_t key[SLH_DSA_128_KEY_SIZE]; uint8_t msg[10]; uint8_t *sig; - void (*sign)(const uint8_t *pub, const uint8_t *priv, + int (*sign)(const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature); int (*verify)(const uint8_t *pub, @@ -985,7 +985,8 @@ static void bench_slh_dsa_sign (void *p) { struct slh_dsa_ctx *ctx = p; - ctx->sign (ctx->pub, ctx->key, sizeof (ctx->msg), ctx->msg, ctx->sig); + int res = ctx->sign (ctx->pub, ctx->key, sizeof (ctx->msg), ctx->msg, ctx->sig); + assert (res); } static void diff --git a/nettle.texinfo b/nettle.texinfo index 3802b3b3..3047b836 100644 --- a/nettle.texinfo +++ b/nettle.texinfo @@ -5837,6 +5837,18 @@ traversal of all leaves of the corresponding Merkle trees, and this is several orders of magnitude slower than signing using elliptic curves or RSA. +The signing operation can recompute the top-level Merkle tree root +almost for free. Nettle's SLH-DSA signing functions do that, and compare +the resulting root hash to the provided public key. If the root hash is +not as expected, the signing operation fails, and the returned signature +is cleared to all zeros before return. This check is intended to detect +invalid inputs, accidental corruption, and some types of software or +hardware bugs that could affect the computation, but it is not a solid +defense against deliberate fault injection attacks. Checking the return +value from these signing functions is not strictly required; it's the +application's choice if it prefers to handle this error, or pass on an +invalid all-zero signature. + Nettle currently implements the four SLH-DSA variants designed for ``128-bit security'': There's the choice of either SHA256 (in the SHA2 family) or SHAKE256 (in the SHA3 family) as the underlying hash @@ -5893,13 +5905,14 @@ typically called with @var{root} = @var{public_seed} + @code{SLH_DSA_128_SEED_SIZE}. @end deftypefun -@deftypefun void slh_dsa_shake_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) -@deftypefunx void slh_dsa_shake_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) -@deftypefunx void slh_dsa_sha2_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) -@deftypefunx void slh_dsa_sha2_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) +@deftypefun int slh_dsa_shake_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) +@deftypefunx int slh_dsa_shake_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) +@deftypefunx int slh_dsa_sha2_128s_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) +@deftypefunx int slh_dsa_sha2_128f_sign (const uint8_t *@var{pub}, const uint8_t *@var{priv}, size_t @var{length}, const uint8_t *@var{msg}, uint8_t *@var{signature}) Signs a message using the provided key pair. The size of the resulting signature is @code{SLH_DSA_128S_SIGNATURE_SIZE} or -@code{SLH_DSA_128F_SIGNATURE_SIZE} for respective functions. +@code{SLH_DSA_128F_SIGNATURE_SIZE} for respective functions. Returns 1 +on success, 0 on failure (see above on what failure means). @end deftypefun @deftypefun int slh_dsa_shake_128s_verify (const uint8_t *@var{pub}, size_t @var{length}, const uint8_t *@var{msg}, const uint8_t *@var{signature}) diff --git a/slh-dsa-internal.h b/slh-dsa-internal.h index 209e58fe..4db2ddcd 100644 --- a/slh-dsa-internal.h +++ b/slh-dsa-internal.h @@ -282,7 +282,7 @@ _slh_dsa_pure_rdigest (const struct slh_hash *hash, size_t length, const uint8_t *msg, uint8_t *randomizer, size_t digest_size, uint8_t *digest); -void +int _slh_dsa_sign (const struct slh_dsa_params *params, const struct slh_hash *hash, const uint8_t *pub, const uint8_t *priv, diff --git a/slh-dsa-sha2-128f.c b/slh-dsa-sha2-128f.c index a121bba5..62c181ee 100644 --- a/slh-dsa-sha2-128f.c +++ b/slh-dsa-sha2-128f.c @@ -35,6 +35,8 @@ # include "config.h" #endif +#include + #include "slh-dsa.h" #include "slh-dsa-internal.h" @@ -65,19 +67,25 @@ slh_dsa_sha2_128f_generate_keypair (uint8_t *pub, uint8_t *priv, } /* Only the "pure" and deterministic variant. */ -void +int slh_dsa_sha2_128f_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature) { struct sha256_ctx tree_ctx, scratch_ctx; uint8_t digest[SLH_DSA_M]; + int res; + _slh_dsa_pure_rdigest (&_slh_hash_sha256, pub, priv + _SLH_DSA_128_SIZE, length, msg, signature, sizeof (digest), digest); - _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_sha256, - pub, priv, digest, signature + _SLH_DSA_128_SIZE, - &tree_ctx, &scratch_ctx); + res = _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_sha256, + pub, priv, digest, signature + _SLH_DSA_128_SIZE, + &tree_ctx, &scratch_ctx); + if (!res) + memset (signature, 0, SLH_DSA_128F_SIGNATURE_SIZE); + + return res; } int diff --git a/slh-dsa-sha2-128s.c b/slh-dsa-sha2-128s.c index 7dafd17f..1ac8e543 100644 --- a/slh-dsa-sha2-128s.c +++ b/slh-dsa-sha2-128s.c @@ -35,6 +35,8 @@ # include "config.h" #endif +#include + #include "slh-dsa.h" #include "slh-dsa-internal.h" @@ -65,19 +67,25 @@ slh_dsa_sha2_128s_generate_keypair (uint8_t *pub, uint8_t *priv, } /* Only the "pure" and deterministic variant. */ -void +int slh_dsa_sha2_128s_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature) { struct sha256_ctx tree_ctx, scratch_ctx; uint8_t digest[SLH_DSA_M]; + int res; + _slh_dsa_pure_rdigest (&_slh_hash_sha256, pub, priv + _SLH_DSA_128_SIZE, length, msg, signature, sizeof (digest), digest); - _slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_sha256, - pub, priv, digest, signature + _SLH_DSA_128_SIZE, - &tree_ctx, &scratch_ctx); + res =_slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_sha256, + pub, priv, digest, signature + _SLH_DSA_128_SIZE, + &tree_ctx, &scratch_ctx); + if (!res) + memset (signature, 0, SLH_DSA_128S_SIGNATURE_SIZE); + + return res; } int diff --git a/slh-dsa-shake-128f.c b/slh-dsa-shake-128f.c index a0901f69..1161f582 100644 --- a/slh-dsa-shake-128f.c +++ b/slh-dsa-shake-128f.c @@ -35,6 +35,8 @@ # include "config.h" #endif +#include + #include "slh-dsa.h" #include "slh-dsa-internal.h" @@ -65,19 +67,25 @@ slh_dsa_shake_128f_generate_keypair (uint8_t *pub, uint8_t *priv, } /* Only the "pure" and deterministic variant. */ -void +int slh_dsa_shake_128f_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature) { struct sha3_ctx tree_ctx, scratch_ctx; uint8_t digest[SLH_DSA_M]; + int res; + _slh_dsa_pure_rdigest (&_slh_hash_shake, pub, priv + _SLH_DSA_128_SIZE, length, msg, signature, sizeof (digest), digest); - _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_shake, - pub, priv, digest, signature + _SLH_DSA_128_SIZE, - &tree_ctx, &scratch_ctx); + res = _slh_dsa_sign (&_slh_dsa_128f_params, &_slh_hash_shake, + pub, priv, digest, signature + _SLH_DSA_128_SIZE, + &tree_ctx, &scratch_ctx); + if (!res) + memset (signature, 0, SLH_DSA_128F_SIGNATURE_SIZE); + + return res; } int diff --git a/slh-dsa-shake-128s.c b/slh-dsa-shake-128s.c index 8dd0d2fe..b8a4062b 100644 --- a/slh-dsa-shake-128s.c +++ b/slh-dsa-shake-128s.c @@ -35,6 +35,8 @@ # include "config.h" #endif +#include + #include "slh-dsa.h" #include "slh-dsa-internal.h" @@ -65,19 +67,25 @@ slh_dsa_shake_128s_generate_keypair (uint8_t *pub, uint8_t *priv, } /* Only the "pure" and deterministic variant. */ -void +int slh_dsa_shake_128s_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature) { struct sha3_ctx tree_ctx, scratch_ctx; uint8_t digest[SLH_DSA_M]; + int res; + _slh_dsa_pure_rdigest (&_slh_hash_shake, pub, priv + _SLH_DSA_128_SIZE, length, msg, signature, sizeof (digest), digest); - _slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_shake, - pub, priv, digest, signature + _SLH_DSA_128_SIZE, - &tree_ctx, &scratch_ctx); + res = _slh_dsa_sign (&_slh_dsa_128s_params, &_slh_hash_shake, + pub, priv, digest, signature + _SLH_DSA_128_SIZE, + &tree_ctx, &scratch_ctx); + if (!res) + memset (signature, 0, SLH_DSA_128S_SIGNATURE_SIZE); + + return res; } int diff --git a/slh-dsa.c b/slh-dsa.c index 4a3e9a5b..9ee74d52 100644 --- a/slh-dsa.c +++ b/slh-dsa.c @@ -35,7 +35,6 @@ # include "config.h" #endif -#include #include #include "memops.h" @@ -66,7 +65,7 @@ _slh_dsa_pure_rdigest (const struct slh_hash *hash, _slh_dsa_pure_digest (hash, pub, length, msg, randomizer, digest_size, digest); } -void +int _slh_dsa_sign (const struct slh_dsa_params *params, const struct slh_hash *hash, const uint8_t *pub, const uint8_t *priv, @@ -103,7 +102,7 @@ _slh_dsa_sign (const struct slh_dsa_params *params, _xmss_sign (&merkle_ctx, params->xmss.h, leaf_idx, root, signature, root); } - assert (memeql_sec (root, pub + _SLH_DSA_128_SIZE, sizeof (root))); + return memeql_sec (root, pub + _SLH_DSA_128_SIZE, sizeof (root)); } int diff --git a/slh-dsa.h b/slh-dsa.h index 0d4c774d..09b8edf9 100644 --- a/slh-dsa.h +++ b/slh-dsa.h @@ -100,19 +100,19 @@ slh_dsa_sha2_128f_generate_keypair (uint8_t *pub, uint8_t *key, void *random_ctx, nettle_random_func *random); /* Only the "pure" and deterministic variant. */ -void +int slh_dsa_shake_128s_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature); -void +int slh_dsa_shake_128f_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature); -void +int slh_dsa_sha2_128s_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature); -void +int slh_dsa_sha2_128f_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature); diff --git a/testsuite/slh-dsa-test.c b/testsuite/slh-dsa-test.c index 1be9b24f..5b3ab185 100644 --- a/testsuite/slh-dsa-test.c +++ b/testsuite/slh-dsa-test.c @@ -255,16 +255,15 @@ test_slh_dsa_128_root (root_func *f, ASSERT (MEMEQ (sizeof (pub), pub, exp_pub->data)); } -typedef void sign_func (const uint8_t *pub, const uint8_t *priv, - size_t length, const uint8_t *msg, - uint8_t *signature); +typedef int sign_func (const uint8_t *pub, const uint8_t *priv, + size_t length, const uint8_t *msg, + uint8_t *signature); typedef int verify_func (const uint8_t *pub, size_t length, const uint8_t *msg, const uint8_t *signature); struct slh_dsa_alg { const char *name; - size_t key_size; size_t signature_size; sign_func *sign; verify_func *verify; @@ -273,7 +272,6 @@ struct slh_dsa_alg static const struct slh_dsa_alg slh_dsa_shake_128s = { "slh_dsa_shake_128s", - SLH_DSA_128_KEY_SIZE, SLH_DSA_128S_SIGNATURE_SIZE, slh_dsa_shake_128s_sign, slh_dsa_shake_128s_verify, @@ -282,7 +280,6 @@ slh_dsa_shake_128s = { static const struct slh_dsa_alg slh_dsa_shake_128f = { "slh_dsa_shake_128f", - SLH_DSA_128_KEY_SIZE, SLH_DSA_128F_SIGNATURE_SIZE, slh_dsa_shake_128f_sign, slh_dsa_shake_128f_verify, @@ -291,7 +288,6 @@ slh_dsa_shake_128f = { static const struct slh_dsa_alg slh_dsa_sha2_128s = { "slh_dsa_sha2_128s", - SLH_DSA_128_KEY_SIZE, SLH_DSA_128S_SIGNATURE_SIZE, slh_dsa_sha2_128s_sign, slh_dsa_sha2_128s_verify, @@ -300,7 +296,6 @@ slh_dsa_sha2_128s = { static const struct slh_dsa_alg slh_dsa_sha2_128f = { "slh_dsa_sha2_128f", - SLH_DSA_128_KEY_SIZE, SLH_DSA_128F_SIGNATURE_SIZE, slh_dsa_sha2_128f_sign, slh_dsa_sha2_128f_verify, @@ -312,11 +307,13 @@ test_slh_dsa (const struct slh_dsa_alg *alg, const struct tstring *msg, const struct tstring *ref) { uint8_t *sig = xalloc (alg->signature_size); - ASSERT (pub->length == alg->key_size); - ASSERT (priv->length == alg->key_size); + uint8_t bad_key[SLH_DSA_128_KEY_SIZE]; + + ASSERT (pub->length == SLH_DSA_128_KEY_SIZE); + ASSERT (priv->length == SLH_DSA_128_KEY_SIZE); ASSERT (ref->length == alg->signature_size); - alg->sign (pub->data, priv->data, msg->length, msg->data, sig); + ASSERT (alg->sign (pub->data, priv->data, msg->length, msg->data, sig)); if (! MEMEQ (alg->signature_size, sig, ref->data)) { size_t i; @@ -334,6 +331,16 @@ test_slh_dsa (const struct slh_dsa_alg *alg, sig[alg->signature_size-1] ^= 1; ASSERT (!alg->verify (pub->data, msg->length, msg->data, sig)); + memcpy (bad_key, pub->data, sizeof (bad_key)); + bad_key[5] ^= 1; /* Modifies public seed */ + ASSERT (!alg->sign (bad_key, priv->data, msg->length, msg->data, sig)); + ASSERT (sig[alg->signature_size - 1] == 0); + + memcpy (bad_key, priv->data, sizeof (bad_key)); + bad_key[5] ^= 1; /* Modifies private seed */ + ASSERT (!alg->sign (pub->data, bad_key, msg->length, msg->data, sig)); + ASSERT (sig[alg->signature_size - 1] == 0); + free (sig); }