diff --git a/mldsa/src/ct.h b/mldsa/src/ct.h index 1975ebf82..b4912933b 100644 --- a/mldsa/src/ct.h +++ b/mldsa/src/ct.h @@ -190,6 +190,37 @@ static MLD_ALWAYS_INLINE uint32_t mld_cast_int32_to_uint32(int32_t x) return mld_cast_int64_to_uint32((int64_t)x); } +/************************************************* + * Name: mld_ct_cmask_nonzero_u32 + * + * Description: Return 0 if input is zero, and -1 otherwise. + * + * Arguments: uint32_t x: Value to be converted into a mask + * + **************************************************/ +static MLD_INLINE uint32_t mld_ct_cmask_nonzero_u32(uint32_t x) +__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFFFFFFFF))) +{ + int64_t tmp = mld_value_barrier_i64(-((int64_t)x)); + tmp >>= 32; + return mld_cast_int64_to_uint32(tmp); +} + +/************************************************* + * Name: mld_ct_cmask_nonzero_u8 + * + * Description: Return 0 if input is zero, and -1 otherwise. + * + * Arguments: uint8_t x: Value to be converted into a mask + * + **************************************************/ +static MLD_INLINE uint8_t mld_ct_cmask_nonzero_u8(uint8_t x) +__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFF))) +{ + uint32_t mask = mld_ct_cmask_nonzero_u32((uint32_t)x); + return (uint8_t)(mask & 0xFF); +} + /************************************************* * Name: mld_ct_sel_int32 * @@ -205,16 +236,33 @@ static MLD_ALWAYS_INLINE uint32_t mld_cast_int32_to_uint32(int32_t x) **************************************************/ static MLD_INLINE int32_t mld_ct_sel_int32(int32_t a, int32_t b, uint32_t cond) __contract__( - requires(cond == 0x0 || cond == 0xFFFFFFFF) ensures(return_value == (cond ? a : b)) ) { uint32_t au = mld_cast_int32_to_uint32(a); uint32_t bu = mld_cast_int32_to_uint32(b); - uint32_t res = bu ^ (mld_value_barrier_u32(cond) & (au ^ bu)); + uint32_t res = bu ^ (mld_ct_cmask_nonzero_u32(cond) & (au ^ bu)); return mld_cast_uint32_to_int32(res); } +/************************************************* + * Name: mld_ct_sel_uint8 + * + * Description: Functionally equivalent to cond ? a : b, + * but implemented with guards against + * compiler-introduced branches. + * + * Arguments: uint8_t a: First alternative + * uint8_t b: Second alternative + * uuint8_t cond: Condition variable. + * + **************************************************/ +static MLD_INLINE uint8_t mld_ct_sel_uint8(uint8_t a, uint8_t b, uint8_t cond) +__contract__(ensures(return_value == (cond ? a : b))) +{ + return b ^ (mld_ct_cmask_nonzero_u8(cond) & (a ^ b)); +} + /************************************************* * Name: mld_ct_cmask_neg_i32 * @@ -295,6 +343,36 @@ __contract__( return (uint8_t)((mld_value_barrier_u32((uint32_t)r) ^ s) ^ s); } +/************************************************* + * Name: mld_ct_cmov_zero + * + * Description: Copy len bytes from x to r if b is zero; + * don't modify x if b is non-zero. + * assumes two's complement representation of negative integers. + * Runs in constant time. + * + * Arguments: uint8_t *r: pointer to output byte array + * const uint8_t *x: pointer to input byte array + * size_t len: Amount of bytes to be copied + * uint8_t b: Condition value. + * + **************************************************/ +static MLD_INLINE void mld_ct_cmov_zero(uint8_t *r, const uint8_t *x, + size_t len, uint8_t b) +__contract__( + requires(len <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(r, len)) + requires(memory_no_alias(x, len)) + assigns(memory_slice(r, len))) +{ + size_t i; + for (i = 0; i < len; i++) + __loop__(invariant(i <= len)) + { + r[i] = mld_ct_sel_uint8(r[i], x[i], b); + } +} + /************************************************* * Name: mld_zeroize * diff --git a/mldsa/src/sign.c b/mldsa/src/sign.c index 86e318fb6..d53a6a739 100644 --- a/mldsa/src/sign.c +++ b/mldsa/src/sign.c @@ -782,7 +782,6 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, const uint8_t pk[CRYPTO_PUBLICKEYBYTES], int externalmu) { - unsigned int i; int res; MLD_ALIGN uint8_t buf[MLDSA_K * MLDSA_POLYW1_PACKEDBYTES]; MLD_ALIGN uint8_t rho[MLDSA_SEEDBYTES]; @@ -853,28 +852,8 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, mld_H(c2, MLDSA_CTILDEBYTES, mu, MLDSA_CRHBYTES, buf, MLDSA_K * MLDSA_POLYW1_PACKEDBYTES, NULL, 0); - /* Constant time: All data in verification is usually considered public. - * However, in our constant-time tests we do not declassify the message and - * context string. - * The following conditional is the only place in verification whose run-time - * depends on the message. As all that can be leakaged here is the output of - * a hash call (that should behave like a random oracle), it is safe to - * declassify here even with a secret message. - */ - MLD_CT_TESTING_DECLASSIFY(c2, sizeof(c2)); - for (i = 0; i < MLDSA_CTILDEBYTES; ++i) - __loop__( - invariant(i <= MLDSA_CTILDEBYTES) - ) - { - if (c[i] != c2[i]) - { - res = -1; - goto cleanup; - } - } - - res = 0; + /* Return 0 if c == c2, -1 otherwise */ + res = mld_ct_sel_int32(-1, 0, mld_ct_memcmp(c, c2, MLDSA_CTILDEBYTES)); cleanup: /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ @@ -938,38 +917,31 @@ int crypto_sign_open(uint8_t *m, size_t *mlen, const uint8_t *sm, size_t smlen, const uint8_t pk[CRYPTO_PUBLICKEYBYTES]) { size_t i; + int result; + size_t mlen2; if (smlen < CRYPTO_BYTES) { - goto badsig; + *mlen = 0; + mld_memset(m, 0, smlen); + return -1; } - *mlen = smlen - CRYPTO_BYTES; - if (crypto_sign_verify(sm, CRYPTO_BYTES, sm + CRYPTO_BYTES, *mlen, ctx, - ctxlen, pk)) - { - goto badsig; - } - else + mlen2 = smlen - CRYPTO_BYTES; + *mlen = 0; + result = crypto_sign_verify(sm, CRYPTO_BYTES, sm + CRYPTO_BYTES, mlen2, ctx, + ctxlen, pk); + for (i = 0; i < mlen2; ++i) + __loop__( + assigns(i, memory_slice(m, mlen2)) + invariant(i <= mlen2) + ) { - /* All good, copy msg, return 0 */ - for (i = 0; i < *mlen; ++i) - __loop__( - assigns(i, memory_slice(m, *mlen)) - invariant(i <= *mlen) - ) - { - m[i] = sm[CRYPTO_BYTES + i]; - } - return 0; + m[i] = mld_ct_sel_uint8(0, sm[CRYPTO_BYTES + i], (uint8_t)result); } - -badsig: - /* Signature verification failed */ - *mlen = 0; - mld_memset(m, 0, smlen); - - return -1; + mld_ct_cmov_zero((uint8_t *)mlen, (uint8_t *)&mlen2, sizeof(size_t), + (uint8_t)result); + return result; } diff --git a/proofs/cbmc/crypto_sign_verify_internal/Makefile b/proofs/cbmc/crypto_sign_verify_internal/Makefile index 38e469764..c3e7ea201 100644 --- a/proofs/cbmc/crypto_sign_verify_internal/Makefile +++ b/proofs/cbmc/crypto_sign_verify_internal/Makefile @@ -39,6 +39,8 @@ USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_caddq USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_use_hint USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_pack_w1 USE_FUNCTION_CONTRACTS+=mld_zeroize +USE_FUNCTION_CONTRACTS+=mld_ct_memcmp +USE_FUNCTION_CONTRACTS+=mld_ct_sel_int32 APPLY_LOOP_CONTRACTS=on USE_DYNAMIC_FRAMES=1 diff --git a/proofs/cbmc/ct_cmask_nonzero_u32/Makefile b/proofs/cbmc/ct_cmask_nonzero_u32/Makefile new file mode 100644 index 000000000..d93c9e9f6 --- /dev/null +++ b/proofs/cbmc/ct_cmask_nonzero_u32/Makefile @@ -0,0 +1,55 @@ +# Copyright (c) The mldsa-native project authors +# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + +include ../Makefile_params.common + +HARNESS_ENTRY = harness +HARNESS_FILE = ct_cmask_nonzero_u32_harness + +# This should be a unique identifier for this proof, and will appear on the +# Litani dashboard. It can be human-readable and contain spaces if you wish. +PROOF_UID = mld_ct_cmask_nonzero_u32 + +DEFINES += -DMLD_CONFIG_NO_ASM_VALUE_BARRIER +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROJECT_SOURCES += $(SRCDIR)/mldsa/src/ct.c + +CHECK_FUNCTION_CONTRACTS=mld_ct_cmask_nonzero_u32 +USE_FUNCTION_CONTRACTS=mld_value_barrier_i64 +APPLY_LOOP_CONTRACTS=on +USE_DYNAMIC_FRAMES=1 + +# Disable any setting of EXTERNAL_SAT_SOLVER, and choose SMT backend instead +EXTERNAL_SAT_SOLVER= +CBMCFLAGS=--smt2 + +FUNCTION_NAME = mld_ct_cmask_nonzero_u32 + +# If this proof is found to consume huge amounts of RAM, you can set the +# EXPENSIVE variable. With new enough versions of the proof tools, this will +# restrict the number of EXPENSIVE CBMC jobs running at once. See the +# documentation in Makefile.common under the "Job Pools" heading for details. +# EXPENSIVE = true + +# This function is large enough to need... +CBMC_OBJECT_BITS = 8 + +# If you require access to a file-local ("static") function or object to conduct +# your proof, set the following (and do not include the original source file +# ("mldsa/poly.c") in PROJECT_SOURCES). +# REWRITTEN_SOURCES = $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i +# include ../Makefile.common +# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_SOURCE = $(SRCDIR)/mldsa/src/poly.c +# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_FUNCTIONS = foo bar +# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_OBJECTS = baz +# Care is required with variables on the left-hand side: REWRITTEN_SOURCES must +# be set before including Makefile.common, but any use of variables on the +# left-hand side requires those variables to be defined. Hence, _SOURCE, +# _FUNCTIONS, _OBJECTS is set after including Makefile.common. + +include ../Makefile.common diff --git a/proofs/cbmc/ct_cmask_nonzero_u32/ct_cmask_nonzero_u32_harness.c b/proofs/cbmc/ct_cmask_nonzero_u32/ct_cmask_nonzero_u32_harness.c new file mode 100644 index 000000000..3a742de43 --- /dev/null +++ b/proofs/cbmc/ct_cmask_nonzero_u32/ct_cmask_nonzero_u32_harness.c @@ -0,0 +1,10 @@ +// Copyright (c) The mldsa-native project authors +// SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + +#include "ct.h" + +void harness(void) +{ + uint32_t cond; + mld_ct_cmask_nonzero_u32(cond); +} diff --git a/proofs/cbmc/ct_sel_int32/Makefile b/proofs/cbmc/ct_sel_int32/Makefile index 76d76b470..6dd4548e9 100644 --- a/proofs/cbmc/ct_sel_int32/Makefile +++ b/proofs/cbmc/ct_sel_int32/Makefile @@ -20,7 +20,7 @@ PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROJECT_SOURCES += $(SRCDIR)/mldsa/src/ct.c CHECK_FUNCTION_CONTRACTS=mld_ct_sel_int32 -USE_FUNCTION_CONTRACTS=mld_value_barrier_u32 +USE_FUNCTION_CONTRACTS=mld_ct_cmask_nonzero_u32 APPLY_LOOP_CONTRACTS=on USE_DYNAMIC_FRAMES=1