Skip to content

Commit 1027dcf

Browse files
authored
Merge pull request #765 from libtom/pr/fix-issue764
Fix issue #764 - small-subgroup attack
2 parents a68fa19 + bc95a1f commit 1027dcf

3 files changed

Lines changed: 70 additions & 5 deletions

File tree

‎src/pk/ecc/ecc_shared_secret.c‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ int ecc_shared_secret(const ecc_key *private_key, const ecc_key *public_key,
2323
{
2424
unsigned long x;
2525
ecc_point *result;
26-
void *prime, *a;
27-
int err;
26+
void *prime, *a, *mp = NULL;
27+
int err, inf;
2828

2929
LTC_ARGCHK(private_key != NULL);
3030
LTC_ARGCHK(public_key != NULL);
@@ -59,7 +59,15 @@ int ecc_shared_secret(const ecc_key *private_key, const ecc_key *public_key,
5959
prime = private_key->dp.prime;
6060
a = private_key->dp.A;
6161

62-
if ((err = ltc_mp.ecc_ptmul(private_key->k, &public_key->pubkey, result, a, prime, 1)) != CRYPT_OK) { goto done; }
62+
if ((err = ltc_mp.ecc_ptmul(private_key->k, &public_key->pubkey, result, a, prime, 0)) != CRYPT_OK) { goto done; }
63+
64+
/* reject small-subgroup / invalid-curve attacks: k*pubkey must not be O */
65+
if ((err = ltc_ecc_is_point_at_infinity(result, prime, &inf)) != CRYPT_OK) { goto done; }
66+
if (inf) { err = CRYPT_ERROR; goto done; }
67+
68+
/* map=0 above kept z meaningful for the infinity check; now finish the affine map */
69+
if ((err = ltc_mp_montgomery_setup(prime, &mp)) != CRYPT_OK) { goto done; }
70+
if ((err = ltc_mp.ecc_map(result, prime, mp)) != CRYPT_OK) { goto done; }
6371

6472
x = (unsigned long)ltc_mp_unsigned_bin_size(prime);
6573
if (*outlen < x) {
@@ -73,6 +81,7 @@ int ecc_shared_secret(const ecc_key *private_key, const ecc_key *public_key,
7381
err = CRYPT_OK;
7482
*outlen = x;
7583
done:
84+
if (mp != NULL) ltc_mp_montgomery_free(mp);
7685
ltc_ecc_del_point(result);
7786
return err;
7887
}

‎src/pk/ecc/ltc_ecc_verify_key.c‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ int ltc_ecc_verify_key(const ecc_key *key)
4040

4141
/* Test 3: does nG = O? (n = order, O = point at infinity, G = public key) */
4242
point = ltc_ecc_new_point();
43-
if ((err = ltc_ecc_mulmod(order, &(key->pubkey), point, a, prime, 1)) != CRYPT_OK) { goto done1; }
43+
if ((err = ltc_ecc_mulmod(order, &(key->pubkey), point, a, prime, 0)) != CRYPT_OK) { goto done1; }
4444

4545
err = ltc_ecc_is_point_at_infinity(point, prime, &inf);
46-
if (err != CRYPT_OK || inf) {
46+
if (err != CRYPT_OK || !inf) {
4747
err = CRYPT_ERROR;
4848
}
4949
else {

‎tests/ecc_test.c‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2549,6 +2549,59 @@ static int s_ecc_test_recovery(void)
25492549
}
25502550
#endif
25512551

2552+
#ifdef LTC_ECC_SECP112R2
2553+
/* https://github.com/libtom/libtomcrypt/issues/764 - small-subgroup attack regression test
2554+
SECP112R2 has cofactor 4, so the curve contains a subgroup of order 4 outside the prime-order generator subgroup
2555+
A point in that small subgroup is on the curve so Test 2 in ltc_ecc_verify_key passes. However n * P != O, so Test 3 must reject it
2556+
The point below has order 4: n * P gives -P, not the point at infinity. Therefore, importing it as a public key must fail
2557+
*/
2558+
static int s_ecc_issue764(void)
2559+
{
2560+
const ltc_ecc_curve *cu;
2561+
ecc_key key, priv;
2562+
int err;
2563+
unsigned char shared[14];
2564+
unsigned long sharedlen;
2565+
const unsigned char pub[] = {
2566+
0x04, /* uncompressed */
2567+
0xB1,0xFD,0x8D,0xE1,0x27,0xD4,0x65,0x6B,0x57,0x3E,0xB5,0x13,0x98,0x4C, /* x = B1FD8DE127D4656B573EB513984C */
2568+
0x2F,0x8C,0xD8,0x80,0x3D,0xB9,0x62,0x0F,0xA3,0xA6,0x0E,0x5B,0x31,0xE2 /* y = 2F8CD8803DB9620FA3A60E5B31E2 */
2569+
};
2570+
DO(ecc_find_curve("SECP112R2", &cu));
2571+
2572+
/* ecc_set_key must reject the malicious public key (verify_key Test 3) */
2573+
DO(ecc_set_curve(cu, &key));
2574+
err = ecc_set_key(pub, sizeof(pub), PK_PUBLIC, &key); /* must fail */
2575+
if (err == CRYPT_OK) {
2576+
ecc_free(&key);
2577+
return CRYPT_FAIL_TESTVECTOR;
2578+
}
2579+
2580+
/* ecc_shared_secret must reject the malicious peer key even when the caller bypassed ecc_set_key by directly loading the pubkey
2581+
priv.k = 4 (a multiple of the malicious point's order 4) so that k * pubkey is deterministically the point at infinity
2582+
*/
2583+
DO(ecc_set_curve(cu, &priv));
2584+
DO(ltc_mp_set(priv.k, 4));
2585+
priv.type = PK_PRIVATE;
2586+
2587+
/* load the malicious point directly to simulate an attacker-supplied pubkey reaching ecc_shared_secret without import checks */
2588+
DO(ecc_set_curve(cu, &key));
2589+
DO(ltc_mp_read_unsigned_bin(key.pubkey.x, (unsigned char *)pub + 1, 14));
2590+
DO(ltc_mp_read_unsigned_bin(key.pubkey.y, (unsigned char *)pub + 15, 14));
2591+
DO(ltc_mp_set(key.pubkey.z, 1));
2592+
key.type = PK_PUBLIC;
2593+
2594+
sharedlen = sizeof(shared);
2595+
err = ecc_shared_secret(&priv, &key, shared, &sharedlen); /* must fail */
2596+
ecc_free(&priv);
2597+
ecc_free(&key);
2598+
if (err == CRYPT_OK) {
2599+
return CRYPT_FAIL_TESTVECTOR;
2600+
}
2601+
return CRYPT_OK;
2602+
}
2603+
#endif
2604+
25522605
int ecc_test(void)
25532606
{
25542607
if (ltc_mp.name == NULL) return CRYPT_NOP;
@@ -2574,6 +2627,9 @@ int ecc_test(void)
25742627
DO(s_ecc_issue443_447());
25752628
DO(s_ecc_issue630());
25762629
DO(s_ecc_issue116());
2630+
#ifdef LTC_ECC_SECP112R2
2631+
DO(s_ecc_issue764());
2632+
#endif
25772633
#ifdef LTC_ECC_SHAMIR
25782634
DO(s_ecc_test_shamir());
25792635
DO(s_ecc_test_recovery());

0 commit comments

Comments
 (0)