From 31e5b621b5e660fbe6e7cf4f33689a5c58781cfc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Feb 2020 06:00:39 +0000 Subject: [PATCH] Implement "curve448-sha512" kex, from RFC 8731. With all the preparation now in place, this is more or less trivial. We add a new curve setup function in sshecc.c, and an ssh_kex linking to it; we add the curve parameters to the reference / test code eccref.py, and use them to generate the list of low-order input values that should be rejected by the sanity check on the kex output; we add the standard test vectors from RFC 7748 in cryptsuite.py, and the low-order values we just generated. --- ssh.h | 1 + sshecc.c | 42 +++++++++++++++++++++++++++++++ test/cryptsuite.py | 61 ++++++++++++++++++++++++++++++++++++++-------- test/eccref.py | 3 +++ testcrypt.c | 1 + 5 files changed, 98 insertions(+), 10 deletions(-) diff --git a/ssh.h b/ssh.h index f017ed68..0ae6a9df 100644 --- a/ssh.h +++ b/ssh.h @@ -981,6 +981,7 @@ extern const ssh_kexes ssh_diffiehellman_gex; extern const ssh_kexes ssh_gssk5_sha1_kex; extern const ssh_kexes ssh_rsa_kex; extern const ssh_kex ssh_ec_kex_curve25519; +extern const ssh_kex ssh_ec_kex_curve448; extern const ssh_kex ssh_ec_kex_nistp256; extern const ssh_kex ssh_ec_kex_nistp384; extern const ssh_kex ssh_ec_kex_nistp521; diff --git a/sshecc.c b/sshecc.c index 7725013f..ef9aeb28 100644 --- a/sshecc.c +++ b/sshecc.c @@ -213,6 +213,35 @@ static struct ec_curve *ec_curve25519(void) return &curve; } +static struct ec_curve *ec_curve448(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6); + mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001); + mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005); + initialise_mcurve(&curve, p, a, b, G_x, 2); + mp_free(p); + mp_free(a); + mp_free(b); + mp_free(G_x); + + /* This curve doesn't need a name, because it's never used in + * any format that embeds the curve name */ + curve.name = NULL; + curve.textname = "Curve448"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + static struct ec_curve *ec_ed25519(void) { static struct ec_curve curve = { 0 }; @@ -1492,6 +1521,18 @@ const ssh_kex ssh_ec_kex_curve25519_libssh = { &ssh_sha256, &kex_extra_curve25519, }; +static const struct eckex_extra kex_extra_curve448 = { + ec_curve448, + ssh_ecdhkex_m_setup, + ssh_ecdhkex_m_cleanup, + ssh_ecdhkex_m_getpublic, + ssh_ecdhkex_m_getkey, +}; +const ssh_kex ssh_ec_kex_curve448 = { + "curve448-sha512", NULL, KEXTYPE_ECDH, + &ssh_sha512, &kex_extra_curve448, +}; + static const struct eckex_extra kex_extra_nistp256 = { ec_p256, ssh_ecdhkex_w_setup, @@ -1529,6 +1570,7 @@ const ssh_kex ssh_ec_kex_nistp521 = { }; static const ssh_kex *const ec_kex_list[] = { + &ssh_ec_kex_curve448, &ssh_ec_kex_curve25519, &ssh_ec_kex_curve25519_libssh, &ssh_ec_kex_nistp256, diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 225b69e4..7faf61b9 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1565,11 +1565,35 @@ class crypt(MyTestBase): "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ] + # Same for Curve448, found by the analogous eccref function call + # find_montgomery_power2_order_x_values(curve448.p, curve448.a) + bad_keys_448 = [ + # The first three are the bad values in canonical + # representationm. In Curve448 these are just 0, 1 and -1. + '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + '0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'fefffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff', + + # As with Curve25519, we must also include values in + # non-canonical representation that reduce to one of the + # above mod p. + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '00000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + # But that's all, because Curve448 fits neatly into a + # whole number of bytes, so there's no secondary reduction + # mod a power of 2. + ] + with random_prng("doesn't matter"): ecdh25519 = ssh_ecdhkex_newkey('curve25519') + ecdh448 = ssh_ecdhkex_newkey('curve448') for pub in bad_keys_25519: key = ssh_ecdhkex_getkey(ecdh25519, unhex(pub)) self.assertEqual(key, None) + for pub in bad_keys_448: + key = ssh_ecdhkex_getkey(ecdh448, unhex(pub)) + self.assertEqual(key, None) def testPRNG(self): hashalg = 'sha256' @@ -2423,36 +2447,53 @@ class standard_test_vectors(MyTestBase): # string and peer public value, giving the expected output # shared key. Source: RFC 7748 section 5.2. rfc7748s5_2 = [ - ('a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4', + ('curve25519', + 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4', 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c', 0xc3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552), - ('4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d', + ('curve25519', + '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d', 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493', 0x95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957), + ('curve448', + '3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3', + '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086', + 0xce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f), + ('curve448', + '203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f', + '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db', + 0x884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d), ] - for priv, pub, expected in rfc7748s5_2: + for method, priv, pub, expected in rfc7748s5_2: with queued_specific_random_data(unhex(priv)): - ecdh = ssh_ecdhkex_newkey('curve25519') + ecdh = ssh_ecdhkex_newkey(method) key = ssh_ecdhkex_getkey(ecdh, unhex(pub)) self.assertEqual(int(key), expected) # Bidirectional tests, consisting of the input random number # strings for both parties, and the expected public values and - # shared key. Source: RFC 7748 section 6.1. - rfc7748s6_1 = [ - ('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a', + # shared key. Source: RFC 7748 section 6. + rfc7748s6 = [ + ('curve25519', # section 6.1 + '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a', '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a', '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb', 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f', 0x4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742), + ('curve448', # section 6.2 + '9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b', + '9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0', + '1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d', + '3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609', + 0x07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d), ] - for apriv, apub, bpriv, bpub, expected in rfc7748s6_1: + for method, apriv, apub, bpriv, bpub, expected in rfc7748s6: with queued_specific_random_data(unhex(apriv)): - alice = ssh_ecdhkex_newkey('curve25519') + alice = ssh_ecdhkex_newkey(method) with queued_specific_random_data(unhex(bpriv)): - bob = ssh_ecdhkex_newkey('curve25519') + bob = ssh_ecdhkex_newkey(method) self.assertEqualBin(ssh_ecdhkex_getpublic(alice), unhex(apub)) self.assertEqualBin(ssh_ecdhkex_getpublic(bob), unhex(bpub)) akey = ssh_ecdhkex_getkey(alice, unhex(bpub)) diff --git a/test/eccref.py b/test/eccref.py index 27d43b4b..9fdafc90 100644 --- a/test/eccref.py +++ b/test/eccref.py @@ -316,6 +316,9 @@ p521.G_order = 0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff curve25519 = MontgomeryCurve(2**255-19, 0x76d06, 1) curve25519.G = curve25519.cpoint(9) +curve448 = MontgomeryCurve(2**448-2**224-1, 0x262a6, 1) +curve448.G = curve448.cpoint(5) + ed25519 = TwistedEdwardsCurve(2**255-19, 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3, -1) ed25519.G = ed25519.point(0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a,0x6666666666666666666666666666666666666666666666666666666666666658) ed25519.G_order = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed diff --git a/testcrypt.c b/testcrypt.c index 8f6990ee..7f6621f4 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -340,6 +340,7 @@ static const ssh_kex *get_ecdh_alg(BinarySource *in) const ssh_kex *value; } algs[] = { {"curve25519", &ssh_ec_kex_curve25519}, + {"curve448", &ssh_ec_kex_curve448}, {"nistp256", &ssh_ec_kex_nistp256}, {"nistp384", &ssh_ec_kex_nistp384}, {"nistp521", &ssh_ec_kex_nistp521},