EdDSA FIPS checks on public key

Check that the Ed25519 and Ed448 public key is valid even without
private key.
Perform pairwise consistency test, only in FIPS, when making a key i
Ed25519 and Ed448.
This commit is contained in:
Sean Parkinson
2024-04-17 16:05:35 +10:00
parent 03ed52bd81
commit c590fe514f
3 changed files with 440 additions and 20 deletions

View File

@ -208,6 +208,56 @@ static int ed25519_hash(ed25519_key* key, const byte* in, word32 inLen,
}
#ifdef HAVE_ED25519_MAKE_KEY
#if FIPS_VERSION_GE(7,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_VERSION_GE(7,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;
}

View File

@ -187,6 +187,56 @@ static int ed448_hash(ed448_key* key, const byte* in, word32 inLen,
return ret;
}
#if FIPS_VERSION_GE(7,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_VERSION_GE(7,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;

View File

@ -32902,6 +32902,88 @@ done:
}
#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)
@ -33735,6 +33817,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)
@ -34260,6 +34345,104 @@ done:
}
#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)
@ -35258,6 +35441,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)