diff --git a/configure.ac b/configure.ac index 152da7ed9a..ef57fca8d8 100644 --- a/configure.ac +++ b/configure.ac @@ -4994,12 +4994,12 @@ AS_CASE([$FIPS_VERSION], [AM_CFLAGS="$AM_CFLAGS -DECC_SHAMIR"])]) AS_IF([test "x$ENABLED_ED25519" != "xyes"], - [ENABLED_ED25519="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_ED25519"]) + [ENABLED_ED25519="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_ED25519 -DHAVE_ED25519_KEY_IMPORT"]) AS_IF([test "x$ENABLED_CURVE25519" != "xyes"], [ENABLED_CURVE25519="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_CURVE25519"]) AS_IF([test "x$ENABLED_ED448" != "xyes"], - [ENABLED_ED448="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_ED448"]) + [ENABLED_ED448="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_ED448 -DHAVE_ED448_KEY_IMPORT"]) AS_IF([test "x$ENABLED_CURVE448" != "xyes"], [ENABLED_CURVE448="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_CURVE448"]) diff --git a/wolfcrypt/src/ed25519.c b/wolfcrypt/src/ed25519.c index 3dd1d8c4a1..fe005f580a 100644 --- a/wolfcrypt/src/ed25519.c +++ b/wolfcrypt/src/ed25519.c @@ -208,6 +208,56 @@ static int ed25519_hash(ed25519_key* key, const byte* in, word32 inLen, } #ifdef HAVE_ED25519_MAKE_KEY +#if FIPS_VERSION3_GE(6,0,0) +/* Performs a Pairwise Consistency Test on an Ed25519 key pair. + * + * @param [in] key Ed25519 key to test. + * @param [in] rng Random number generator to use to create random digest. + * @return 0 on success. + * @return ECC_PCT_E when signing or verification fail. + * @return Other -ve when random number generation fails. + */ +static int ed25519_pairwise_consistency_test(ed25519_key* key, WC_RNG* rng) +{ + int err = 0; + byte digest[WC_SHA512_DIGEST_SIZE]; + word32 digestLen = WC_SHA512_DIGEST_SIZE; + byte sig[ED25519_SIG_SIZE]; + word32 sigLen = ED25519_SIG_SIZE; + int res = 0; + + /* Generate a random digest to sign. */ + err = wc_RNG_GenerateBlock(rng, digest, digestLen); + if (err == 0) { + /* Sign digest without context. */ + err = wc_ed25519_sign_msg_ex(digest, digestLen, sig, &sigLen, key, + Ed25519, NULL, 0); + if (err != 0) { + /* Any sign failure means test failed. */ + err = ECC_PCT_E; + } + } + if (err == 0) { + /* Verify digest without context. */ + err = wc_ed25519_verify_msg_ex(sig, sigLen, digest, digestLen, &res, + key, Ed25519, NULL, 0); + if (err != 0) { + /* Any verification operation failure means test failed. */ + err = ECC_PCT_E; + } + /* Check whether the signature verified. */ + else if (res == 0) { + /* Test failed. */ + err = ECC_PCT_E; + } + } + + ForceZero(sig, sigLen); + + return err; +} +#endif + int wc_ed25519_make_public(ed25519_key* key, unsigned char* pubKey, word32 pubKeySz) { @@ -291,6 +341,13 @@ int wc_ed25519_make_key(WC_RNG* rng, int keySz, ed25519_key* key) /* put public key after private key, on the same buffer */ XMEMMOVE(key->k + ED25519_KEY_SIZE, key->p, ED25519_PUB_KEY_SIZE); +#if FIPS_VERSION3_GE(6,0,0) + ret = wc_ed25519_check_key(key); + if (ret == 0) { + ret = ed25519_pairwise_consistency_test(key, rng); + } +#endif + return ret; } #endif /* HAVE_ED25519_MAKE_KEY */ @@ -1077,7 +1134,7 @@ int wc_ed25519_import_public_ex(const byte* in, word32 inLen, ed25519_key* key, if (ret == 0) { key->pubKeySet = 1; - if (key->privKeySet && (!trusted)) { + if (!trusted) { ret = wc_ed25519_check_key(key); } } @@ -1278,23 +1335,84 @@ int wc_ed25519_export_key(ed25519_key* key, #endif /* HAVE_ED25519_KEY_EXPORT */ -/* check the private and public keys match */ +/* Check the public key is valid. + * + * When private key available, check the calculated public key matches. + * When no private key, check Y is in range and an X is able to be calculated. + * + * @param [in] key Ed25519 private/public key. + * @return 0 otherwise. + * @return BAD_FUNC_ARG when key is NULL. + * @return PUBLIC_KEY_E when the public key is not set, doesn't match or is + * invalid. + * @return other -ve value on hash failure. + */ int wc_ed25519_check_key(ed25519_key* key) { int ret = 0; -#ifdef HAVE_ED25519_MAKE_KEY - ALIGN16 unsigned char pubKey[ED25519_PUB_KEY_SIZE]; - if (!key->pubKeySet) + /* Validate parameter. */ + if (key == NULL) { + ret = BAD_FUNC_ARG; + } + + /* Check we have a public key to check. */ + if ((ret == 0) && (!key->pubKeySet)) { ret = PUBLIC_KEY_E; - if (ret == 0) + } + +#ifdef HAVE_ED25519_MAKE_KEY + /* If we have a private key just make the public key and compare. */ + if ((ret == 0) && (key->privKeySet)) { + ALIGN16 unsigned char pubKey[ED25519_PUB_KEY_SIZE]; + ret = wc_ed25519_make_public(key, pubKey, sizeof(pubKey)); - if (ret == 0 && XMEMCMP(pubKey, key->p, ED25519_PUB_KEY_SIZE) != 0) - ret = PUBLIC_KEY_E; + if (ret == 0 && XMEMCMP(pubKey, key->p, ED25519_PUB_KEY_SIZE) != 0) + ret = PUBLIC_KEY_E; + } #else - (void)key; + (void)key; #endif /* HAVE_ED25519_MAKE_KEY */ + /* No private key (or ability to make a public key), check Y is valid. */ + if ((ret == 0) +#ifdef HAVE_ED25519_MAKE_KEY + && (!key->privKeySet) +#endif + ) { + /* Verify that Q is not identity element 0. + * 0 has no representation for Ed25519. */ + + /* Verify that xQ and yQ are integers in the interval [0, p - 1]. + * Only have yQ so check that ordinate. p = 2^255 - 19 */ + if ((key->p[ED25519_PUB_KEY_SIZE - 1] & 0x7f) == 0x7f) { + int i; + + ret = PUBLIC_KEY_E; + /* Check up to last byte. */ + for (i = ED25519_PUB_KEY_SIZE - 2; i > 0; i--) { + if (key->p[i] != 0xff) { + ret = 0; + break; + } + } + /* Bits are all one up to last byte - check less than -19. */ + if ((ret == PUBLIC_KEY_E) && (key->p[0] < 0xed)) { + ret = 0; + } + } + + if (ret == 0) { + /* Verify that Q is on the curve. + * Uncompressing the public key will validate yQ. */ + ge_p3 A; + + if (ge_frombytes_negate_vartime(&A, key->p) != 0) { + ret = PUBLIC_KEY_E; + } + } + } + return ret; } diff --git a/wolfcrypt/src/ed448.c b/wolfcrypt/src/ed448.c index 727ca810ea..de57ba0393 100644 --- a/wolfcrypt/src/ed448.c +++ b/wolfcrypt/src/ed448.c @@ -187,6 +187,56 @@ static int ed448_hash(ed448_key* key, const byte* in, word32 inLen, return ret; } +#if FIPS_VERSION3_GE(6,0,0) +/* Performs a Pairwise Consistency Test on an Ed448 key pair. + * + * @param [in] key Ed448 key to test. + * @param [in] rng Random number generator to use to create random digest. + * @return 0 on success. + * @return ECC_PCT_E when signing or verification fail. + * @return Other -ve when random number generation fails. + */ +static int ed448_pairwise_consistency_test(ed448_key* key, WC_RNG* rng) +{ + int err = 0; + byte digest[WC_SHA256_DIGEST_SIZE]; + word32 digestLen = WC_SHA256_DIGEST_SIZE; + byte sig[ED448_SIG_SIZE]; + word32 sigLen = ED448_SIG_SIZE; + int res = 0; + + /* Generate a random digest to sign. */ + err = wc_RNG_GenerateBlock(rng, digest, digestLen); + if (err == 0) { + /* Sign digest without context. */ + err = wc_ed448_sign_msg_ex(digest, digestLen, sig, &sigLen, key, Ed448, + NULL, 0); + if (err != 0) { + /* Any sign failure means test failed. */ + err = ECC_PCT_E; + } + } + if (err == 0) { + /* Verify digest without context. */ + err = wc_ed448_verify_msg_ex(sig, sigLen, digest, digestLen, &res, key, + Ed448, NULL, 0); + if (err != 0) { + /* Any verification operation failure means test failed. */ + err = ECC_PCT_E; + } + /* Check whether the signature verified. */ + else if (res == 0) { + /* Test failed. */ + err = ECC_PCT_E; + } + } + + ForceZero(sig, sigLen); + + return err; +} +#endif + /* Derive the public key for the private key. * * key [in] Ed448 key object. @@ -272,6 +322,13 @@ int wc_ed448_make_key(WC_RNG* rng, int keySz, ed448_key* key) if (ret == 0) { /* put public key after private key, on the same buffer */ XMEMMOVE(key->k + ED448_KEY_SIZE, key->p, ED448_PUB_KEY_SIZE); + + #if FIPS_VERSION3_GE(6,0,0) + ret = wc_ed448_check_key(key); + if (ret == 0) { + ret = ed448_pairwise_consistency_test(key, rng); + } + #endif } return ret; @@ -966,7 +1023,7 @@ int wc_ed448_import_public_ex(const byte* in, word32 inLen, ed448_key* key, ret = BAD_FUNC_ARG; } - if (inLen != ED448_PUB_KEY_SIZE) { + if ((inLen != ED448_PUB_KEY_SIZE) && (inLen != ED448_PUB_KEY_SIZE + 1)) { ret = BAD_FUNC_ARG; } @@ -995,7 +1052,7 @@ int wc_ed448_import_public_ex(const byte* in, word32 inLen, ed448_key* key, if (ret == 0) { key->pubKeySet = 1; - if (key->privKeySet && (!trusted)) { + if (!trusted) { /* Check untrusted public key data matches private key. */ ret = wc_ed448_check_key(key); } @@ -1243,31 +1300,90 @@ int wc_ed448_export_key(ed448_key* key, byte* priv, word32 *privSz, #endif /* HAVE_ED448_KEY_EXPORT */ -/* Check the public key of the ed448 key matches the private key. +/* Check the public key is valid. * - * key [in] Ed448 private/public key. - * returns BAD_FUNC_ARG when key is NULL, - * PUBLIC_KEY_E when the public key is not set or doesn't match, - * other -ve value on hash failure, - * 0 otherwise. + * When private key available, check the calculated public key matches. + * When no private key, check Y is in range and an X is able to be calculated. + * + * @param [in] key Ed448 private/public key. + * @return 0 otherwise. + * @return BAD_FUNC_ARG when key is NULL. + * @return PUBLIC_KEY_E when the public key is not set, doesn't match or is + * invalid. + * @return other -ve value on hash failure. */ int wc_ed448_check_key(ed448_key* key) { int ret = 0; unsigned char pubKey[ED448_PUB_KEY_SIZE]; + /* Validate parameter. */ if (key == NULL) { ret = BAD_FUNC_ARG; } + /* Check we have a public key to check. */ if (ret == 0 && !key->pubKeySet) { ret = PUBLIC_KEY_E; } - if (ret == 0) { + + /* If we have a private key just make the public key and compare. */ + if ((ret == 0) && key->privKeySet) { ret = wc_ed448_make_public(key, pubKey, sizeof(pubKey)); + if ((ret == 0) && (XMEMCMP(pubKey, key->p, ED448_PUB_KEY_SIZE) != 0)) { + ret = PUBLIC_KEY_E; + } } - if ((ret == 0) && (XMEMCMP(pubKey, key->p, ED448_PUB_KEY_SIZE) != 0)) { - ret = PUBLIC_KEY_E; + /* No private key, check Y is valid. */ + else if ((ret == 0) && (!key->privKeySet)) { + /* Verify that Q is not identity element 0. + * 0 has no representation for Ed448. */ + + /* Verify that xQ and yQ are integers in the interval [0, p - 1]. + * Only have yQ so check that ordinate. + * p = 2^448-2^224-1 = 0xff..fe..ff + */ + { + int i; + ret = PUBLIC_KEY_E; + + /* Check top part before 0xFE. */ + for (i = ED448_PUB_KEY_SIZE - 1; i > ED448_PUB_KEY_SIZE/2; i--) { + if (key->p[i] < 0xff) { + ret = 0; + break; + } + } + if (ret == PUBLIC_KEY_E) { + /* Check against 0xFE. */ + if (key->p[ED448_PUB_KEY_SIZE/2] < 0xfe) { + ret = 0; + } + else if (key->p[ED448_PUB_KEY_SIZE/2] == 0xfe) { + /* Check bottom part before last byte. */ + for (i = ED448_PUB_KEY_SIZE/2 - 1; i > 0; i--) { + if (key->p[i] != 0xff) { + ret = 0; + break; + } + } + /* Check last byte. */ + if ((ret == PUBLIC_KEY_E) && (key->p[0] < 0xff)) { + ret = 0; + } + } + } + } + + if (ret == 0) { + /* Verify that Q is on the curve. + * Uncompressing the public key will validate yQ. */ + ge448_p2 A; + + if (ge448_from_bytes_negate_vartime(&A, key->p) != 0) { + ret = PUBLIC_KEY_E; + } + } } return ret; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 9eb3021bfe..50dd5ae02c 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -32907,6 +32907,88 @@ static wc_test_ret_t ed25519_test_make_cert(void) } #endif /* WOLFSSL_TEST_CERT */ +#if defined(HAVE_ED25519_KEY_IMPORT) +static wc_test_ret_t ed25519_test_check_key(void) +{ + /* Fails to find x-ordinate from this y-ordinate. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_bad_y[] = { + 0x40, + 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + }; + /* Y-ordinate value larger than prime. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_bad_y_max[] = { + 0x40, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + }; + /* Y-ordinate value equal to prime. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_bad_y_is_p[] = { + 0x40, + 0xed,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + }; + /* Y-ordinate value equal to prime - 1. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_y_is_p_minus_1[] = { + 0x40, + 0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + }; + ed25519_key key; + int ret; + int res = 0; + + /* Initialize key for use. */ + ret = wc_ed25519_init_ex(&key, HEAP_HINT, devId); + if (ret != 0) { + return WC_TEST_RET_ENC_NC; + } + + /* Load bad public key only and perform checks. */ + ret = wc_ed25519_import_public(key_bad_y, ED25519_PUB_KEY_SIZE + 1, &key); + if (ret != PUBLIC_KEY_E) { + res = WC_TEST_RET_ENC_NC; + } + if (res == 0) { + /* Load bad public key only and perform checks. */ + ret = wc_ed25519_import_public(key_bad_y_max, ED25519_PUB_KEY_SIZE + 1, + &key); + if (ret != PUBLIC_KEY_E) { + res = WC_TEST_RET_ENC_NC; + } + } + if (res == 0) { + /* Load bad public key only and perform checks. */ + ret = wc_ed25519_import_public(key_bad_y_is_p, ED25519_PUB_KEY_SIZE + 1, + &key); + if (ret != PUBLIC_KEY_E) { + res = WC_TEST_RET_ENC_NC; + } + } + if (res == 0) { + /* Load good public key only and perform checks. */ + ret = wc_ed25519_import_public(key_y_is_p_minus_1, + ED25519_PUB_KEY_SIZE + 1, &key); + if (ret != 0) { + res = WC_TEST_RET_ENC_NC; + } + } + + /* Dispose of key. */ + wc_ed25519_free(&key); + + return res; +} +#endif + #if defined(HAVE_ED25519_SIGN) && defined(HAVE_ED25519_KEY_EXPORT) && \ defined(HAVE_ED25519_KEY_IMPORT) static wc_test_ret_t ed25519ctx_test(void) @@ -33740,6 +33822,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ed25519_test(void) (void)keySz; (void)sigSz; + ret = ed25519_test_check_key(); + if (ret < 0) + return ret; #ifdef WOLFSSL_TEST_CERT ret = ed25519_test_cert(); if (ret < 0) @@ -34265,6 +34350,104 @@ static wc_test_ret_t ed448_test_make_cert(void) } #endif /* WOLFSSL_TEST_CERT */ +#if defined(HAVE_ED448_KEY_IMPORT) +static wc_test_ret_t ed448_test_check_key(void) +{ + /* Fails to find x-ordinate from this y-ordinate. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_bad_y[] = { + 0x40, + 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00 + }; + /* Y-ordinate value larger than prime. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_bad_y_max[] = { + 0x40, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff + }; + /* Y-ordinate value equal to prime. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_bad_y_is_p[] = { + 0x40, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff + }; + /* Y-ordinate value equal to prime - 1. */ + WOLFSSL_SMALL_STACK_STATIC const byte key_y_is_p_minus_1[] = { + 0x40, + 0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff + }; + ed448_key key; + int ret; + int res = 0; + + /* Initialize key for use. */ + ret = wc_ed448_init_ex(&key, HEAP_HINT, devId); + if (ret != 0) { + return WC_TEST_RET_ENC_NC; + } + + /* Load bad public key only and perform checks. */ + ret = wc_ed448_import_public(key_bad_y, ED448_PUB_KEY_SIZE + 1, &key); + if (ret != PUBLIC_KEY_E) { + res = WC_TEST_RET_ENC_NC; + } + if (ret == 0) { + /* Load bad public key only and perform checks. */ + ret = wc_ed448_import_public(key_bad_y_max, ED448_PUB_KEY_SIZE + 1, + &key); + if (ret != PUBLIC_KEY_E) { + res = WC_TEST_RET_ENC_NC; + } + } + if (res == 0) { + /* Load bad public key only and perform checks. */ + ret = wc_ed448_import_public(key_bad_y_is_p, ED448_PUB_KEY_SIZE + 1, + &key); + if (ret != PUBLIC_KEY_E) { + res = WC_TEST_RET_ENC_NC; + } + } + if (res == 0) { + /* Load good public key only and perform checks. */ + ret = wc_ed448_import_public(key_y_is_p_minus_1, ED448_PUB_KEY_SIZE + 1, + &key); + if (ret != 0) { + res = WC_TEST_RET_ENC_NC; + } + } + + /* Dispose of key. */ + wc_ed448_free(&key); + + return res; +} +#endif + #if defined(HAVE_ED448_SIGN) && defined(HAVE_ED448_KEY_EXPORT) && \ defined(HAVE_ED448_KEY_IMPORT) static wc_test_ret_t ed448_ctx_test(void) @@ -35263,6 +35446,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ed448_test(void) (void)keySz; (void)sigSz; + ret = ed448_test_check_key(); + if (ret < 0) + return ret; #ifdef WOLFSSL_TEST_CERT ret = ed448_test_cert(); if (ret < 0)