Skip to content

Commit 2571898

Browse files
committed
curve25519: Prevent Ed25519 signature malleability
As per RFC 8032, section 5.1.7 (and section 8.4) we have to make sure s, which is the scalar in the second half of the signature value, is smaller than L. Without that check, L can be added to most signatures at least once to create another valid signature for the same public key and message. This could be problematic if, for instance, a blacklist is based on hashes of certificates. A new certificate could be created with a different signature (without knowing the signature key) by simply adding L to s. Currently, both OpenSSL 1.1.1 and Botan 2.8.0 are vulnerable to this, which is why the unit test currently only warns about it.
1 parent 69756c0 commit 2571898

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

src/libstrongswan/plugins/curve25519/curve25519_public_key.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ METHOD(public_key_t, get_type, key_type_t,
4949
return KEY_ED25519;
5050
}
5151

52+
/* L = 2^252+27742317777372353535851937790883648493 in little-endian form */
53+
static chunk_t curve25519_order = chunk_from_chars(
54+
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
55+
0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
56+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
57+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10);
58+
5259
METHOD(public_key_t, verify, bool,
5360
private_curve25519_public_key_t *this, signature_scheme_t scheme,
5461
void *params, chunk_t data, chunk_t signature)
@@ -94,6 +101,20 @@ METHOD(public_key_t, verify, bool,
94101
{
95102
return FALSE;
96103
}
104+
/* make sure 0 <= s < L, as per RFC 8032, section 5.1.7 to prevent signature
105+
* malleability. Due to the three-bit check above (forces s < 2^253) there
106+
* is not that much room, but adding L once works with most signatures */
107+
for (i = 31; ; i--)
108+
{
109+
if (sig[i+32] < curve25519_order.ptr[i])
110+
{
111+
break;
112+
}
113+
else if (sig[i+32] > curve25519_order.ptr[i] || i == 0)
114+
{
115+
return FALSE;
116+
}
117+
}
97118

98119
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA512);
99120
if (!hasher)

src/libstrongswan/tests/suites/test_ed25519.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct sig_test_t {
2727
};
2828

2929
/**
30-
* Ed25519 Test Vectors from draft-irtf-cfrg-eddsa
30+
* Ed25519 Test Vectors from RFC 8032
3131
*/
3232
static sig_test_t sig_tests[] = {
3333
/* Test 1 */
@@ -429,6 +429,16 @@ static chunk_t zero_pk = chunk_from_chars(
429429
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
430430
0x00, 0x00, 0x00, 0x00);
431431

432+
/* sig_tests[0].sig with s+L */
433+
static chunk_t malleable_sig = chunk_from_chars(
434+
0xe5, 0x56, 0x43, 0x00, 0xc3, 0x60, 0xac, 0x72, 0x90, 0x86,
435+
0xe2, 0xcc, 0x80, 0x6e, 0x82, 0x8a, 0x84, 0x87, 0x7f, 0x1e,
436+
0xb8, 0xe5, 0xd9, 0x74, 0xd8, 0x73, 0xe0, 0x65, 0x22, 0x49,
437+
0x01, 0x55, 0x4c, 0x8c, 0x78, 0x72, 0xaa, 0x06, 0x4e, 0x04,
438+
0x9d, 0xbb, 0x30, 0x13, 0xfb, 0xf2, 0x93, 0x80, 0xd2, 0x5b,
439+
0xf5, 0xf0, 0x59, 0x5b, 0xbe, 0x24, 0x65, 0x51, 0x41, 0x43,
440+
0x8e, 0x7a, 0x10, 0x1b);
441+
432442
START_TEST(test_ed25519_fail)
433443
{
434444
private_key_t *key;
@@ -479,6 +489,16 @@ START_TEST(test_ed25519_fail)
479489
ck_assert(!pubkey->verify(pubkey, SIGN_ED25519, NULL, chunk_empty,
480490
chunk_empty));
481491

492+
/* RFC 8032, section 5.1.7 requires that 0 <= s < L to prevent signature
493+
* malleability. Only a warning because Botan and OpenSSL are both
494+
* vulnerable to this. */
495+
if (pubkey->verify(pubkey, SIGN_ED25519, NULL, sig_tests[0].msg,
496+
malleable_sig))
497+
{
498+
warn("Ed25519 signature verification is vulnerable to malleable "
499+
"signatures");
500+
}
501+
482502
/* malformed signature */
483503
sig = chunk_create(sig1, 64);
484504
memcpy(sig1, sig_tests[0].sig.ptr, 64);

0 commit comments

Comments
 (0)