Add extra LMS and LMOTS tests

NULL-message and LMOTS signature leak tests

Signed-off-by: Raef Coles <raef.coles@arm.com>
diff --git a/library/lmots.c b/library/lmots.c
index 8e56696..d733dc0 100644
--- a/library/lmots.c
+++ b/library/lmots.c
@@ -44,11 +44,6 @@
 
 #include "psa/crypto.h"
 
-#define MBEDTLS_LMOTS_SIG_C_RANDOM_OFFSET (MBEDTLS_LMOTS_SIG_TYPE_OFFSET + \
-                                           MBEDTLS_LMOTS_TYPE_LEN)
-#define MBEDTLS_LMOTS_SIG_SIGNATURE_OFFSET(type) (MBEDTLS_LMOTS_SIG_C_RANDOM_OFFSET + \
-                                                  MBEDTLS_LMOTS_C_RANDOM_VALUE_LEN(type))
-
 #define MBEDTLS_LMOTS_PUBLIC_KEY_TYPE_OFFSET     (0)
 #define MBEDTLS_LMOTS_PUBLIC_KEY_I_KEY_ID_OFFSET (MBEDTLS_LMOTS_PUBLIC_KEY_TYPE_OFFSET + \
                                                   MBEDTLS_LMOTS_TYPE_LEN)
@@ -74,6 +69,10 @@
 static const unsigned char D_PUBLIC_CONSTANT_BYTES[D_CONST_LEN] = {0x80, 0x80};
 static const unsigned char D_MESSAGE_CONSTANT_BYTES[D_CONST_LEN] = {0x81, 0x81};
 
+#if defined(MBEDTLS_TEST_HOOKS)
+int( *mbedtls_lmots_sign_private_key_invalidated_hook )( unsigned char * ) = NULL;
+#endif /* defined(MBEDTLS_TEST_HOOKS) */
+
 void unsigned_int_to_network_bytes( unsigned int val, size_t len,
                                     unsigned char *bytes )
 {
@@ -815,6 +814,18 @@
                                    MBEDTLS_LMOTS_TYPE_LEN,
                                    sig + MBEDTLS_LMOTS_SIG_TYPE_OFFSET );
 
+    /* Test hook to check if sig is being written to before we invalidate the
+     * private key.
+     */
+#if defined(MBEDTLS_TEST_HOOKS)
+    if( mbedtls_lmots_sign_private_key_invalidated_hook != NULL )
+    {
+        ret = ( *mbedtls_lmots_sign_private_key_invalidated_hook )( sig );
+        if( ret != 0 )
+            return( ret );
+    }
+#endif /* defined(MBEDTLS_TEST_HOOKS) */
+
     /* We've got a valid signature now, so it's time to make sure the private
      * key can't be reused.
      */
diff --git a/library/lmots.h b/library/lmots.h
index 56d23f8..993c01c 100644
--- a/library/lmots.h
+++ b/library/lmots.h
@@ -54,6 +54,10 @@
                                             MBEDTLS_LMOTS_N_HASH_LEN(type))
 
 #define MBEDTLS_LMOTS_SIG_TYPE_OFFSET       (0)
+#define MBEDTLS_LMOTS_SIG_C_RANDOM_OFFSET (MBEDTLS_LMOTS_SIG_TYPE_OFFSET + \
+                                           MBEDTLS_LMOTS_TYPE_LEN)
+#define MBEDTLS_LMOTS_SIG_SIGNATURE_OFFSET(type) (MBEDTLS_LMOTS_SIG_C_RANDOM_OFFSET + \
+                                                  MBEDTLS_LMOTS_C_RANDOM_VALUE_LEN(type))
 
 #ifdef __cplusplus
 extern "C" {
diff --git a/tests/suites/test_suite_lmots.data b/tests/suites/test_suite_lmots.data
index 6b4cc35..0845a17 100644
--- a/tests/suites/test_suite_lmots.data
+++ b/tests/suites/test_suite_lmots.data
@@ -8,6 +8,11 @@
 # message, and verifies the signature.
 lmots_sign_verify_test:"55a6647a581004306792b653a561d9f3":"00000000000000000000000000000000":12:"c3dbc3fea047dca8fb7a3cdf609a5b7f48599c193c90e958ce9388c84df0a906"
 
+LMOTS NULL-message sign-verify test
+# This test uses a NULL zero-length message, and then generates a private key,
+# signs the message, and verifies the signature.
+lmots_sign_verify_null_msg_test::"00000000000000000000000000000000":12:"be5fa89144f2d665c66ead8216bc02006e0eccd8b3697a0aea44f6c93afe7955"
+
 LMOTS hash-sigs interop test #1
 # This test uses data from https://github.com/cisco/hash-sigs due to the limited
 # amount of available test vectors for LMOTS. A public/private key was generated
@@ -60,3 +65,10 @@
 # message, and then attempts to sign the message again. The second signature
 # must fail as private key material must be deleted after a key is used to sign.
 lmots_reuse_test:"cfcd1e81193e310c9d931d1b00818d14":"00000000000000000000000000000000":12:"a7f53cc5a228ce63811ba4d7c1f74f7fce62afbf6813f3ca3ae43c11b138086f"
+
+LMOTS signature leak test
+# This test uses a fixed message, and then generates a private key, signs the
+# message, and then uses a test hook to check that the signature has not been
+# modifier before the private key has been deleted (which could cause signature
+# leakage during errors).
+lmots_signature_leak_test:"cfcd1e81193e310c9d931d1b00818d14":"00000000000000000000000000000000":12:"a7f53cc5a228ce63811ba4d7c1f74f7fce62afbf6813f3ca3ae43c11b138086f"
diff --git a/tests/suites/test_suite_lmots.function b/tests/suites/test_suite_lmots.function
index 2a9ea54..d7067df 100644
--- a/tests/suites/test_suite_lmots.function
+++ b/tests/suites/test_suite_lmots.function
@@ -2,6 +2,27 @@
 #include "lmots.h"
 #include "mbedtls/lms.h"
 
+#if defined(MBEDTLS_TEST_HOOKS)
+extern int( *mbedtls_lmots_sign_private_key_invalidated_hook )( unsigned char * );
+
+int check_lmots_private_key_for_leak(unsigned char * sig)
+{
+    size_t idx;
+
+    for( idx = MBEDTLS_LMOTS_SIG_SIGNATURE_OFFSET(MBEDTLS_LMOTS_SHA256_N32_W8);
+         idx < MBEDTLS_LMOTS_SIG_LEN(MBEDTLS_LMOTS_SHA256_N32_W8);
+         idx++ )
+    {
+        if( sig[idx] != 0x7E ) {
+        while(1){}
+            return 1;
+        }
+    }
+
+    return 0;
+}
+#endif /* defined(MBEDTLS_TEST_HOOKS) */
+
 /* END_HEADER */
 
 /* BEGIN_DEPENDENCIES
@@ -34,6 +55,29 @@
 /* END_CASE */
 
 /* BEGIN_CASE */
+void lmots_sign_verify_null_msg_test ( data_t *key_id, int leaf_id, data_t *seed )
+{
+    mbedtls_lmots_public_t pub_ctx;
+    mbedtls_lmots_private_t priv_ctx;
+    unsigned char sig[MBEDTLS_LMOTS_SIG_LEN(MBEDTLS_LMOTS_SHA256_N32_W8)];
+
+    mbedtls_lmots_init_public( &pub_ctx );
+    mbedtls_lmots_init_private( &priv_ctx );
+
+    TEST_ASSERT( mbedtls_lmots_generate_private_key(&priv_ctx, MBEDTLS_LMOTS_SHA256_N32_W8,
+                 key_id->x, leaf_id, seed->x, seed->len ) == 0 );
+    TEST_ASSERT( mbedtls_lmots_calculate_public_key(&pub_ctx, &priv_ctx) == 0 );
+    TEST_ASSERT( mbedtls_lmots_sign(&priv_ctx, &mbedtls_test_rnd_std_rand, NULL,
+                 NULL, 0, sig, sizeof(sig), NULL ) == 0 );
+    TEST_ASSERT( mbedtls_lmots_verify(&pub_ctx, NULL, 0, sig, sizeof(sig)) == 0 );
+
+exit:
+    mbedtls_lmots_free_public( &pub_ctx );
+    mbedtls_lmots_free_private( &priv_ctx );
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
 void lmots_verify_test ( data_t *msg, data_t *sig, data_t *pub_key,
                           int expected_rc )
 {
@@ -94,3 +138,28 @@
     mbedtls_lmots_free_private( &ctx );
 }
 /* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_TEST_HOOKS */
+void lmots_signature_leak_test ( data_t *msg, data_t *key_id, int leaf_id,
+                                 data_t *seed )
+{
+    mbedtls_lmots_private_t ctx;
+    unsigned char sig[MBEDTLS_LMOTS_SIG_LEN(MBEDTLS_LMOTS_SHA256_N32_W8)];
+
+    mbedtls_lmots_sign_private_key_invalidated_hook = &check_lmots_private_key_for_leak;
+
+    /* Fill with recognisable pattern */
+    memset( sig, 0x7E, sizeof( sig ) );
+
+    mbedtls_lmots_init_private( &ctx );
+    TEST_ASSERT( mbedtls_lmots_generate_private_key(&ctx, MBEDTLS_LMOTS_SHA256_N32_W8,
+                                                    key_id->x, leaf_id, seed->x,
+                                                    seed->len ) == 0 );
+    TEST_ASSERT( mbedtls_lmots_sign(&ctx, mbedtls_test_rnd_std_rand, NULL,
+                                    msg->x, msg->len, sig, sizeof( sig ), NULL ) == 0 );
+
+exit:
+    mbedtls_lmots_free_private( &ctx );
+    mbedtls_lmots_sign_private_key_invalidated_hook = NULL;
+}
+/* END_CASE */
diff --git a/tests/suites/test_suite_lms.data b/tests/suites/test_suite_lms.data
index c17d05a..d890dd4 100644
--- a/tests/suites/test_suite_lms.data
+++ b/tests/suites/test_suite_lms.data
@@ -3,6 +3,11 @@
 # message, and verifies the signature.
 lms_sign_verify_test:"c41ba177a0ca1ec31dfb2e145237e65b":"626201f41afd7c9af793cf158da58e33"
 
+LMS NULL-message sign-verify test
+# This test uses a NULL zero-length message, and then generates a private key,
+# signs the message, and verifies the signature.
+lms_sign_verify_null_msg_test:"923a3c8e38c9b72e067996bfdaa36856"
+
 LMS hash-sigs interop test #1
 # This test uses data from https://github.com/cisco/hash-sigs due to the limited
 # amount of available test vectors for LMS. A public/private key was generated
diff --git a/tests/suites/test_suite_lms.function b/tests/suites/test_suite_lms.function
index 148075d..9003923 100644
--- a/tests/suites/test_suite_lms.function
+++ b/tests/suites/test_suite_lms.function
@@ -45,6 +45,42 @@
 /* END_CASE */
 
 /* BEGIN_CASE */
+void lms_sign_verify_null_msg_test( data_t *seed )
+{
+    mbedtls_lms_public_t pub_ctx;
+    mbedtls_lms_private_t priv_ctx;
+    unsigned char sig[MBEDTLS_LMS_SIG_LEN(MBEDTLS_LMS_SHA256_M32_H10, MBEDTLS_LMOTS_SHA256_N32_W8)];
+    int rc;
+
+    mbedtls_lms_init_public( &pub_ctx );
+    mbedtls_lms_init_private( &priv_ctx );
+
+    /* Allocation failure isn't a test failure, since it likely just means
+     * there's not enough memory to run the test.
+     */
+    rc = mbedtls_lms_generate_private_key( &priv_ctx, MBEDTLS_LMS_SHA256_M32_H10,
+                                           MBEDTLS_LMOTS_SHA256_N32_W8,
+                                           mbedtls_test_rnd_std_rand, NULL,
+                                           seed->x, seed->len );
+    TEST_ASSUME( rc != MBEDTLS_ERR_LMS_ALLOC_FAILED );
+    TEST_ASSERT( rc == 0 );
+
+    TEST_ASSERT( mbedtls_lms_calculate_public_key( &pub_ctx, &priv_ctx ) == 0 );
+
+    TEST_ASSERT( mbedtls_lms_sign( &priv_ctx, mbedtls_test_rnd_std_rand, NULL,
+                                   NULL, 0, sig, sizeof( sig ),
+                                   NULL ) == 0 );
+
+    TEST_ASSERT( mbedtls_lms_verify( &pub_ctx, NULL, 0, sig,
+                                     sizeof( sig ) ) == 0 );
+
+exit:
+    mbedtls_lms_free_public( &pub_ctx );
+    mbedtls_lms_free_private( &priv_ctx );
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
 void lms_verify_test ( data_t * msg, data_t * sig, data_t * pub_key,
                           int expected_rc )
 {