Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/sig_stfl/lms/external/common_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@
#define MIN_HSS_LEVELS 1 /* Minumum levels we allow */
#define MAX_HSS_LEVELS 8 /* Maximum levels we allow */

/* RFC 8554 u32 field width in serialized HSS/LMS structures */
#define HSS_U32_LEN 4

/*
* Incremental HSS signature prefix parsed by hss_validate_signature_init():
* u32(levels-1) || u32(q) || u32(lm_ots_type)
*/
#define HSS_SIG_LEVELS_M1 0
#define HSS_SIG_Q (HSS_SIG_LEVELS_M1 + HSS_U32_LEN)
#define HSS_SIG_LM_OTS (HSS_SIG_Q + HSS_U32_LEN)
#define HSS_SIG_INC_HEADER_LEN (HSS_SIG_LM_OTS + HSS_U32_LEN)

/* This is the length of our internal seed values */
#define SEED_LEN 32 /* Enough to make Grover's infeasible */

Expand Down
28 changes: 21 additions & 7 deletions src/sig_stfl/lms/external/hss_verify_inc.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
* Further, the signature LM-OTS types must those present in the public key.
*/
static bool is_hss_public_key(const unsigned char *public_key,
const unsigned char *signature) {
const unsigned char *signature,
size_t signature_len) {
if (signature_len < HSS_SIG_INC_HEADER_LEN) {
return false;
}
uint_fast32_t sig_levels =
(uint_fast32_t)get_bigendian(signature, 4) + 1;
uint_fast32_t sig_lm_ots = (uint_fast32_t)get_bigendian(signature + 8, 4);
Expand All @@ -61,12 +65,13 @@ static bool is_hss_public_key(const unsigned char *public_key,
* Validate by matching the fields in the signature buffer.
*/
static uint_fast32_t public_key_levels(const unsigned char *public_key,
const unsigned char *signature) {
const unsigned char *signature,
size_t signature_len) {
param_set_t w0 = (param_set_t)get_bigendian(public_key, 4);
param_set_t w1 = (param_set_t)get_bigendian(public_key + 4, 4);
param_set_t w2 = (param_set_t)get_bigendian(public_key + 8, 4);

if (is_hss_public_key(public_key, signature)) {
if (is_hss_public_key(public_key, signature, signature_len)) {
uint_fast32_t levels = (uint_fast32_t)w0;
if (levels < MIN_HSS_LEVELS || levels > MAX_HSS_LEVELS) {
return 0;
Expand Down Expand Up @@ -109,15 +114,18 @@ bool hss_validate_signature_init(

const unsigned char *orig_signature = signature;

/* Get the number of levels the signature claims */
if (signature_len < 4) {
/* Get the number of levels the signature claims. The header parsed here
* is u32(levels-1) || u32(q) || u32(lm_ots), and is_hss_public_key()
* below reads the LM-OTS type at offset 8, so HSS_SIG_INC_HEADER_LEN
* bytes must be present */
if (signature_len < HSS_SIG_INC_HEADER_LEN) {
ctx->status = info->error_code = hss_error_bad_signature;
return false;
}
uint_fast32_t levels = (uint_fast32_t)get_bigendian( signature, 4 ) + 1;
/* +1 because what's in the signature is levels-1 */
uint_fast32_t pub_levels = public_key_levels(public_key, orig_signature);
if (is_hss_public_key(public_key, orig_signature)) {
uint_fast32_t pub_levels = public_key_levels(public_key, orig_signature, signature_len);
if (is_hss_public_key(public_key, orig_signature, signature_len)) {
public_key += 4;
}
signature += 4; signature_len -= 4;
Expand Down Expand Up @@ -205,6 +213,12 @@ bool hss_validate_signature_init(
ctx->status = info->error_code = hss_error_bad_signature;
return false;
}
/* The bottom level signature starts with u32(q), and the LM-OTS
* randomizer C sits at offset 8 and is n bytes wide; both are read below */
if (signature_len < 8 + (size_t)n) {
ctx->status = info->error_code = hss_error_bad_signature;
return false;
}
ctx->h = h;
hss_init_hash_context( h, &ctx->hash_ctx );
{
Expand Down
88 changes: 88 additions & 0 deletions tests/test_sig_stfl.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,23 @@ static char *convert_method_name_to_file_name(const char *method_name) {
#define TEST_XMSS_OID_SHA2_10_256 0x01U
#endif

#ifdef OQS_ENABLE_SIG_STFL_LMS
/* test_invalid_sig_lms: HSS pk layout (RFC 8554): u32(levels) || u32(lm_type) || u32(lm_ots) || I[16] || T[32]. */
#define TEST_INVALID_SIG_LMS_PK_LEN 60
/* Signature header: u32(levels-1) || u32(q) || u32(lm_ots). Real LMS signatures are kilobytes. */
#define TEST_INVALID_SIG_LMS_HEADER_LEN 12
/* The bottom level parse needs u32(q) and the n-byte LM-OTS randomizer C, i.e. 8 + n bytes
* beyond the four the header consumes: 44 for the SHA-256/n=32 parameter sets liboqs supports.
* This is the shortest signature that clears every length bound in the parser. */
#define TEST_INVALID_SIG_LMS_MIN_BOUNDED_LEN 44
/* Offsets of the type fields within the pk and the signature header. */
#define TEST_LMS_LEVELS_OFFSET 3
#define TEST_LMS_TYPE_OFFSET 7
#define TEST_LMS_OTS_TYPE_OFFSET 11
#define TEST_LMS_TYPE_SHA256_H5 5U
#define TEST_LMOTS_TYPE_SHA256_N32_W2 2U
#endif

/*
* This function is used to test the invalid signature verification.
* @param method_name: The name of the signature algorithm to test.
Expand Down Expand Up @@ -465,6 +482,73 @@ static OQS_STATUS test_invalid_sig(const char *method_name) {
#endif
}

/*
* This function tests verification of LMS signatures that are shorter than the
* header the parser reads. The signature buffers are allocated at exactly the
* length passed to verify so that a sanitizer build observes any over-read.
* @param method_name: The name of the signature algorithm to test.
* @return OQS_SUCCESS if every truncated signature is rejected, OQS_ERROR otherwise.
*/
static OQS_STATUS test_invalid_sig_lms(const char *method_name) {
if (method_name == NULL) {
return OQS_ERROR;
}
#ifndef OQS_ENABLE_SIG_STFL_LMS
(void)method_name;
return OQS_SUCCESS;
#else
OQS_SIG_STFL *sig = OQS_SIG_STFL_new(method_name);
if (sig == NULL) {
return OQS_ERROR;
}

/* hss_validate_signature_init derives every parameter from the pk and
* signature bytes, so one well-formed single-level pk covers all variants. */
uint8_t pk[TEST_INVALID_SIG_LMS_PK_LEN] = {0};
pk[TEST_LMS_LEVELS_OFFSET] = 1;
pk[TEST_LMS_TYPE_OFFSET] = TEST_LMS_TYPE_SHA256_H5;
pk[TEST_LMS_OTS_TYPE_OFFSET] = TEST_LMOTS_TYPE_SHA256_N32_W2;

uint8_t message[] = "test";
/* Every length here must be rejected without reading past the buffer:
* 4, 11 shorter than the 12-byte header, which is_hss_public_key()
* reads to offset 8 for the LM-OTS type;
* 12, 39, 40, 43 header present, but fewer than 8 + n bytes remain for q and
* the LM-OTS randomizer C that the bottom level copies -- 43
* is the largest such length;
* 44 clears both of those bounds, so it reaches
* lm_validate_signature() and must be rejected on length
* there, still without an over-read. */
const size_t trunc_lens[] = {
4, 11,
TEST_INVALID_SIG_LMS_HEADER_LEN, 39, 40, 43,
TEST_INVALID_SIG_LMS_MIN_BOUNDED_LEN
};

for (size_t i = 0; i < sizeof(trunc_lens) / sizeof(trunc_lens[0]); i++) {
uint8_t *malicious_sig = OQS_MEM_malloc(trunc_lens[i]);
if (malicious_sig == NULL) {
OQS_SIG_STFL_free(sig);
return OQS_ERROR;
}
memset(malicious_sig, 0, trunc_lens[i]);
if (trunc_lens[i] > TEST_LMS_OTS_TYPE_OFFSET) {
malicious_sig[TEST_LMS_OTS_TYPE_OFFSET] = TEST_LMOTS_TYPE_SHA256_N32_W2;
}

OQS_STATUS status = OQS_SIG_STFL_verify(sig, message, sizeof(message) - 1, malicious_sig, trunc_lens[i], pk);
OQS_MEM_insecure_free(malicious_sig);
if (status == OQS_SUCCESS) {
OQS_SIG_STFL_free(sig);
return OQS_ERROR;
}
}

OQS_SIG_STFL_free(sig);
return OQS_SUCCESS;
#endif
}

static OQS_STATUS sig_stfl_test_correctness(const char *method_name, const char *katfile, bool bitflips_all[2], size_t bitflips[2]) {

OQS_SIG_STFL *sig = NULL;
Expand Down Expand Up @@ -1097,6 +1181,8 @@ void *test_correctness_wrapper(void *arg) {
td->rc = sig_stfl_test_correctness(td->alg_name, td->katfile, td->bitflips_all, td->bitflips);
if (strstr(td->alg_name, "XMSS") != NULL) {
td->rc2 = test_invalid_sig(td->alg_name);
} else if (strstr(td->alg_name, "LMS") != NULL) {
td->rc2 = test_invalid_sig_lms(td->alg_name);
}
OQS_thread_stop();
return NULL;
Expand Down Expand Up @@ -1369,6 +1455,8 @@ int main(int argc, char **argv) {
rc1 = sig_stfl_test_secret_key(alg_name, katfile);
if (is_xmss) {
rc2 = test_invalid_sig(alg_name);
} else if (strstr(alg_name, "LMS") != NULL) {
rc2 = test_invalid_sig_lms(alg_name);
}

OQS_destroy();
Expand Down
Loading