From c082840d43daf591535da6d88ceaeae614547031 Mon Sep 17 00:00:00 2001 From: Kris Kwiatkowski Date: Mon, 11 Mar 2019 14:40:59 +0000 Subject: [PATCH] Integrate SIKE with TLS key exchange. Implements support for hybrid key exchange based on SIKEp503, a post quantum, isogeny based KEM. This is a hybrid construction mixed with X25519 key agreement. Code point is 0xFE32. Cloudflare's SIDH implementation is used for testing. Key exchange can be used with TLS1.3 only. Change-Id: I3a5f38d6f7d016274e5bcfb629249664e1d983eb --- include/openssl/nid.h | 2 + include/openssl/ssl.h | 1 + ssl/ssl_key_share.cc | 101 +++- ssl/t1_lib.cc | 20 +- ssl/test/runner/common.go | 15 +- ssl/test/runner/handshake_server.go | 8 +- ssl/test/runner/key_agreement.go | 99 +++- ssl/test/runner/runner.go | 201 +++++++- ssl/test/runner/sike/arith.go | 416 +++++++++++++++++ ssl/test/runner/sike/consts.go | 287 ++++++++++++ ssl/test/runner/sike/curve.go | 408 ++++++++++++++++ ssl/test/runner/sike/sike.go | 695 ++++++++++++++++++++++++++++ ssl/test/runner/sike/sike_test.go | 680 +++++++++++++++++++++++++++ ssl/test/test_config.cc | 4 + ssl/tls13_server.cc | 2 +- 15 files changed, 2915 insertions(+), 24 deletions(-) create mode 100644 ssl/test/runner/sike/arith.go create mode 100644 ssl/test/runner/sike/consts.go create mode 100644 ssl/test/runner/sike/curve.go create mode 100644 ssl/test/runner/sike/sike.go create mode 100644 ssl/test/runner/sike/sike_test.go diff --git a/include/openssl/nid.h b/include/openssl/nid.h index 270d443a..afa88297 100644 --- a/include/openssl/nid.h +++ b/include/openssl/nid.h @@ -4237,6 +4237,8 @@ extern "C" { #define SN_CECPQ2 "CECPQ2" #define NID_CECPQ2 959 +#define SN_CECPQ2b "CECPQ2b" +#define NID_CECPQ2b 960 #if defined(__cplusplus) } /* extern C */ diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 629f0063..35354fce 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -2206,6 +2206,7 @@ OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); #define SSL_CURVE_SECP521R1 25 #define SSL_CURVE_X25519 29 #define SSL_CURVE_CECPQ2 16696 +#define SSL_CURVE_CECPQ2b 0xFE32 // SSL_get_curve_id returns the ID of the curve used by |ssl|'s most recently // completed handshake or 0 if not applicable. diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc index 78d2aa16..25403072 100644 --- a/ssl/ssl_key_share.cc +++ b/ssl/ssl_key_share.cc @@ -31,7 +31,7 @@ #include "internal.h" #include "../crypto/internal.h" - +#include "../third_party/sike/sike.h" BSSL_NAMESPACE_BEGIN @@ -300,6 +300,102 @@ class CECPQ2KeyShare : public SSLKeyShare { HRSS_private_key hrss_private_key_; }; +class CECPQ2bKeyShare : public SSLKeyShare { +public: + uint16_t GroupID() const override { + return SSL_CURVE_CECPQ2b; + } + + bool Offer(CBB *out) override { + uint8_t public_x25519[32] = {0}; + X25519_keypair(public_x25519, private_x25519_); + if (!SIKE_keypair(private_SIKE_, public_SIKE_)) { + return false; + } + + return + CBB_add_bytes(out, public_x25519, sizeof(public_x25519)) && + CBB_add_bytes(out, public_SIKE_, sizeof(public_SIKE_)); + } + + bool Accept(CBB *out_public_key, Array *out_secret, + uint8_t *out_alert, Span peer_key) override + { + uint8_t public_x25519[32] = {0}; + uint8_t private_x25519[32] = {0}; + uint8_t sike_ct[SIKEp503_CT_BYTESZ] = {0}; + + *out_alert = SSL_AD_INTERNAL_ERROR; + + if (peer_key.size() != sizeof(public_x25519) + SIKEp503_PUB_BYTESZ) { + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + + Array secret; + if (!secret.Init(sizeof(private_x25519_) + SIKEp503_SS_BYTESZ)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); + return false; + } + + X25519_keypair(public_x25519, private_x25519); + if (!X25519(secret.data(), private_x25519, peer_key.data())) { + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + + SIKE_encaps(secret.data()+sizeof(private_x25519_), sike_ct, peer_key.data()+sizeof(public_x25519)); + *out_secret = std::move(secret); + + return + CBB_add_bytes(out_public_key, public_x25519, sizeof(public_x25519)) && + CBB_add_bytes(out_public_key, sike_ct, sizeof(sike_ct)); + } + + bool Finish(Array *out_secret, uint8_t *out_alert, + Span peer_key) override { + *out_alert = SSL_AD_INTERNAL_ERROR; + + Array secret; + if (!secret.Init(sizeof(private_x25519_) + SIKEp503_SS_BYTESZ)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); + return false; + } + + if (peer_key.size() != (32 + SIKEp503_CT_BYTESZ) || + !X25519(secret.data(), private_x25519_, peer_key.data())) { + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + + SIKE_decaps(secret.data()+sizeof(private_x25519_), peer_key.data()+32, public_SIKE_, private_SIKE_); + *out_secret = std::move(secret); + return true; + } + + bool Serialize(CBB *out) override { + return (CBB_add_asn1_uint64(out, GroupID()) && + CBB_add_asn1_octet_string(out, private_x25519_, sizeof(private_x25519_)) && + CBB_add_asn1_octet_string(out, private_SIKE_, sizeof(private_SIKE_))); + } + + bool Deserialize(CBS *in) override { + CBS key; + return CBS_get_asn1(in, &key, CBS_ASN1_OCTETSTRING) && + (CBS_len(&key) == (sizeof(private_x25519_) + sizeof(private_SIKE_))) && + CBS_copy_bytes(&key, private_x25519_, sizeof(private_x25519_)) && + CBS_copy_bytes(&key, private_SIKE_, sizeof(private_SIKE_)); + } + +private: + uint8_t private_x25519_[32]; + uint8_t private_SIKE_[SIKEp503_PRV_BYTESZ]; + uint8_t public_SIKE_[SIKEp503_PUB_BYTESZ]; +}; + CONSTEXPR_ARRAY NamedGroup kNamedGroups[] = { {NID_secp224r1, SSL_CURVE_SECP224R1, "P-224", "secp224r1"}, {NID_X9_62_prime256v1, SSL_CURVE_SECP256R1, "P-256", "prime256v1"}, @@ -307,6 +403,7 @@ CONSTEXPR_ARRAY NamedGroup kNamedGroups[] = { {NID_secp521r1, SSL_CURVE_SECP521R1, "P-521", "secp521r1"}, {NID_X25519, SSL_CURVE_X25519, "X25519", "x25519"}, {NID_CECPQ2, SSL_CURVE_CECPQ2, "CECPQ2", "CECPQ2"}, + {NID_CECPQ2b, SSL_CURVE_CECPQ2b, "CECPQ2b", "CECPQ2b"}, }; } // namespace @@ -333,6 +430,8 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { return UniquePtr(New()); case SSL_CURVE_CECPQ2: return UniquePtr(New()); + case SSL_CURVE_CECPQ2b: + return UniquePtr(New()); default: return nullptr; } diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc index c0452dc7..a1a2bcc3 100644 --- a/ssl/t1_lib.cc +++ b/ssl/t1_lib.cc @@ -199,6 +199,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { return true; } +static bool is_pq_group(uint16_t id) { + return (id == SSL_CURVE_CECPQ2) || (id == SSL_CURVE_CECPQ2b); +} + bool ssl_client_hello_init(SSL *ssl, SSL_CLIENT_HELLO *out, const SSLMessage &msg) { OPENSSL_memset(out, 0, sizeof(*out)); @@ -325,10 +329,10 @@ bool tls1_get_shared_group(SSL_HANDSHAKE *hs, uint16_t *out_group_id) { for (uint16_t pref_group : pref) { for (uint16_t supp_group : supp) { if (pref_group == supp_group && - // CECPQ2 doesn't fit in the u8-length-prefixed ECPoint field in TLS + // CECPQ2/3 doesn't fit in the u8-length-prefixed ECPoint field in TLS // 1.2 and below. (ssl_protocol_version(ssl) >= TLS1_3_VERSION || - pref_group != SSL_CURVE_CECPQ2)) { + !is_pq_group(pref_group))) { *out_group_id = pref_group; return true; } @@ -390,9 +394,9 @@ bool tls1_set_curves_list(Array *out_group_ids, const char *curves) { } bool tls1_check_group_id(const SSL_HANDSHAKE *hs, uint16_t group_id) { - if (group_id == SSL_CURVE_CECPQ2 && + if (is_pq_group(group_id) && ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) { - // CECPQ2 requires TLS 1.3. + // CECPQ2/3 requires TLS 1.3. return false; } @@ -2186,7 +2190,7 @@ static bool ext_key_share_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { group_id = groups[0]; - if (group_id == SSL_CURVE_CECPQ2 && groups.size() >= 2) { + if (is_pq_group(group_id) && groups.size() >= 2) { // CECPQ2 is not sent as the only initial key share. We'll include the // 2nd preference group too to avoid round-trips. second_group_id = groups[1]; @@ -2195,7 +2199,9 @@ static bool ext_key_share_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { } CBB key_exchange; - hs->key_shares[0] = SSLKeyShare::Create(group_id); + if ((hs->key_shares[0] = SSLKeyShare::Create(group_id)) == nullptr) { + return false; + } if (!hs->key_shares[0] || !CBB_add_u16(&kse_bytes, group_id) || !CBB_add_u16_length_prefixed(&kse_bytes, &key_exchange) || @@ -2424,7 +2430,7 @@ static bool ext_supported_groups_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { } for (uint16_t group : tls1_get_grouplist(hs)) { - if (group == SSL_CURVE_CECPQ2 && + if (is_pq_group(group) && hs->max_version < TLS1_3_VERSION) { continue; } diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index bbcacf59..f0861242 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -144,12 +144,13 @@ var tls13HelloRetryRequest = []uint8{ type CurveID uint16 const ( - CurveP224 CurveID = 21 - CurveP256 CurveID = 23 - CurveP384 CurveID = 24 - CurveP521 CurveID = 25 - CurveX25519 CurveID = 29 - CurveCECPQ2 CurveID = 16696 + CurveP224 CurveID = 21 + CurveP256 CurveID = 23 + CurveP384 CurveID = 24 + CurveP521 CurveID = 25 + CurveX25519 CurveID = 29 + CurveCECPQ2 CurveID = 16696 + CurveCECPQ2b CurveID = 0xFE32 ) // TLS Elliptic Curve Point Formats @@ -1728,7 +1729,7 @@ func (c *Config) maxVersion(isDTLS bool) uint16 { return ret } -var defaultCurvePreferences = []CurveID{CurveCECPQ2, CurveX25519, CurveP256, CurveP384, CurveP521} +var defaultCurvePreferences = []CurveID{CurveCECPQ2b, CurveCECPQ2, CurveX25519, CurveP256, CurveP384, CurveP521} func (c *Config) curvePreferences() []CurveID { if c == nil || len(c.CurvePreferences) == 0 { diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index d2ef9b42..2dc70326 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -210,8 +210,8 @@ func (hs *serverHandshakeState) readClientHello() error { if config.Bugs.FailIfCECPQ2Offered { for _, offeredCurve := range hs.clientHello.supportedCurves { - if offeredCurve == CurveCECPQ2 { - return errors.New("tls: CECPQ2 was offered") + if isPqGroup(offeredCurve) { + return errors.New("tls: CECPQ2 or CECPQ2b was offered") } } } @@ -1232,8 +1232,8 @@ func (hs *serverHandshakeState) processClientHello() (isResume bool, err error) preferredCurves := config.curvePreferences() Curves: for _, curve := range hs.clientHello.supportedCurves { - if curve == CurveCECPQ2 && c.vers < VersionTLS13 { - // CECPQ2 is TLS 1.3-only. + if isPqGroup(curve) && c.vers < VersionTLS13 { + // CECPQ2 and CECPQ2b is TLS 1.3-only. continue } diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go index 13e78bc4..0d6405fa 100644 --- a/ssl/test/runner/key_agreement.go +++ b/ssl/test/runner/key_agreement.go @@ -19,6 +19,7 @@ import ( "boringssl.googlesource.com/boringssl/ssl/test/runner/curve25519" "boringssl.googlesource.com/boringssl/ssl/test/runner/ed25519" "boringssl.googlesource.com/boringssl/ssl/test/runner/hrss" + "boringssl.googlesource.com/boringssl/ssl/test/runner/sike" ) type keyType int @@ -433,6 +434,98 @@ func (e *cecpq2Curve) finish(peerKey []byte) (preMasterSecret []byte, err error) return preMasterSecret, nil } +// cecpq2BCurve implements CECPQ2b, which is SIKEp503 combined with X25519. +type cecpq2BCurve struct { + // Both public key and shared secret size + x25519PrivateKey [32]byte + sikePrivateKey *sike.PrivateKey +} + +func (e *cecpq2BCurve) offer(rand io.Reader) (publicKey []byte, err error) { + if _, err = io.ReadFull(rand, e.x25519PrivateKey[:]); err != nil { + return nil, err + } + + var x25519Public [32]byte + curve25519.ScalarBaseMult(&x25519Public, &e.x25519PrivateKey) + + e.sikePrivateKey = sike.NewPrivateKey(sike.KeyVariant_SIKE) + if err = e.sikePrivateKey.Generate(rand); err != nil { + return nil, err + } + + sikePublic := e.sikePrivateKey.GeneratePublicKey().Export() + var ret []byte + ret = append(ret, x25519Public[:]...) + ret = append(ret, sikePublic...) + return ret, nil +} + +func (e *cecpq2BCurve) accept(rand io.Reader, peerKey []byte) (publicKey []byte, preMasterSecret []byte, err error) { + if len(peerKey) != 32+sike.Params.PublicKeySize { + return nil, nil, errors.New("tls: bad length CECPQ2b offer") + } + + if _, err = io.ReadFull(rand, e.x25519PrivateKey[:]); err != nil { + return nil, nil, err + } + + var x25519Shared, x25519PeerKey, x25519Public [32]byte + copy(x25519PeerKey[:], peerKey) + curve25519.ScalarBaseMult(&x25519Public, &e.x25519PrivateKey) + curve25519.ScalarMult(&x25519Shared, &e.x25519PrivateKey, &x25519PeerKey) + + // Per RFC 7748, reject the all-zero value in constant time. + var zeros [32]byte + if subtle.ConstantTimeCompare(zeros[:], x25519Shared[:]) == 1 { + return nil, nil, errors.New("tls: X25519 value with wrong order") + } + + var sikePubKey = sike.NewPublicKey(sike.KeyVariant_SIKE) + if err = sikePubKey.Import(peerKey[32:]); err != nil { + // should never happen as size was already checked + return nil, nil, errors.New("tls: implementation error") + } + sikeCiphertext, sikeShared, err := sike.Encapsulate(rand, sikePubKey) + if err != nil { + return nil, nil, err + } + + publicKey = append(publicKey, x25519Public[:]...) + publicKey = append(publicKey, sikeCiphertext...) + preMasterSecret = append(preMasterSecret, x25519Shared[:]...) + preMasterSecret = append(preMasterSecret, sikeShared...) + + return publicKey, preMasterSecret, nil +} + +func (e *cecpq2BCurve) finish(peerKey []byte) (preMasterSecret []byte, err error) { + if len(peerKey) != 32+(sike.Params.PublicKeySize+sike.Params.MsgLen) { + return nil, errors.New("tls: bad length CECPQ2b reply") + } + + var x25519Shared, x25519PeerKey [32]byte + copy(x25519PeerKey[:], peerKey) + curve25519.ScalarMult(&x25519Shared, &e.x25519PrivateKey, &x25519PeerKey) + + // Per RFC 7748, reject the all-zero value in constant time. + var zeros [32]byte + if subtle.ConstantTimeCompare(zeros[:], x25519Shared[:]) == 1 { + return nil, errors.New("tls: X25519 value with wrong order") + } + + var sikePubKey = e.sikePrivateKey.GeneratePublicKey() + sikeShared, err := sike.Decapsulate(e.sikePrivateKey, sikePubKey, peerKey[32:]) + if err != nil { + return nil, errors.New("tls: invalid SIKE ciphertext") + } + + preMasterSecret = append(preMasterSecret, x25519Shared[:]...) + preMasterSecret = append(preMasterSecret, sikeShared...) + + return preMasterSecret, nil +} + func curveForCurveID(id CurveID, config *Config) (ecdhCurve, bool) { switch id { case CurveP224: @@ -447,6 +540,8 @@ func curveForCurveID(id CurveID, config *Config) (ecdhCurve, bool) { return &x25519ECDHCurve{setHighBit: config.Bugs.SetX25519HighBit}, true case CurveCECPQ2: return &cecpq2Curve{}, true + case CurveCECPQ2b: + return &cecpq2BCurve{}, true default: return nil, false } @@ -594,8 +689,8 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer NextCandidate: for _, candidate := range preferredCurves { - if candidate == CurveCECPQ2 && version < VersionTLS13 { - // CECPQ2 is TLS 1.3-only. + if isPqGroup(candidate) && version < VersionTLS13 { + // CECPQ2 and CECPQ2b is TLS 1.3-only. continue } diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 8461bd86..8dac0f3f 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -10580,14 +10580,19 @@ var testCurves = []struct { {"P-521", CurveP521}, {"X25519", CurveX25519}, {"CECPQ2", CurveCECPQ2}, + {"CECPQ2b", CurveCECPQ2b}, } const bogusCurve = 0x1234 +func isPqGroup(r CurveID) bool { + return r == CurveCECPQ2 || r == CurveCECPQ2b +} + func addCurveTests() { for _, curve := range testCurves { for _, ver := range tlsVersions { - if curve.id == CurveCECPQ2 && ver.version < VersionTLS13 { + if isPqGroup(curve.id) && ver.version < VersionTLS13 { continue } @@ -10629,7 +10634,7 @@ func addCurveTests() { expectedCurveID: curve.id, }) - if curve.id != CurveX25519 && curve.id != CurveCECPQ2 { + if curve.id != CurveX25519 && !isPqGroup(curve.id) { testCases = append(testCases, testCase{ name: "CurveTest-Client-Compressed-" + suffix, config: Config{ @@ -11054,6 +11059,21 @@ func addCurveTests() { }, }) + // CECPQ2b should not be offered by a TLS < 1.3 client. + testCases = append(testCases, testCase{ + name: "CECPQ2bNotInTLS12", + config: Config{ + Bugs: ProtocolBugs{ + FailIfCECPQ2Offered: true, + }, + }, + flags: []string{ + "-max-version", strconv.Itoa(VersionTLS12), + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-curves", strconv.Itoa(int(CurveX25519)), + }, + }) + // CECPQ2 should not crash a TLS < 1.3 client if the server mistakenly // selects it. testCases = append(testCases, testCase{ @@ -11072,6 +11092,24 @@ func addCurveTests() { expectedError: ":WRONG_CURVE:", }) + // CECPQ2b should not crash a TLS < 1.3 client if the server mistakenly + // selects it. + testCases = append(testCases, testCase{ + name: "CECPQ2bNotAcceptedByTLS12Client", + config: Config{ + Bugs: ProtocolBugs{ + SendCurve: CurveCECPQ2b, + }, + }, + flags: []string{ + "-max-version", strconv.Itoa(VersionTLS12), + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-curves", strconv.Itoa(int(CurveX25519)), + }, + shouldFail: true, + expectedError: ":WRONG_CURVE:", + }) + // CECPQ2 should not be offered by default as a client. testCases = append(testCases, testCase{ name: "CECPQ2NotEnabledByDefaultInClients", @@ -11083,6 +11121,17 @@ func addCurveTests() { }, }) + // CECPQ2b should not be offered by default as a client. + testCases = append(testCases, testCase{ + name: "CECPQ2bNotEnabledByDefaultInClients", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + FailIfCECPQ2Offered: true, + }, + }, + }) + // If CECPQ2 is offered, both X25519 and CECPQ2 should have a key-share. testCases = append(testCases, testCase{ name: "NotJustCECPQ2KeyShare", @@ -11115,6 +11164,38 @@ func addCurveTests() { }, }) + // If CECPQ2b is offered, both X25519 and CECPQ2b should have a key-share. + testCases = append(testCases, testCase{ + name: "NotJustCECPQ2bKeyShare", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectedKeyShares: []CurveID{CurveCECPQ2b, CurveX25519}, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-curves", strconv.Itoa(int(CurveX25519)), + "-expect-curve-id", strconv.Itoa(int(CurveCECPQ2b)), + }, + }) + + // ... but only if CECPQ2b is listed first. + testCases = append(testCases, testCase{ + name: "CECPQ2bKeyShareNotIncludedSecond", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectedKeyShares: []CurveID{CurveX25519}, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveX25519)), + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-curve-id", strconv.Itoa(int(CurveX25519)), + }, + }) + // If CECPQ2 is the only configured curve, the key share is sent. testCases = append(testCases, testCase{ name: "JustConfiguringCECPQ2Works", @@ -11130,6 +11211,21 @@ func addCurveTests() { }, }) + // If CECPQ2b is the only configured curve, the key share is sent. + testCases = append(testCases, testCase{ + name: "JustConfiguringCECPQ2bWorks", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectedKeyShares: []CurveID{CurveCECPQ2b}, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-curve-id", strconv.Itoa(int(CurveCECPQ2b)), + }, + }) + // As a server, CECPQ2 is not yet supported by default. testCases = append(testCases, testCase{ testType: serverTest, @@ -11144,6 +11240,21 @@ func addCurveTests() { "-expect-curve-id", strconv.Itoa(int(CurveX25519)), }, }) + + // As a server, CECPQ2b is not yet supported by default. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "CECPQ2bNotEnabledByDefaultForAServer", + config: Config{ + MinVersion: VersionTLS13, + CurvePreferences: []CurveID{CurveCECPQ2b, CurveX25519}, + DefaultCurves: []CurveID{CurveCECPQ2b}, + }, + flags: []string{ + "-server-preference", + "-expect-curve-id", strconv.Itoa(int(CurveX25519)), + }, + }) } func addTLS13RecordTests() { @@ -13756,6 +13867,22 @@ func addTLS13CipherPreferenceTests() { "-curves", strconv.Itoa(int(CurveCECPQ2)), }, }) + + // CECPQ2b prefers 256-bit ciphers but will use AES-128 if there's nothing else. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128Only", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + }, + }) + // When a 256-bit cipher is offered, even if not in first place, it should be // picked. testCases = append(testCases, testCase{ @@ -13790,6 +13917,40 @@ func addTLS13CipherPreferenceTests() { expectedCipher: TLS_AES_128_GCM_SHA256, }) + // When a 256-bit cipher is offered, even if not in first place, it should be + // picked. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES256Preferred", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + }, + expectedCipher: TLS_AES_256_GCM_SHA384, + }) + // ... but when CECPQ2b isn't being used, the client's preference controls. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128PreferredOtherwise", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveX25519)), + }, + expectedCipher: TLS_AES_128_GCM_SHA256, + }) + // Test that CECPQ2 continues to honor AES vs ChaCha20 logic. testCases = append(testCases, testCase{ testType: serverTest, @@ -13825,6 +13986,42 @@ func addTLS13CipherPreferenceTests() { "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), }, }) + + // Test that CECPQ2b continues to honor AES vs ChaCha20 logic. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128-ChaCha20-AES256", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_CHACHA20_POLY1305_SHA256, + TLS_AES_256_GCM_SHA384, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-cipher-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), + "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128-AES256-ChaCha20", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-cipher-aes", strconv.Itoa(int(TLS_AES_256_GCM_SHA384)), + "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), + }, + }) } func addPeekTests() { diff --git a/ssl/test/runner/sike/arith.go b/ssl/test/runner/sike/arith.go new file mode 100644 index 00000000..b0edf4ab --- /dev/null +++ b/ssl/test/runner/sike/arith.go @@ -0,0 +1,416 @@ +package sike + +// Helpers + +// uint128 representation +type uint128 struct { + H, L uint64 +} + +func addc64(cin, a, b uint64) (ret, cout uint64) { + ret = cin + ret = ret + a + if ret < a { + cout = 1 + } + ret = ret + b + if ret < b { + cout = 1 + } + + return +} + +func subc64(bIn, a, b uint64) (ret, bOut uint64) { + tmp := a - bIn + if tmp > a { + bOut = 1 + } + ret = tmp - b + if ret > tmp { + bOut = 1 + } + return +} + +func mul64(a, b uint64) (res uint128) { + var al, bl, ah, bh, albl, albh, ahbl, ahbh uint64 + var res1, res2, res3 uint64 + var carry, maskL, maskH, temp uint64 + + maskL = (^maskL) >> 32 + maskH = ^maskL + + al = a & maskL + ah = a >> 32 + bl = b & maskL + bh = b >> 32 + + albl = al * bl + albh = al * bh + ahbl = ah * bl + ahbh = ah * bh + res.L = albl & maskL + + res1 = albl >> 32 + res2 = ahbl & maskL + res3 = albh & maskL + temp = res1 + res2 + res3 + carry = temp >> 32 + res.L ^= temp << 32 + + res1 = ahbl >> 32 + res2 = albh >> 32 + res3 = ahbh & maskL + temp = res1 + res2 + res3 + carry + res.H = temp & maskL + carry = temp & maskH + res.H ^= (ahbh & maskH) + carry + return +} + +// Fp implementation + +// Compute z = x + y (mod 2*p). +func fpAddRdc(z, x, y *Fp) { + var carry uint64 + + // z=x+y % p503 + for i := 0; i < FP_WORDS; i++ { + z[i], carry = addc64(carry, x[i], y[i]) + } + + // z = z - p503x2 + carry = 0 + for i := 0; i < FP_WORDS; i++ { + z[i], carry = subc64(carry, z[i], p503x2[i]) + } + + // if z<0 add p503x2 back + mask := uint64(0 - carry) + carry = 0 + for i := 0; i < FP_WORDS; i++ { + z[i], carry = addc64(carry, z[i], p503x2[i]&mask) + } +} + +// Compute z = x - y (mod 2*p). +func fpSubRdc(z, x, y *Fp) { + var borrow uint64 + + // z = z - p503x2 + for i := 0; i < FP_WORDS; i++ { + z[i], borrow = subc64(borrow, x[i], y[i]) + } + + // if z<0 add p503x2 back + mask := uint64(0 - borrow) + borrow = 0 + for i := 0; i < FP_WORDS; i++ { + z[i], borrow = addc64(borrow, z[i], p503x2[i]&mask) + } +} + +// Reduce a field element in [0, 2*p) to one in [0,p). +func fpRdcP(x *Fp) { + var borrow, mask uint64 + for i := 0; i < FP_WORDS; i++ { + x[i], borrow = subc64(borrow, x[i], p503[i]) + } + + // Sets all bits if borrow = 1 + mask = 0 - borrow + borrow = 0 + for i := 0; i < FP_WORDS; i++ { + x[i], borrow = addc64(borrow, x[i], p503[i]&mask) + } +} + +// Implementation doesn't actually depend on a prime field. +func fpSwapCond(x, y *Fp, mask uint8) { + if mask != 0 { + var tmp Fp + copy(tmp[:], y[:]) + copy(y[:], x[:]) + copy(x[:], tmp[:]) + } +} + +// Compute z = x * y. +func fpMul(z *FpX2, x, y *Fp) { + var u, v, t uint64 + var carry uint64 + var uv uint128 + + for i := uint64(0); i < FP_WORDS; i++ { + for j := uint64(0); j <= i; j++ { + uv = mul64(x[j], y[i-j]) + v, carry = addc64(0, uv.L, v) + u, carry = addc64(carry, uv.H, u) + t += carry + } + z[i] = v + v = u + u = t + t = 0 + } + + for i := FP_WORDS; i < (2*FP_WORDS)-1; i++ { + for j := i - FP_WORDS + 1; j < FP_WORDS; j++ { + uv = mul64(x[j], y[i-j]) + v, carry = addc64(0, uv.L, v) + u, carry = addc64(carry, uv.H, u) + t += carry + } + z[i] = v + v = u + u = t + t = 0 + } + z[2*FP_WORDS-1] = v +} + +// Perform Montgomery reduction: set z = x R^{-1} (mod 2*p) +// with R=2^512. Destroys the input value. +func fpMontRdc(z *Fp, x *FpX2) { + var carry, t, u, v uint64 + var uv uint128 + var count int + + count = 3 // number of 0 digits in the least significat part of p503 + 1 + + for i := 0; i < FP_WORDS; i++ { + for j := 0; j < i; j++ { + if j < (i - count + 1) { + uv = mul64(z[j], p503p1[i-j]) + v, carry = addc64(0, uv.L, v) + u, carry = addc64(carry, uv.H, u) + t += carry + } + } + v, carry = addc64(0, v, x[i]) + u, carry = addc64(carry, u, 0) + t += carry + + z[i] = v + v = u + u = t + t = 0 + } + + for i := FP_WORDS; i < 2*FP_WORDS-1; i++ { + if count > 0 { + count-- + } + for j := i - FP_WORDS + 1; j < FP_WORDS; j++ { + if j < (FP_WORDS - count) { + uv = mul64(z[j], p503p1[i-j]) + v, carry = addc64(0, uv.L, v) + u, carry = addc64(carry, uv.H, u) + t += carry + } + } + v, carry = addc64(0, v, x[i]) + u, carry = addc64(carry, u, 0) + + t += carry + z[i-FP_WORDS] = v + v = u + u = t + t = 0 + } + v, carry = addc64(0, v, x[2*FP_WORDS-1]) + z[FP_WORDS-1] = v +} + +// Compute z = x + y, without reducing mod p. +func fp2Add(z, x, y *FpX2) { + var carry uint64 + for i := 0; i < 2*FP_WORDS; i++ { + z[i], carry = addc64(carry, x[i], y[i]) + } +} + +// Compute z = x - y, without reducing mod p. +func fp2Sub(z, x, y *FpX2) { + var borrow, mask uint64 + for i := 0; i < 2*FP_WORDS; i++ { + z[i], borrow = subc64(borrow, x[i], y[i]) + } + + // Sets all bits if borrow = 1 + mask = 0 - borrow + borrow = 0 + for i := FP_WORDS; i < 2*FP_WORDS; i++ { + z[i], borrow = addc64(borrow, z[i], p503[i-FP_WORDS]&mask) + } +} + +// Montgomery multiplication. Input values must be already +// in Montgomery domain. +func fpMulRdc(dest, lhs, rhs *Fp) { + a := lhs // = a*R + b := rhs // = b*R + + var ab FpX2 + fpMul(&ab, a, b) // = a*b*R*R + fpMontRdc(dest, &ab) // = a*b*R mod p +} + +// Set dest = x^((p-3)/4). If x is square, this is 1/sqrt(x). +// Uses variation of sliding-window algorithm from with window size +// of 5 and least to most significant bit sliding (left-to-right) +// See HAC 14.85 for general description. +// +// Allowed to overlap x with dest. +// All values in Montgomery domains +func p34(dest, x *Fp) { + + // Set dest = x^(2^k), for k >= 1, by repeated squarings. + pow2k := func(dest, x *Fp, k uint8) { + fpMulRdc(dest, x, x) + for i := uint8(1); i < k; i++ { + fpMulRdc(dest, dest, dest) + } + } + // Sliding-window strategy computed with etc/scripts/sliding_window_strat_calc.py + // + // This performs sum(powStrategy) + 1 squarings and len(lookup) + len(mulStrategy) + // multiplications. + powStrategy := []uint8{1, 12, 5, 5, 2, 7, 11, 3, 8, 4, 11, 4, 7, 5, 6, 3, 7, 5, 7, 2, 12, 5, 6, 4, 6, 8, 6, 4, 7, 5, 5, 8, 5, 8, 5, 5, 8, 9, 3, 6, 2, 10, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3} + mulStrategy := []uint8{0, 12, 11, 10, 0, 1, 8, 3, 7, 1, 8, 3, 6, 7, 14, 2, 14, 14, 9, 0, 13, 9, 15, 5, 12, 7, 13, 7, 15, 6, 7, 9, 0, 5, 7, 6, 8, 8, 3, 7, 0, 10, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3} + + // Precompute lookup table of odd multiples of x for window + // size k=5. + lookup := [16]Fp{} + var xx Fp + fpMulRdc(&xx, x, x) + lookup[0] = *x + for i := 1; i < 16; i++ { + fpMulRdc(&lookup[i], &lookup[i-1], &xx) + } + + // Now lookup = {x, x^3, x^5, ... } + // so that lookup[i] = x^{2*i + 1} + // so that lookup[k/2] = x^k, for odd k + *dest = lookup[mulStrategy[0]] + for i := uint8(1); i < uint8(len(powStrategy)); i++ { + pow2k(dest, dest, powStrategy[i]) + fpMulRdc(dest, dest, &lookup[mulStrategy[i]]) + } +} + +func add(dest, lhs, rhs *Fp2) { + fpAddRdc(&dest.A, &lhs.A, &rhs.A) + fpAddRdc(&dest.B, &lhs.B, &rhs.B) +} + +func sub(dest, lhs, rhs *Fp2) { + fpSubRdc(&dest.A, &lhs.A, &rhs.A) + fpSubRdc(&dest.B, &lhs.B, &rhs.B) +} + +func mul(dest, lhs, rhs *Fp2) { + // Let (a,b,c,d) = (lhs.a,lhs.b,rhs.a,rhs.b). + a := &lhs.A + b := &lhs.B + c := &rhs.A + d := &rhs.B + + // We want to compute + // + // (a + bi)*(c + di) = (a*c - b*d) + (a*d + b*c)i + // + // Use Karatsuba's trick: note that + // + // (b - a)*(c - d) = (b*c + a*d) - a*c - b*d + // + // so (a*d + b*c) = (b-a)*(c-d) + a*c + b*d. + + var ac, bd FpX2 + fpMul(&ac, a, c) // = a*c*R*R + fpMul(&bd, b, d) // = b*d*R*R + + var b_minus_a, c_minus_d Fp + fpSubRdc(&b_minus_a, b, a) // = (b-a)*R + fpSubRdc(&c_minus_d, c, d) // = (c-d)*R + + var ad_plus_bc FpX2 + fpMul(&ad_plus_bc, &b_minus_a, &c_minus_d) // = (b-a)*(c-d)*R*R + fp2Add(&ad_plus_bc, &ad_plus_bc, &ac) // = ((b-a)*(c-d) + a*c)*R*R + fp2Add(&ad_plus_bc, &ad_plus_bc, &bd) // = ((b-a)*(c-d) + a*c + b*d)*R*R + + fpMontRdc(&dest.B, &ad_plus_bc) // = (a*d + b*c)*R mod p + + var ac_minus_bd FpX2 + fp2Sub(&ac_minus_bd, &ac, &bd) // = (a*c - b*d)*R*R + fpMontRdc(&dest.A, &ac_minus_bd) // = (a*c - b*d)*R mod p +} + +func inv(dest, x *Fp2) { + var a2PlusB2 Fp + var asq, bsq FpX2 + var ac FpX2 + var minusB Fp + var minusBC FpX2 + + a := &x.A + b := &x.B + + // We want to compute + // + // 1 1 (a - bi) (a - bi) + // -------- = -------- -------- = ----------- + // (a + bi) (a + bi) (a - bi) (a^2 + b^2) + // + // Letting c = 1/(a^2 + b^2), this is + // + // 1/(a+bi) = a*c - b*ci. + + fpMul(&asq, a, a) // = a*a*R*R + fpMul(&bsq, b, b) // = b*b*R*R + fp2Add(&asq, &asq, &bsq) // = (a^2 + b^2)*R*R + fpMontRdc(&a2PlusB2, &asq) // = (a^2 + b^2)*R mod p + // Now a2PlusB2 = a^2 + b^2 + + inv := a2PlusB2 + fpMulRdc(&inv, &a2PlusB2, &a2PlusB2) + p34(&inv, &inv) + fpMulRdc(&inv, &inv, &inv) + fpMulRdc(&inv, &inv, &a2PlusB2) + + fpMul(&ac, a, &inv) + fpMontRdc(&dest.A, &ac) + + fpSubRdc(&minusB, &minusB, b) + fpMul(&minusBC, &minusB, &inv) + fpMontRdc(&dest.B, &minusBC) +} + +func sqr(dest, x *Fp2) { + var a2, aPlusB, aMinusB Fp + var a2MinB2, ab2 FpX2 + + a := &x.A + b := &x.B + + // (a + bi)*(a + bi) = (a^2 - b^2) + 2abi. + fpAddRdc(&a2, a, a) // = a*R + a*R = 2*a*R + fpAddRdc(&aPlusB, a, b) // = a*R + b*R = (a+b)*R + fpSubRdc(&aMinusB, a, b) // = a*R - b*R = (a-b)*R + fpMul(&a2MinB2, &aPlusB, &aMinusB) // = (a+b)*(a-b)*R*R = (a^2 - b^2)*R*R + fpMul(&ab2, &a2, b) // = 2*a*b*R*R + fpMontRdc(&dest.A, &a2MinB2) // = (a^2 - b^2)*R mod p + fpMontRdc(&dest.B, &ab2) // = 2*a*b*R mod p +} + +// In case choice == 1, performs following swap in constant time: +// xPx <-> xQx +// xPz <-> xQz +// Otherwise returns xPx, xPz, xQx, xQz unchanged +func condSwap(xPx, xPz, xQx, xQz *Fp2, choice uint8) { + fpSwapCond(&xPx.A, &xQx.A, choice) + fpSwapCond(&xPx.B, &xQx.B, choice) + fpSwapCond(&xPz.A, &xQz.A, choice) + fpSwapCond(&xPz.B, &xQz.B, choice) +} diff --git a/ssl/test/runner/sike/consts.go b/ssl/test/runner/sike/consts.go new file mode 100644 index 00000000..e9e598be --- /dev/null +++ b/ssl/test/runner/sike/consts.go @@ -0,0 +1,287 @@ +package sike + +// I keep it bool in order to be able to apply logical NOT +type KeyVariant uint + +// Representation of an element of the base field F_p. +// +// No particular meaning is assigned to the representation -- it could represent +// an element in Montgomery form, or not. Tracking the meaning of the field +// element is left to higher types. +type Fp [FP_WORDS]uint64 + +// Represents an intermediate product of two elements of the base field F_p. +type FpX2 [2 * FP_WORDS]uint64 + +// Represents an element of the extended field Fp^2 = Fp(x+i) +type Fp2 struct { + A Fp + B Fp +} + +type DomainParams struct { + // P, Q and R=P-Q base points + Affine_P, Affine_Q, Affine_R Fp2 + // Size of a compuatation strategy for x-torsion group + IsogenyStrategy []uint32 + // Max size of secret key for x-torsion group + SecretBitLen uint + // Max size of secret key for x-torsion group + SecretByteLen uint +} + +type SidhParams struct { + Id uint8 + // Bytelen of P + Bytelen int + // The public key size, in bytes. + PublicKeySize int + // The shared secret size, in bytes. + SharedSecretSize int + // 2- and 3-torsion group parameter definitions + A, B DomainParams + // Precomputed identity element in the Fp2 in Montgomery domain + OneFp2 Fp2 + // Precomputed 1/2 in the Fp2 in Montgomery domain + HalfFp2 Fp2 + // Length of SIKE secret message. Must be one of {24,32,40}, + // depending on size of prime field used (see [SIKE], 1.4 and 5.1) + MsgLen int + // Length of SIKE ephemeral KEM key (see [SIKE], 1.4 and 5.1) + KemSize int +} + +// Stores curve projective parameters equivalent to A/C. Meaning of the +// values depends on the context. When working with isogenies over +// subgroup that are powers of: +// * three then (A:C) ~ (A+2C:A-2C) +// * four then (A:C) ~ (A+2C: 4C) +// See Appendix A of SIKE for more details +type CurveCoefficientsEquiv struct { + A Fp2 + C Fp2 +} + +// A point on the projective line P^1(F_{p^2}). +// +// This represents a point on the Kummer line of a Montgomery curve. The +// curve is specified by a ProjectiveCurveParameters struct. +type ProjectivePoint struct { + X Fp2 + Z Fp2 +} + +// Base type for public and private key. Used mainly to carry domain +// parameters. +type key struct { + // Domain parameters of the algorithm to be used with a key + params *SidhParams + // Flag indicates wether corresponds to 2-, 3-torsion group or SIKE + keyVariant KeyVariant +} + +// Defines operations on private key +type PrivateKey struct { + key + // Secret key + Scalar []byte + // Used only by KEM + S []byte +} + +// Defines operations on public key +type PublicKey struct { + key + affine_xP Fp2 + affine_xQ Fp2 + affine_xQmP Fp2 +} + +// A point on the projective line P^1(F_{p^2}). +// +// This is used to work projectively with the curve coefficients. +type ProjectiveCurveParameters struct { + A Fp2 + C Fp2 +} + +const ( + // First 2 bits identify SIDH variant third bit indicates + // wether key is a SIKE variant (set) or SIDH (not set) + + // 001 - SIDH: corresponds to 2-torsion group + KeyVariant_SIDH_A KeyVariant = 1 << 0 + // 010 - SIDH: corresponds to 3-torsion group + KeyVariant_SIDH_B = 1 << 1 + // 110 - SIKE + KeyVariant_SIKE = 1<<2 | KeyVariant_SIDH_B + // Number of uint64 limbs used to store field element + FP_WORDS = 8 +) + +// Used internally by this package +// ------------------------------- + +var p503 = Fp{ + 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xABFFFFFFFFFFFFFF, + 0x13085BDA2211E7A0, 0x1B9BF6C87B7E7DAF, 0x6045C6BDDA77A4D0, 0x004066F541811E1E, +} + +// 2*503 +var p503x2 = Fp{ + 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x57FFFFFFFFFFFFFF, + 0x2610B7B44423CF41, 0x3737ED90F6FCFB5E, 0xC08B8D7BB4EF49A0, 0x0080CDEA83023C3C, +} + +// p503 + 1 +var p503p1 = Fp{ + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xAC00000000000000, + 0x13085BDA2211E7A0, 0x1B9BF6C87B7E7DAF, 0x6045C6BDDA77A4D0, 0x004066F541811E1E, +} + +// R^2=(2^512)^2 mod p +var p503R2 = Fp{ + 0x5289A0CF641D011F, 0x9B88257189FED2B9, 0xA3B365D58DC8F17A, 0x5BC57AB6EFF168EC, + 0x9E51998BD84D4423, 0xBF8999CBAC3B5695, 0x46E9127BCE14CDB6, 0x003F6CFCE8B81771, +} + +// p503 + 1 left-shifted by 8, assuming little endianness +var p503p1s8 = Fp{ + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x085BDA2211E7A0AC, 0x9BF6C87B7E7DAF13, 0x45C6BDDA77A4D01B, 0x4066F541811E1E60, +} + +// 1*R mod p +var P503_OneFp2 = Fp2{ + A: Fp{ + 0x00000000000003F9, 0x0000000000000000, 0x0000000000000000, 0xB400000000000000, + 0x63CB1A6EA6DED2B4, 0x51689D8D667EB37D, 0x8ACD77C71AB24142, 0x0026FBAEC60F5953}, +} + +// 1/2 * R mod p +var P503_HalfFp2 = Fp2{ + A: Fp{ + 0x00000000000001FC, 0x0000000000000000, 0x0000000000000000, 0xB000000000000000, + 0x3B69BB2464785D2A, 0x36824A2AF0FE9896, 0xF5899F427A94F309, 0x0033B15203C83BB8}, +} + +var Params SidhParams + +func init() { + Params = SidhParams{ + // SIDH public key byte size. + PublicKeySize: 378, + // SIDH shared secret byte size. + SharedSecretSize: 126, + A: DomainParams{ + // The x-coordinate of PA + Affine_P: Fp2{ + A: Fp{ + 0xE7EF4AA786D855AF, 0xED5758F03EB34D3B, 0x09AE172535A86AA9, 0x237B9CC07D622723, + 0xE3A284CBA4E7932D, 0x27481D9176C5E63F, 0x6A323FF55C6E71BF, 0x002ECC31A6FB8773, + }, + B: Fp{ + 0x64D02E4E90A620B8, 0xDAB8128537D4B9F1, 0x4BADF77B8A228F98, 0x0F5DBDF9D1FB7D1B, + 0xBEC4DB288E1A0DCC, 0xE76A8665E80675DB, 0x6D6F252E12929463, 0x003188BD1463FACC, + }, + }, + // The x-coordinate of QA + Affine_Q: Fp2{ + A: Fp{ + 0xB79D41025DE85D56, 0x0B867DA9DF169686, 0x740E5368021C827D, 0x20615D72157BF25C, + 0xFF1590013C9B9F5B, 0xC884DCADE8C16CEA, 0xEBD05E53BF724E01, 0x0032FEF8FDA5748C, + }, + B: Fp{ + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + }, + }, + // The x-coordinate of RA = PA-QA + Affine_R: Fp2{ + A: Fp{ + 0x12E2E849AA0A8006, 0x41CF47008635A1E8, 0x9CD720A70798AED7, 0x42A820B42FCF04CF, + 0x7BF9BAD32AAE88B1, 0xF619127A54090BBE, 0x1CB10D8F56408EAA, 0x001D6B54C3C0EDEB, + }, + B: Fp{ + 0x34DB54931CBAAC36, 0x420A18CB8DD5F0C4, 0x32008C1A48C0F44D, 0x3B3BA772B1CFD44D, + 0xA74B058FDAF13515, 0x095FC9CA7EEC17B4, 0x448E829D28F120F8, 0x00261EC3ED16A489, + }, + }, + // Max size of secret key for 2-torsion group, corresponds to 2^e2 - 1 + SecretBitLen: 250, + // SecretBitLen in bytes. + SecretByteLen: uint((250 + 7) / 8), + // 2-torsion group computation strategy + IsogenyStrategy: []uint32{ + 0x3D, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, + 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x01, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, + 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, + 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x1D, 0x10, 0x08, 0x04, 0x02, 0x01, + 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x0D, 0x08, + 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, + 0x05, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01}, + }, + B: DomainParams{ + // The x-coordinate of PB + Affine_P: Fp2{ + A: Fp{ + 0x7EDE37F4FA0BC727, 0xF7F8EC5C8598941C, 0xD15519B516B5F5C8, 0xF6D5AC9B87A36282, + 0x7B19F105B30E952E, 0x13BD8B2025B4EBEE, 0x7B96D27F4EC579A2, 0x00140850CAB7E5DE, + }, + B: Fp{ + 0x7764909DAE7B7B2D, 0x578ABB16284911AB, 0x76E2BFD146A6BF4D, 0x4824044B23AA02F0, + 0x1105048912A321F3, 0xB8A2E482CF0F10C1, 0x42FF7D0BE2152085, 0x0018E599C5223352, + }, + }, + // The x-coordinate of QB + Affine_Q: Fp2{ + A: Fp{ + 0x4256C520FB388820, 0x744FD7C3BAAF0A13, 0x4B6A2DDDB12CBCB8, 0xE46826E27F427DF8, + 0xFE4A663CD505A61B, 0xD6B3A1BAF025C695, 0x7C3BB62B8FCC00BD, 0x003AFDDE4A35746C, + }, + B: Fp{ + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + }, + }, + // The x-coordinate of RB = PB - QB + Affine_R: Fp2{ + A: Fp{ + 0x75601CD1E6C0DFCB, 0x1A9007239B58F93E, 0xC1F1BE80C62107AC, 0x7F513B898F29FF08, + 0xEA0BEDFF43E1F7B2, 0x2C6D94018CBAE6D0, 0x3A430D31BCD84672, 0x000D26892ECCFE83, + }, + B: Fp{ + 0x1119D62AEA3007A1, 0xE3702AA4E04BAE1B, 0x9AB96F7D59F990E7, 0xF58440E8B43319C0, + 0xAF8134BEE1489775, 0xE7F7774E905192AA, 0xF54AE09308E98039, 0x001EF7A041A86112, + }, + }, + // Size of secret key for 3-torsion group, corresponds to log_2(3^e3) - 1. + SecretBitLen: 252, + // SecretBitLen in bytes. + SecretByteLen: uint((252 + 7) / 8), + // 3-torsion group computation strategy + IsogenyStrategy: []uint32{ + 0x47, 0x26, 0x15, 0x0D, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x01, 0x05, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, + 0x01, 0x01, 0x01, 0x09, 0x05, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, + 0x01, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x11, 0x09, 0x05, 0x03, 0x02, + 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, + 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, + 0x01, 0x02, 0x01, 0x01, 0x21, 0x11, 0x09, 0x05, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, + 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, + 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, + 0x01, 0x02, 0x01, 0x01}, + }, + OneFp2: P503_OneFp2, + HalfFp2: P503_HalfFp2, + MsgLen: 24, + // SIKEp503 provides 128 bit of classical security ([SIKE], 5.1) + KemSize: 16, + // ceil(503+7/8) + Bytelen: 63, + } +} diff --git a/ssl/test/runner/sike/curve.go b/ssl/test/runner/sike/curve.go new file mode 100644 index 00000000..36cac527 --- /dev/null +++ b/ssl/test/runner/sike/curve.go @@ -0,0 +1,408 @@ +package sike + +// Interface for working with isogenies. +type isogeny interface { + // Given a torsion point on a curve computes isogenous curve. + // Returns curve coefficients (A:C), so that E_(A/C) = E_(A/C)/

, + // where P is a provided projective point. Sets also isogeny constants + // that are needed for isogeny evaluation. + GenerateCurve(*ProjectivePoint) CurveCoefficientsEquiv + // Evaluates isogeny at caller provided point. Requires isogeny curve constants + // to be earlier computed by GenerateCurve. + EvaluatePoint(*ProjectivePoint) ProjectivePoint +} + +// Stores isogeny 3 curve constants +type isogeny3 struct { + K1 Fp2 + K2 Fp2 +} + +// Stores isogeny 4 curve constants +type isogeny4 struct { + isogeny3 + K3 Fp2 +} + +// Constructs isogeny3 objects +func NewIsogeny3() isogeny { + return &isogeny3{} +} + +// Constructs isogeny4 objects +func NewIsogeny4() isogeny { + return &isogeny4{} +} + +// Helper function for RightToLeftLadder(). Returns A+2C / 4. +func calcAplus2Over4(cparams *ProjectiveCurveParameters) (ret Fp2) { + var tmp Fp2 + + // 2C + add(&tmp, &cparams.C, &cparams.C) + // A+2C + add(&ret, &cparams.A, &tmp) + // 1/4C + add(&tmp, &tmp, &tmp) + inv(&tmp, &tmp) + // A+2C/4C + mul(&ret, &ret, &tmp) + return +} + +// Converts values in x.A and x.B to Montgomery domain +// x.A = x.A * R mod p +// x.B = x.B * R mod p +// Performs v = v*R^2*R^(-1) mod p, for both x.A and x.B +func toMontDomain(x *Fp2) { + var aRR FpX2 + + // convert to montgomery domain + fpMul(&aRR, &x.A, &p503R2) // = a*R*R + fpMontRdc(&x.A, &aRR) // = a*R mod p + fpMul(&aRR, &x.B, &p503R2) + fpMontRdc(&x.B, &aRR) +} + +// Converts values in x.A and x.B from Montgomery domain +// a = x.A mod p +// b = x.B mod p +// +// After returning from the call x is not modified. +func fromMontDomain(x *Fp2, out *Fp2) { + var aR FpX2 + + // convert from montgomery domain + copy(aR[:], x.A[:]) + fpMontRdc(&out.A, &aR) // = a mod p in [0, 2p) + fpRdcP(&out.A) // = a mod p in [0, p) + for i := range aR { + aR[i] = 0 + } + copy(aR[:], x.B[:]) + fpMontRdc(&out.B, &aR) + fpRdcP(&out.B) +} + +// Computes j-invariant for a curve y2=x3+A/Cx+x with A,C in F_(p^2). Result +// is returned in 'j'. Implementation corresponds to Algorithm 9 from SIKE. +func Jinvariant(cparams *ProjectiveCurveParameters, j *Fp2) { + var t0, t1 Fp2 + + sqr(j, &cparams.A) // j = A^2 + sqr(&t1, &cparams.C) // t1 = C^2 + add(&t0, &t1, &t1) // t0 = t1 + t1 + sub(&t0, j, &t0) // t0 = j - t0 + sub(&t0, &t0, &t1) // t0 = t0 - t1 + sub(j, &t0, &t1) // t0 = t0 - t1 + sqr(&t1, &t1) // t1 = t1^2 + mul(j, j, &t1) // j = j * t1 + add(&t0, &t0, &t0) // t0 = t0 + t0 + add(&t0, &t0, &t0) // t0 = t0 + t0 + sqr(&t1, &t0) // t1 = t0^2 + mul(&t0, &t0, &t1) // t0 = t0 * t1 + add(&t0, &t0, &t0) // t0 = t0 + t0 + add(&t0, &t0, &t0) // t0 = t0 + t0 + inv(j, j) // j = 1/j + mul(j, &t0, j) // j = t0 * j +} + +// Given affine points x(P), x(Q) and x(Q-P) in a extension field F_{p^2}, function +// recorvers projective coordinate A of a curve. This is Algorithm 10 from SIKE. +func RecoverCoordinateA(curve *ProjectiveCurveParameters, xp, xq, xr *Fp2) { + var t0, t1 Fp2 + + add(&t1, xp, xq) // t1 = Xp + Xq + mul(&t0, xp, xq) // t0 = Xp * Xq + mul(&curve.A, xr, &t1) // A = X(q-p) * t1 + add(&curve.A, &curve.A, &t0) // A = A + t0 + mul(&t0, &t0, xr) // t0 = t0 * X(q-p) + sub(&curve.A, &curve.A, &Params.OneFp2) // A = A - 1 + add(&t0, &t0, &t0) // t0 = t0 + t0 + add(&t1, &t1, xr) // t1 = t1 + X(q-p) + add(&t0, &t0, &t0) // t0 = t0 + t0 + sqr(&curve.A, &curve.A) // A = A^2 + inv(&t0, &t0) // t0 = 1/t0 + mul(&curve.A, &curve.A, &t0) // A = A * t0 + sub(&curve.A, &curve.A, &t1) // A = A - t1 +} + +// Computes equivalence (A:C) ~ (A+2C : A-2C) +func CalcCurveParamsEquiv3(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv { + var coef CurveCoefficientsEquiv + var c2 Fp2 + + add(&c2, &cparams.C, &cparams.C) + // A24p = A+2*C + add(&coef.A, &cparams.A, &c2) + // A24m = A-2*C + sub(&coef.C, &cparams.A, &c2) + return coef +} + +// Computes equivalence (A:C) ~ (A+2C : 4C) +func CalcCurveParamsEquiv4(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv { + var coefEq CurveCoefficientsEquiv + + add(&coefEq.C, &cparams.C, &cparams.C) + // A24p = A+2C + add(&coefEq.A, &cparams.A, &coefEq.C) + // C24 = 4*C + add(&coefEq.C, &coefEq.C, &coefEq.C) + return coefEq +} + +// Recovers (A:C) curve parameters from projectively equivalent (A+2C:A-2C). +func RecoverCurveCoefficients3(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) { + add(&cparams.A, &coefEq.A, &coefEq.C) + // cparams.A = 2*(A+2C+A-2C) = 4A + add(&cparams.A, &cparams.A, &cparams.A) + // cparams.C = (A+2C-A+2C) = 4C + sub(&cparams.C, &coefEq.A, &coefEq.C) + return +} + +// Recovers (A:C) curve parameters from projectively equivalent (A+2C:4C). +func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) { + // cparams.C = (4C)*1/2=2C + mul(&cparams.C, &coefEq.C, &Params.HalfFp2) + // cparams.A = A+2C - 2C = A + sub(&cparams.A, &coefEq.A, &cparams.C) + // cparams.C = 2C * 1/2 = C + mul(&cparams.C, &cparams.C, &Params.HalfFp2) + return +} + +// Combined coordinate doubling and differential addition. Takes projective points +// P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E. +// Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE +func xDbladd(P, Q, QmP *ProjectivePoint, a24 *Fp2) (dblP, PaQ ProjectivePoint) { + var t0, t1, t2 Fp2 + xQmP, zQmP := &QmP.X, &QmP.Z + xPaQ, zPaQ := &PaQ.X, &PaQ.Z + x2P, z2P := &dblP.X, &dblP.Z + xP, zP := &P.X, &P.Z + xQ, zQ := &Q.X, &Q.Z + + add(&t0, xP, zP) // t0 = Xp+Zp + sub(&t1, xP, zP) // t1 = Xp-Zp + sqr(x2P, &t0) // 2P.X = t0^2 + sub(&t2, xQ, zQ) // t2 = Xq-Zq + add(xPaQ, xQ, zQ) // Xp+q = Xq+Zq + mul(&t0, &t0, &t2) // t0 = t0 * t2 + mul(z2P, &t1, &t1) // 2P.Z = t1 * t1 + mul(&t1, &t1, xPaQ) // t1 = t1 * Xp+q + sub(&t2, x2P, z2P) // t2 = 2P.X - 2P.Z + mul(x2P, x2P, z2P) // 2P.X = 2P.X * 2P.Z + mul(xPaQ, a24, &t2) // Xp+q = A24 * t2 + sub(zPaQ, &t0, &t1) // Zp+q = t0 - t1 + add(z2P, xPaQ, z2P) // 2P.Z = Xp+q + 2P.Z + add(xPaQ, &t0, &t1) // Xp+q = t0 + t1 + mul(z2P, z2P, &t2) // 2P.Z = 2P.Z * t2 + sqr(zPaQ, zPaQ) // Zp+q = Zp+q ^ 2 + sqr(xPaQ, xPaQ) // Xp+q = Xp+q ^ 2 + mul(zPaQ, xQmP, zPaQ) // Zp+q = Xq-p * Zp+q + mul(xPaQ, zQmP, xPaQ) // Xp+q = Zq-p * Xp+q + return +} + +// Given the curve parameters, xP = x(P), computes xP = x([2^k]P) +// Safe to overlap xP, x2P. +func Pow2k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) { + var t0, t1 Fp2 + + x, z := &xP.X, &xP.Z + for i := uint32(0); i < k; i++ { + sub(&t0, x, z) // t0 = Xp - Zp + add(&t1, x, z) // t1 = Xp + Zp + sqr(&t0, &t0) // t0 = t0 ^ 2 + sqr(&t1, &t1) // t1 = t1 ^ 2 + mul(z, ¶ms.C, &t0) // Z2p = C24 * t0 + mul(x, z, &t1) // X2p = Z2p * t1 + sub(&t1, &t1, &t0) // t1 = t1 - t0 + mul(&t0, ¶ms.A, &t1) // t0 = A24+ * t1 + add(z, z, &t0) // Z2p = Z2p + t0 + mul(z, z, &t1) // Zp = Z2p * t1 + } +} + +// Given the curve parameters, xP = x(P), and k >= 0, compute xP = x([3^k]P). +// +// Safe to overlap xP, xR. +func Pow3k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) { + var t0, t1, t2, t3, t4, t5, t6 Fp2 + + x, z := &xP.X, &xP.Z + for i := uint32(0); i < k; i++ { + sub(&t0, x, z) // t0 = Xp - Zp + sqr(&t2, &t0) // t2 = t0^2 + add(&t1, x, z) // t1 = Xp + Zp + sqr(&t3, &t1) // t3 = t1^2 + add(&t4, &t1, &t0) // t4 = t1 + t0 + sub(&t0, &t1, &t0) // t0 = t1 - t0 + sqr(&t1, &t4) // t1 = t4^2 + sub(&t1, &t1, &t3) // t1 = t1 - t3 + sub(&t1, &t1, &t2) // t1 = t1 - t2 + mul(&t5, &t3, ¶ms.A) // t5 = t3 * A24+ + mul(&t3, &t3, &t5) // t3 = t5 * t3 + mul(&t6, &t2, ¶ms.C) // t6 = t2 * A24- + mul(&t2, &t2, &t6) // t2 = t2 * t6 + sub(&t3, &t2, &t3) // t3 = t2 - t3 + sub(&t2, &t5, &t6) // t2 = t5 - t6 + mul(&t1, &t2, &t1) // t1 = t2 * t1 + add(&t2, &t3, &t1) // t2 = t3 + t1 + sqr(&t2, &t2) // t2 = t2^2 + mul(x, &t2, &t4) // X3p = t2 * t4 + sub(&t1, &t3, &t1) // t1 = t3 - t1 + sqr(&t1, &t1) // t1 = t1^2 + mul(z, &t1, &t0) // Z3p = t1 * t0 + } +} + +// Set (y1, y2, y3) = (1/x1, 1/x2, 1/x3). +// +// All xi, yi must be distinct. +func Fp2Batch3Inv(x1, x2, x3, y1, y2, y3 *Fp2) { + var x1x2, t Fp2 + + mul(&x1x2, x1, x2) // x1*x2 + mul(&t, &x1x2, x3) // 1/(x1*x2*x3) + inv(&t, &t) + mul(y1, &t, x2) // 1/x1 + mul(y1, y1, x3) + mul(y2, &t, x1) // 1/x2 + mul(y2, y2, x3) + mul(y3, &t, &x1x2) // 1/x3 +} + +// ScalarMul3Pt is a right-to-left point multiplication that given the +// x-coordinate of P, Q and P-Q calculates the x-coordinate of R=Q+[scalar]P. +// nbits must be smaller or equal to len(scalar). +func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint, scalar []uint8) ProjectivePoint { + var R0, R2, R1 ProjectivePoint + aPlus2Over4 := calcAplus2Over4(cparams) + R1 = *P + R2 = *PmQ + R0 = *Q + + // Iterate over the bits of the scalar, bottom to top + prevBit := uint8(0) + for i := uint(0); i < nbits; i++ { + bit := (scalar[i>>3] >> (i & 7) & 1) + swap := prevBit ^ bit + prevBit = bit + condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap) + R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4) + } + condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit) + return R1 +} + +// Given a three-torsion point p = x(PB) on the curve E_(A:C), construct the +// three-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). +// +// Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C +// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ +// * isogeny phi with constants in F_p^2 +func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { + var t0, t1, t2, t3, t4 Fp2 + var coefEq CurveCoefficientsEquiv + var K1, K2 = &phi.K1, &phi.K2 + + sub(K1, &p.X, &p.Z) // K1 = XP3 - ZP3 + sqr(&t0, K1) // t0 = K1^2 + add(K2, &p.X, &p.Z) // K2 = XP3 + ZP3 + sqr(&t1, K2) // t1 = K2^2 + add(&t2, &t0, &t1) // t2 = t0 + t1 + add(&t3, K1, K2) // t3 = K1 + K2 + sqr(&t3, &t3) // t3 = t3^2 + sub(&t3, &t3, &t2) // t3 = t3 - t2 + add(&t2, &t1, &t3) // t2 = t1 + t3 + add(&t3, &t3, &t0) // t3 = t3 + t0 + add(&t4, &t3, &t0) // t4 = t3 + t0 + add(&t4, &t4, &t4) // t4 = t4 + t4 + add(&t4, &t1, &t4) // t4 = t1 + t4 + mul(&coefEq.C, &t2, &t4) // A24m = t2 * t4 + add(&t4, &t1, &t2) // t4 = t1 + t2 + add(&t4, &t4, &t4) // t4 = t4 + t4 + add(&t4, &t0, &t4) // t4 = t0 + t4 + mul(&t4, &t3, &t4) // t4 = t3 * t4 + sub(&t0, &t4, &coefEq.C) // t0 = t4 - A24m + add(&coefEq.A, &coefEq.C, &t0) // A24p = A24m + t0 + return coefEq +} + +// Given a 3-isogeny phi and a point pB = x(PB), compute x(QB), the x-coordinate +// of the image QB = phi(PB) of PB under phi : E_(A:C) -> E_(A':C'). +// +// The output xQ = x(Q) is then a point on the curve E_(A':C'); the curve +// parameters are returned by the GenerateCurve function used to construct phi. +func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) ProjectivePoint { + var t0, t1, t2 Fp2 + var q ProjectivePoint + var K1, K2 = &phi.K1, &phi.K2 + var px, pz = &p.X, &p.Z + + add(&t0, px, pz) // t0 = XQ + ZQ + sub(&t1, px, pz) // t1 = XQ - ZQ + mul(&t0, K1, &t0) // t2 = K1 * t0 + mul(&t1, K2, &t1) // t1 = K2 * t1 + add(&t2, &t0, &t1) // t2 = t0 + t1 + sub(&t0, &t1, &t0) // t0 = t1 - t0 + sqr(&t2, &t2) // t2 = t2 ^ 2 + sqr(&t0, &t0) // t0 = t0 ^ 2 + mul(&q.X, px, &t2) // XQ'= XQ * t2 + mul(&q.Z, pz, &t0) // ZQ'= ZQ * t0 + return q +} + +// Given a four-torsion point p = x(PB) on the curve E_(A:C), construct the +// four-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). +// +// Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C +// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ +// * isogeny phi with constants in F_p^2 +func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { + var coefEq CurveCoefficientsEquiv + var xp4, zp4 = &p.X, &p.Z + var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3 + + sub(K2, xp4, zp4) + add(K3, xp4, zp4) + sqr(K1, zp4) + add(K1, K1, K1) + sqr(&coefEq.C, K1) + add(K1, K1, K1) + sqr(&coefEq.A, xp4) + add(&coefEq.A, &coefEq.A, &coefEq.A) + sqr(&coefEq.A, &coefEq.A) + return coefEq +} + +// Given a 4-isogeny phi and a point xP = x(P), compute x(Q), the x-coordinate +// of the image Q = phi(P) of P under phi : E_(A:C) -> E_(A':C'). +// +// Input: isogeny returned by GenerateCurve and point q=(Qx,Qz) from E0_A/C +// Output: Corresponding point q from E1_A'/C', where E1 is 4-isogenous to E0 +func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) ProjectivePoint { + var t0, t1 Fp2 + var q = *p + var xq, zq = &q.X, &q.Z + var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3 + + add(&t0, xq, zq) + sub(&t1, xq, zq) + mul(xq, &t0, K2) + mul(zq, &t1, K3) + mul(&t0, &t0, &t1) + mul(&t0, &t0, K1) + add(&t1, xq, zq) + sub(zq, xq, zq) + sqr(&t1, &t1) + sqr(zq, zq) + add(xq, &t0, &t1) + sub(&t0, zq, &t0) + mul(xq, xq, &t1) + mul(zq, zq, &t0) + return q +} diff --git a/ssl/test/runner/sike/sike.go b/ssl/test/runner/sike/sike.go new file mode 100644 index 00000000..2af54ce2 --- /dev/null +++ b/ssl/test/runner/sike/sike.go @@ -0,0 +1,695 @@ +package sike + +import ( + "crypto/hmac" + "crypto/sha256" + "crypto/subtle" + "errors" + "io" +) + +// Constants used for cSHAKE customization +// Those values are different than in [SIKE] - they are encoded on 16bits. This is +// done in order for implementation to be compatible with [REF] and test vectors. +var G = []byte{0x00, 0x00} +var H = []byte{0x01, 0x00} +var F = []byte{0x02, 0x00} + +// Generates HMAC-SHA256 sum +func hashMac(out, in, S []byte) { + h := hmac.New(sha256.New, in) + h.Write(S) + copy(out, h.Sum(nil)) +} + +// Zeroize Fp2 +func zeroize(fp *Fp2) { + // Zeroizing in 2 seperated loops tells compiler to + // use fast runtime.memclr() + for i := range fp.A { + fp.A[i] = 0 + } + for i := range fp.B { + fp.B[i] = 0 + } +} + +// Convert the input to wire format. +// +// The output byte slice must be at least 2*bytelen(p) bytes long. +func convFp2ToBytes(output []byte, fp2 *Fp2) { + if len(output) < 2*Params.Bytelen { + panic("output byte slice too short") + } + var a Fp2 + fromMontDomain(fp2, &a) + + // convert to bytes in little endian form + for i := 0; i < Params.Bytelen; i++ { + // set i = j*8 + k + tmp := i / 8 + k := uint64(i % 8) + output[i] = byte(a.A[tmp] >> (8 * k)) + output[i+Params.Bytelen] = byte(a.B[tmp] >> (8 * k)) + } +} + +// Read 2*bytelen(p) bytes into the given ExtensionFieldElement. +// +// It is an error to call this function if the input byte slice is less than 2*bytelen(p) bytes long. +func convBytesToFp2(fp2 *Fp2, input []byte) { + if len(input) < 2*Params.Bytelen { + panic("input byte slice too short") + } + + for i := 0; i < Params.Bytelen; i++ { + j := i / 8 + k := uint64(i % 8) + fp2.A[j] |= uint64(input[i]) << (8 * k) + fp2.B[j] |= uint64(input[i+Params.Bytelen]) << (8 * k) + } + toMontDomain(fp2) +} + +// ----------------------------------------------------------------------------- +// Functions for traversing isogeny trees acoording to strategy. Key type 'A' is +// + +// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed +// for public key generation. +func traverseTreePublicKeyA(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv4(curve) + phi := NewIsogeny4() + strat := pub.params.A.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow2k(xR, &cparam, 2*k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + *phiP = phi.EvaluatePoint(phiP) + *phiQ = phi.EvaluatePoint(phiQ) + *phiR = phi.EvaluatePoint(phiR) + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Traverses isogeny tree in order to compute xR needed +// for public key generation. +func traverseTreeSharedKeyA(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv4(curve) + phi := NewIsogeny4() + strat := pub.params.A.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow2k(xR, &cparam, 2*k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed +// for public key generation. +func traverseTreePublicKeyB(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv3(curve) + phi := NewIsogeny3() + strat := pub.params.B.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow3k(xR, &cparam, k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + *phiP = phi.EvaluatePoint(phiP) + *phiQ = phi.EvaluatePoint(phiQ) + *phiR = phi.EvaluatePoint(phiR) + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed +// for public key generation. +func traverseTreeSharedKeyB(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv3(curve) + phi := NewIsogeny3() + strat := pub.params.B.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow3k(xR, &cparam, k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Generate a public key in the 2-torsion group +func publicKeyGenA(prv *PrivateKey) (pub *PublicKey) { + var xPA, xQA, xRA ProjectivePoint + var xPB, xQB, xRB, xR ProjectivePoint + var invZP, invZQ, invZR Fp2 + var tmp ProjectiveCurveParameters + + pub = NewPublicKey(KeyVariant_SIDH_A) + var phi = NewIsogeny4() + + // Load points for A + xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2} + xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2} + xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2} + + // Load points for B + xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2} + xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2} + xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2} + + // Find isogeny kernel + tmp.C = pub.params.OneFp2 + xR = ScalarMul3Pt(&tmp, &xPA, &xQA, &xRA, prv.params.A.SecretBitLen, prv.Scalar) + + // Reset params object and travers isogeny tree + tmp.C = pub.params.OneFp2 + zeroize(&tmp.A) + traverseTreePublicKeyA(&tmp, &xR, &xPB, &xQB, &xRB, pub) + + // Secret isogeny + phi.GenerateCurve(&xR) + xPA = phi.EvaluatePoint(&xPB) + xQA = phi.EvaluatePoint(&xQB) + xRA = phi.EvaluatePoint(&xRB) + Fp2Batch3Inv(&xPA.Z, &xQA.Z, &xRA.Z, &invZP, &invZQ, &invZR) + + mul(&pub.affine_xP, &xPA.X, &invZP) + mul(&pub.affine_xQ, &xQA.X, &invZQ) + mul(&pub.affine_xQmP, &xRA.X, &invZR) + return +} + +// Generate a public key in the 3-torsion group +func publicKeyGenB(prv *PrivateKey) (pub *PublicKey) { + var xPB, xQB, xRB, xR ProjectivePoint + var xPA, xQA, xRA ProjectivePoint + var invZP, invZQ, invZR Fp2 + var tmp ProjectiveCurveParameters + + pub = NewPublicKey(prv.keyVariant) + var phi = NewIsogeny3() + + // Load points for B + xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2} + xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2} + xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2} + + // Load points for A + xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2} + xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2} + xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2} + + tmp.C = pub.params.OneFp2 + xR = ScalarMul3Pt(&tmp, &xPB, &xQB, &xRB, prv.params.B.SecretBitLen, prv.Scalar) + + tmp.C = pub.params.OneFp2 + zeroize(&tmp.A) + traverseTreePublicKeyB(&tmp, &xR, &xPA, &xQA, &xRA, pub) + + phi.GenerateCurve(&xR) + xPB = phi.EvaluatePoint(&xPA) + xQB = phi.EvaluatePoint(&xQA) + xRB = phi.EvaluatePoint(&xRA) + Fp2Batch3Inv(&xPB.Z, &xQB.Z, &xRB.Z, &invZP, &invZQ, &invZR) + + mul(&pub.affine_xP, &xPB.X, &invZP) + mul(&pub.affine_xQ, &xQB.X, &invZQ) + mul(&pub.affine_xQmP, &xRB.X, &invZR) + return +} + +// ----------------------------------------------------------------------------- +// Key agreement functions +// + +// Establishing shared keys in in 2-torsion group +func deriveSecretA(prv *PrivateKey, pub *PublicKey) []byte { + var sharedSecret = make([]byte, pub.params.SharedSecretSize) + var cparam ProjectiveCurveParameters + var xP, xQ, xQmP ProjectivePoint + var xR ProjectivePoint + var phi = NewIsogeny4() + var jInv Fp2 + + // Recover curve coefficients + cparam.C = pub.params.OneFp2 + RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP) + + // Find kernel of the morphism + xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2} + xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2} + xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2} + xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.A.SecretBitLen, prv.Scalar) + + // Traverse isogeny tree + traverseTreeSharedKeyA(&cparam, &xR, pub) + + // Calculate j-invariant on isogeneus curve + c := phi.GenerateCurve(&xR) + RecoverCurveCoefficients4(&cparam, &c) + Jinvariant(&cparam, &jInv) + convFp2ToBytes(sharedSecret, &jInv) + return sharedSecret +} + +// Establishing shared keys in in 3-torsion group +func deriveSecretB(prv *PrivateKey, pub *PublicKey) []byte { + var sharedSecret = make([]byte, pub.params.SharedSecretSize) + var xP, xQ, xQmP ProjectivePoint + var xR ProjectivePoint + var cparam ProjectiveCurveParameters + var phi = NewIsogeny3() + var jInv Fp2 + + // Recover curve coefficients + cparam.C = pub.params.OneFp2 + RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP) + + // Find kernel of the morphism + xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2} + xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2} + xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2} + xR = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.B.SecretBitLen, prv.Scalar) + + // Traverse isogeny tree + traverseTreeSharedKeyB(&cparam, &xR, pub) + + // Calculate j-invariant on isogeneus curve + c := phi.GenerateCurve(&xR) + RecoverCurveCoefficients3(&cparam, &c) + Jinvariant(&cparam, &jInv) + convFp2ToBytes(sharedSecret, &jInv) + return sharedSecret +} + +func encrypt(skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) ([]byte, error) { + var n [40]byte // n can is max 320-bit (see 1.4 of [SIKE]) + var ptextLen = len(ptext) + + if pkB.keyVariant != KeyVariant_SIKE { + return nil, errors.New("wrong key type") + } + + j, err := DeriveSecret(skA, pkB) + if err != nil { + return nil, err + } + + hashMac(n[:ptextLen], j, F) + for i, _ := range ptext { + n[i] ^= ptext[i] + } + + ret := make([]byte, pkA.Size()+ptextLen) + copy(ret, pkA.Export()) + copy(ret[pkA.Size():], n[:ptextLen]) + return ret, nil +} + +// NewPrivateKey initializes private key. +// Usage of this function guarantees that the object is correctly initialized. +func NewPrivateKey(v KeyVariant) *PrivateKey { + prv := &PrivateKey{key: key{params: &Params, keyVariant: v}} + if (v & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + prv.Scalar = make([]byte, prv.params.A.SecretByteLen) + } else { + prv.Scalar = make([]byte, prv.params.B.SecretByteLen) + } + if v == KeyVariant_SIKE { + prv.S = make([]byte, prv.params.MsgLen) + } + return prv +} + +// NewPublicKey initializes public key. +// Usage of this function guarantees that the object is correctly initialized. +func NewPublicKey(v KeyVariant) *PublicKey { + return &PublicKey{key: key{params: &Params, keyVariant: v}} +} + +// Import clears content of the public key currently stored in the structure +// and imports key stored in the byte string. Returns error in case byte string +// size is wrong. Doesn't perform any validation. +func (pub *PublicKey) Import(input []byte) error { + if len(input) != pub.Size() { + return errors.New("sidh: input to short") + } + ssSz := pub.params.SharedSecretSize + convBytesToFp2(&pub.affine_xP, input[0:ssSz]) + convBytesToFp2(&pub.affine_xQ, input[ssSz:2*ssSz]) + convBytesToFp2(&pub.affine_xQmP, input[2*ssSz:3*ssSz]) + return nil +} + +// Exports currently stored key. In case structure hasn't been filled with key data +// returned byte string is filled with zeros. +func (pub *PublicKey) Export() []byte { + output := make([]byte, pub.params.PublicKeySize) + ssSz := pub.params.SharedSecretSize + convFp2ToBytes(output[0:ssSz], &pub.affine_xP) + convFp2ToBytes(output[ssSz:2*ssSz], &pub.affine_xQ) + convFp2ToBytes(output[2*ssSz:3*ssSz], &pub.affine_xQmP) + return output +} + +// Size returns size of the public key in bytes +func (pub *PublicKey) Size() int { + return pub.params.PublicKeySize +} + +// Exports currently stored key. In case structure hasn't been filled with key data +// returned byte string is filled with zeros. +func (prv *PrivateKey) Export() []byte { + ret := make([]byte, len(prv.Scalar)+len(prv.S)) + copy(ret, prv.S) + copy(ret[len(prv.S):], prv.Scalar) + return ret +} + +// Size returns size of the private key in bytes +func (prv *PrivateKey) Size() int { + tmp := len(prv.Scalar) + if prv.keyVariant == KeyVariant_SIKE { + tmp += int(prv.params.MsgLen) + } + return tmp +} + +// Import clears content of the private key currently stored in the structure +// and imports key from octet string. In case of SIKE, the random value 'S' +// must be prepended to the value of actual private key (see SIKE spec for details). +// Function doesn't import public key value to PrivateKey object. +func (prv *PrivateKey) Import(input []byte) error { + if len(input) != prv.Size() { + return errors.New("sidh: input to short") + } + copy(prv.S, input[:len(prv.S)]) + copy(prv.Scalar, input[len(prv.S):]) + return nil +} + +// Generates random private key for SIDH or SIKE. Generated value is +// formed as little-endian integer from key-space <2^(e2-1)..2^e2 - 1> +// for KeyVariant_A or <2^(s-1)..2^s - 1>, where s = floor(log_2(3^e3)), +// for KeyVariant_B. +// +// Returns error in case user provided RNG fails. +func (prv *PrivateKey) Generate(rand io.Reader) error { + var err error + var dp *DomainParams + + if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + dp = &prv.params.A + } else { + dp = &prv.params.B + } + + if prv.keyVariant == KeyVariant_SIKE && err == nil { + _, err = io.ReadFull(rand, prv.S) + } + + // Private key generation takes advantage of the fact that keyspace for secret + // key is (0, 2^x - 1), for some possitivite value of 'x' (see SIKE, 1.3.8). + // It means that all bytes in the secret key, but the last one, can take any + // value between <0x00,0xFF>. Similarily for the last byte, but generation + // needs to chop off some bits, to make sure generated value is an element of + // a key-space. + _, err = io.ReadFull(rand, prv.Scalar) + if err != nil { + return err + } + prv.Scalar[len(prv.Scalar)-1] &= (1 << (dp.SecretBitLen % 8)) - 1 + // Make sure scalar is SecretBitLen long. SIKE spec says that key + // space starts from 0, but I'm not confortable with having low + // value scalars used for private keys. It is still secrure as per + // table 5.1 in [SIKE]. + prv.Scalar[len(prv.Scalar)-1] |= 1 << ((dp.SecretBitLen % 8) - 1) + return err +} + +// Generates public key. +// +// Constant time. +func (prv *PrivateKey) GeneratePublicKey() *PublicKey { + if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + return publicKeyGenA(prv) + } + return publicKeyGenB(prv) +} + +// Computes a shared secret which is a j-invariant. Function requires that pub has +// different KeyVariant than prv. Length of returned output is 2*ceil(log_2 P)/8), +// where P is a prime defining finite field. +// +// It's important to notice that each keypair must not be used more than once +// to calculate shared secret. +// +// Function may return error. This happens only in case provided input is invalid. +// Constant time for properly initialized private and public key. +func DeriveSecret(prv *PrivateKey, pub *PublicKey) ([]byte, error) { + + if (pub == nil) || (prv == nil) { + return nil, errors.New("sidh: invalid arguments") + } + + if (pub.keyVariant == prv.keyVariant) || (pub.params.Id != prv.params.Id) { + return nil, errors.New("sidh: public and private are incompatbile") + } + + if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + return deriveSecretA(prv, pub), nil + } else { + return deriveSecretB(prv, pub), nil + } +} + +// Uses SIKE public key to encrypt plaintext. Requires cryptographically secure PRNG +// Returns ciphertext in case encryption succeeds. Returns error in case PRNG fails +// or wrongly formated input was provided. +func Encrypt(rng io.Reader, pub *PublicKey, ptext []byte) ([]byte, error) { + var ptextLen = len(ptext) + // c1 must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3) + if ptextLen != (pub.params.KemSize + 8) { + return nil, errors.New("Unsupported message length") + } + + skA := NewPrivateKey(KeyVariant_SIDH_A) + err := skA.Generate(rng) + if err != nil { + return nil, err + } + + pkA := skA.GeneratePublicKey() + return encrypt(skA, pkA, pub, ptext) +} + +// Uses SIKE private key to decrypt ciphertext. Returns plaintext in case +// decryption succeeds or error in case unexptected input was provided. +// Constant time +func Decrypt(prv *PrivateKey, ctext []byte) ([]byte, error) { + var n [40]byte // n can is max 320-bit (see 1.4 of [SIKE]) + var c1_len int + var pk_len = prv.params.PublicKeySize + + if prv.keyVariant != KeyVariant_SIKE { + return nil, errors.New("wrong key type") + } + + // ctext is a concatenation of (pubkey_A || c1=ciphertext) + // it must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3) + c1_len = len(ctext) - pk_len + if c1_len != (int(prv.params.KemSize) + 8) { + return nil, errors.New("wrong size of cipher text") + } + + c0 := NewPublicKey(KeyVariant_SIDH_A) + err := c0.Import(ctext[:pk_len]) + if err != nil { + return nil, err + } + j, err := DeriveSecret(prv, c0) + if err != nil { + return nil, err + } + + hashMac(n[:c1_len], j, F) + for i, _ := range n[:c1_len] { + n[i] ^= ctext[pk_len+i] + } + return n[:c1_len], nil +} + +// Encapsulation receives the public key and generates SIKE ciphertext and shared secret. +// The generated ciphertext is used for authentication. +// The rng must be cryptographically secure PRNG. +// Error is returned in case PRNG fails or wrongly formated input was provided. +func Encapsulate(rng io.Reader, pub *PublicKey) (ctext []byte, secret []byte, err error) { + // Buffer for random, secret message + var ptext = make([]byte, pub.params.MsgLen) + // r = G(ptext||pub) + var r = make([]byte, pub.params.A.SecretByteLen) + // Resulting shared secret + secret = make([]byte, pub.params.KemSize) + + // Generate ephemeral value + _, err = io.ReadFull(rng, ptext) + if err != nil { + return nil, nil, err + } + + // must be big enough to store ptext+c0+c1 + var hmac_key = make([]byte, pub.Size()+2*Params.MsgLen) + copy(hmac_key, ptext) + copy(hmac_key[len(ptext):], pub.Export()) + hashMac(r, hmac_key[:len(ptext)+pub.Size()], G) + // Ensure bitlength is not bigger then to 2^e2-1 + r[len(r)-1] &= (1 << (pub.params.A.SecretBitLen % 8)) - 1 + + // (c0 || c1) = Enc(pkA, ptext; r) + skA := NewPrivateKey(KeyVariant_SIDH_A) + err = skA.Import(r) + if err != nil { + return nil, nil, err + } + + pkA := skA.GeneratePublicKey() + ctext, err = encrypt(skA, pkA, pub, ptext) + if err != nil { + return nil, nil, err + } + + // K = H(ptext||(c0||c1)) + copy(hmac_key, ptext) + copy(hmac_key[len(ptext):], ctext) + hashMac(secret, hmac_key[:len(ptext)+len(ctext)], H) + return ctext, secret, nil +} + +// Decapsulate given the keypair and ciphertext as inputs, Decapsulate outputs a shared +// secret if plaintext verifies correctly, otherwise function outputs random value. +// Decapsulation may fail in case input is wrongly formated. +// Constant time for properly initialized input. +func Decapsulate(prv *PrivateKey, pub *PublicKey, ctext []byte) ([]byte, error) { + var r = make([]byte, pub.params.A.SecretByteLen) + // Resulting shared secret + var secret = make([]byte, pub.params.KemSize) + var skA = NewPrivateKey(KeyVariant_SIDH_A) + + m, err := Decrypt(prv, ctext) + if err != nil { + return nil, err + } + + // r' = G(m'||pub) + var hmac_key = make([]byte, pub.Size()+2*Params.MsgLen) + copy(hmac_key, m) + copy(hmac_key[len(m):], pub.Export()) + hashMac(r, hmac_key[:len(m)+pub.Size()], G) + // Ensure bitlength is not bigger than 2^e2-1 + r[len(r)-1] &= (1 << (pub.params.A.SecretBitLen % 8)) - 1 + + // Never fails + skA.Import(r) + + // Never fails + pkA := skA.GeneratePublicKey() + c0 := pkA.Export() + + if subtle.ConstantTimeCompare(c0, ctext[:len(c0)]) == 1 { + copy(hmac_key, m) + } else { + // S is chosen at random when generating a key and unknown to other party. It + // may seem weird, but it's correct. It is important that S is unpredictable + // to other party. Without this check, it is possible to recover a secret, by + // providing series of invalid ciphertexts. It is also important that in case + // + // See more details in "On the security of supersingular isogeny cryptosystems" + // (S. Galbraith, et al., 2016, ePrint #859). + copy(hmac_key, prv.S) + } + copy(hmac_key[len(m):], ctext) + hashMac(secret, hmac_key[:len(m)+len(ctext)], H) + return secret, nil +} diff --git a/ssl/test/runner/sike/sike_test.go b/ssl/test/runner/sike/sike_test.go new file mode 100644 index 00000000..f7621dfc --- /dev/null +++ b/ssl/test/runner/sike/sike_test.go @@ -0,0 +1,680 @@ +package sike + +import ( + "bufio" + "bytes" + "crypto/rand" + "encoding/hex" + "math/big" + "strings" + "testing" +) + +var tdata = struct { + name string + PrB_sidh string + PkB_sidh string + PkB_sike string + PrB_sike string + PrA_sike string + PkA_sike string +}{ + name: "P-503", + PkB_sike: "68460C22466E95864CFEA7B5D9077E768FF4F9ED69AE56D7CF3F236FB06B31020EEE34B5B572CEA5DDF20B531966AA8F5F3ACC0C6D1CE04EEDC30FD1F1233E2D96FE60C6D638FC646EAF2E2246F1AEC96859CE874A1F029A78F9C978CD6B22114A0D5AB20101191FD923E80C76908B1498B9D0200065CCA09159A0C65A1E346CC6470314FE78388DAA89DD08EC67DBE63C1F606674ACC49EBF9FDBB2B898B3CE733113AA6F942DB401A76D629CE6EE6C0FDAF4CFB1A5E366DB66C17B3923A1B7FB26A3FF25B9018869C674D3DEF4AF269901D686FE4647F9D2CDB2CEB3AFA305B27C885F037ED167F595066C21E7DD467D8332B934A5102DA5F13332DFA356B82156A0BB2E7E91C6B85B7D1E381BC9E3F0FC4DB9C36016D9ECEC415D7E977E9AC29910D934BA2FE4EE49D3B387607A4E1AFABF495FB86A77194626589E802FF5167C7A25C542C1EAD25A6E0AA931D94F2F9AFD3DBDF222E651F729A90E77B20974905F1E65E041CE6C95AAB3E1F22D332E0A5DE9C5DB3D9C7A38", + PrB_sike: "80FC55DA74DEFE3113487B80841E678AF9ED4E0599CF07353A4AB93971C090A0" + + "A9402C9DC98AC6DC8F5FDE5E970AE22BA48A400EFC72851C", + PrB_sidh: "A885A8B889520A6DBAD9FB33365E5B77FDED629440A16A533F259A510F63A822", + PrA_sike: "B0AD510708F4ABCF3E0D97DC2F2FF112D9D2AAE49D97FFD1E4267F21C6E71C03", + PkA_sike: "A6BADBA04518A924B20046B59AC197DCDF0EA48014C9E228C4994CCA432F360E" + + "2D527AFB06CA7C96EE5CEE19BAD53BF9218A3961CAD7EC092BD8D9EBB22A3D51" + + "33008895A3F1F6A023F91E0FE06A00A622FD6335DAC107F8EC4283DC2632F080" + + "4E64B390DAD8A2572F1947C67FDF4F8787D140CE2C6B24E752DA9A195040EDFA" + + "C27333FAE97DBDEB41DA9EEB2DB067AE7DA8C58C0EF57AEFC18A3D6BD0576FF2" + + "F1CFCAEC50C958331BF631F3D2E769790C7B6DF282B74BBC02998AD10F291D47" + + "C5A762FF84253D3B3278BDF20C8D4D4AA317BE401B884E26A1F02C7308AADB68" + + "20EBDB0D339F5A63346F3B40CACED72F544DAF51566C6E807D0E6E1E38514342" + + "432661DC9564DA07548570E256688CD9E8060D8775F95D501886D958588CACA0" + + "9F2D2AE1913F996E76AF63E31A179A7A7D2A46EDA03B2BCCF9020A5AA15F9A28" + + "9340B33F3AE7F97360D45F8AE1B9DD48779A57E8C45B50A02C00349CD1C58C55" + + "1D68BC2A75EAFED944E8C599C288037181E997471352E24C952B", + PkB_sidh: "244AF1F367C2C33912750A98497CC8214BC195BD52BD76513D32ACE4B75E31F0" + + "281755C265F5565C74E3C04182B9C244071859C8588CC7F09547CEFF8F7705D2" + + "60CE87D6BFF914EE7DBE4B9AF051CA420062EEBDF043AF58184495026949B068" + + "98A47046BFAE8DF3B447746184AF550553BB5D266D6E1967ACA33CAC5F399F90" + + "360D70867F2C71EF6F94FF915C7DA8BC9549FB7656E691DAEFC93CF56876E482" + + "CA2F8BE2D6CDCC374C31AD8833CABE997CC92305F38497BEC4DFD1821B004FEC" + + "E16448F9A24F965EFE409A8939EEA671633D9FFCF961283E59B8834BDF7EDDB3" + + "05D6275B61DA6692325432A0BAA074FC7C1F51E76208AB193A57520D40A76334" + + "EE5712BDC3E1EFB6103966F2329EDFF63082C4DFCDF6BE1C5A048630B81871B8" + + "83B735748A8FD4E2D9530C272163AB18105B10015CA7456202FE1C9B92CEB167" + + "5EAE1132E582C88E47ED87B363D45F05BEA714D5E9933D7AF4071CBB5D49008F" + + "3E3DAD7DFF935EE509D5DE561842B678CCEB133D62E270E9AC3E", +} + +/* ------------------------------------------------------------------------- + Helpers + -------------------------------------------------------------------------*/ +// Fail if err !=nil. Display msg as an error message +func checkErr(t testing.TB, err error, msg string) { + if err != nil { + t.Error(msg) + } +} + +// Utility used for running same test with all registered prime fields +type MultiIdTestingFunc func(testing.TB) + +// Converts string to private key +func convToPrv(s string, v KeyVariant) *PrivateKey { + key := NewPrivateKey(v) + hex, e := hex.DecodeString(s) + if e != nil { + panic("non-hex number provided") + } + e = key.Import(hex) + if e != nil { + panic("Can't import private key") + } + return key +} + +// Converts string to public key +func convToPub(s string, v KeyVariant) *PublicKey { + key := NewPublicKey(v) + hex, e := hex.DecodeString(s) + if e != nil { + panic("non-hex number provided") + } + e = key.Import(hex) + if e != nil { + panic("Can't import public key") + } + return key +} + +/* ------------------------------------------------------------------------- + Unit tests + -------------------------------------------------------------------------*/ +func TestKeygen(t *testing.T) { + alicePrivate := convToPrv(tdata.PrA_sike, KeyVariant_SIDH_A) + bobPrivate := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B) + expPubA := convToPub(tdata.PkA_sike, KeyVariant_SIDH_A) + expPubB := convToPub(tdata.PkB_sidh, KeyVariant_SIDH_B) + + pubA := alicePrivate.GeneratePublicKey() + pubB := bobPrivate.GeneratePublicKey() + + if !bytes.Equal(pubA.Export(), expPubA.Export()) { + t.Fatalf("unexpected value of public key A") + } + if !bytes.Equal(pubB.Export(), expPubB.Export()) { + t.Fatalf("unexpected value of public key B") + } +} + +func TestImportExport(t *testing.T) { + var err error + a := NewPublicKey(KeyVariant_SIDH_A) + b := NewPublicKey(KeyVariant_SIDH_B) + + // Import keys + a_hex, err := hex.DecodeString(tdata.PkA_sike) + checkErr(t, err, "invalid hex-number provided") + + err = a.Import(a_hex) + checkErr(t, err, "import failed") + + b_hex, err := hex.DecodeString(tdata.PkB_sike) + checkErr(t, err, "invalid hex-number provided") + + err = b.Import(b_hex) + checkErr(t, err, "import failed") + + // Export and check if same + if !bytes.Equal(b.Export(), b_hex) || !bytes.Equal(a.Export(), a_hex) { + t.Fatalf("export/import failed") + } + + if (len(b.Export()) != b.Size()) || (len(a.Export()) != a.Size()) { + t.Fatalf("wrong size of exported keys") + } +} + +func testPrivateKeyBelowMax(t testing.TB) { + for variant, keySz := range map[KeyVariant]*DomainParams{ + KeyVariant_SIDH_A: &Params.A, + KeyVariant_SIDH_B: &Params.B} { + + func(v KeyVariant, dp *DomainParams) { + var blen = int(dp.SecretByteLen) + var prv = NewPrivateKey(v) + + // Calculate either (2^e2 - 1) or (2^s - 1); where s=ceil(log_2(3^e3))) + maxSecertVal := big.NewInt(int64(dp.SecretBitLen)) + maxSecertVal.Exp(big.NewInt(int64(2)), maxSecertVal, nil) + maxSecertVal.Sub(maxSecertVal, big.NewInt(1)) + + // Do same test 1000 times + for i := 0; i < 1000; i++ { + err := prv.Generate(rand.Reader) + checkErr(t, err, "Private key generation") + + // Convert to big-endian, as that's what expected by (*Int)SetBytes() + secretBytes := prv.Export() + for i := 0; i < int(blen/2); i++ { + tmp := secretBytes[i] ^ secretBytes[blen-i-1] + secretBytes[i] = tmp ^ secretBytes[i] + secretBytes[blen-i-1] = tmp ^ secretBytes[blen-i-1] + } + prvBig := new(big.Int).SetBytes(secretBytes) + // Check if generated key is bigger then acceptable + if prvBig.Cmp(maxSecertVal) == 1 { + t.Error("Generated private key is wrong") + } + } + }(variant, keySz) + } +} + +func testKeyAgreement(t *testing.T, pkA, prA, pkB, prB string) { + var e error + + // KeyPairs + alicePublic := convToPub(pkA, KeyVariant_SIDH_A) + bobPublic := convToPub(pkB, KeyVariant_SIDH_B) + alicePrivate := convToPrv(prA, KeyVariant_SIDH_A) + bobPrivate := convToPrv(prB, KeyVariant_SIDH_B) + + // Do actual test + s1, e := DeriveSecret(bobPrivate, alicePublic) + checkErr(t, e, "derivation s1") + s2, e := DeriveSecret(alicePrivate, bobPublic) + checkErr(t, e, "derivation s1") + + if !bytes.Equal(s1[:], s2[:]) { + t.Fatalf("two shared keys: %d, %d do not match", s1, s2) + } + + // Negative case + dec, e := hex.DecodeString(tdata.PkA_sike) + if e != nil { + t.FailNow() + } + dec[0] = ^dec[0] + e = alicePublic.Import(dec) + if e != nil { + t.FailNow() + } + + s1, e = DeriveSecret(bobPrivate, alicePublic) + checkErr(t, e, "derivation of s1 failed") + s2, e = DeriveSecret(alicePrivate, bobPublic) + checkErr(t, e, "derivation of s2 failed") + + if bytes.Equal(s1[:], s2[:]) { + t.Fatalf("The two shared keys: %d, %d match", s1, s2) + } +} + +func TestDerivationRoundTrip(t *testing.T) { + var err error + + prvA := NewPrivateKey(KeyVariant_SIDH_A) + prvB := NewPrivateKey(KeyVariant_SIDH_B) + + // Generate private keys + err = prvA.Generate(rand.Reader) + checkErr(t, err, "key generation failed") + err = prvB.Generate(rand.Reader) + checkErr(t, err, "key generation failed") + + // Generate public keys + pubA := prvA.GeneratePublicKey() + pubB := prvB.GeneratePublicKey() + + // Derive shared secret + s1, err := DeriveSecret(prvB, pubA) + checkErr(t, err, "") + + s2, err := DeriveSecret(prvA, pubB) + checkErr(t, err, "") + + if !bytes.Equal(s1[:], s2[:]) { + t.Fatalf("Two shared keys: \n%X, \n%X do not match", s1, s2) + } +} + +// Encrypt, Decrypt, check if input/output plaintext is the same +func testPKERoundTrip(t testing.TB, id uint8) { + // Message to be encrypted + var msg = make([]byte, Params.MsgLen) + for i, _ := range msg { + msg[i] = byte(i) + } + + // Import keys + pkB := NewPublicKey(KeyVariant_SIKE) + skB := NewPrivateKey(KeyVariant_SIKE) + pk_hex, err := hex.DecodeString(tdata.PkB_sike) + if err != nil { + t.Fatal(err) + } + sk_hex, err := hex.DecodeString(tdata.PrB_sike) + if err != nil { + t.Fatal(err) + } + if pkB.Import(pk_hex) != nil || skB.Import(sk_hex) != nil { + t.Error("Import") + } + + ct, err := Encrypt(rand.Reader, pkB, msg[:]) + if err != nil { + t.Fatal(err) + } + pt, err := Decrypt(skB, ct) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(pt[:], msg[:]) { + t.Errorf("Decryption failed \n got : %X\n exp : %X", pt, msg) + } +} + +// Generate key and check if can encrypt +func TestPKEKeyGeneration(t *testing.T) { + // Message to be encrypted + var msg = make([]byte, Params.MsgLen) + var err error + for i, _ := range msg { + msg[i] = byte(i) + } + + sk := NewPrivateKey(KeyVariant_SIKE) + err = sk.Generate(rand.Reader) + checkErr(t, err, "PEK key generation") + pk := sk.GeneratePublicKey() + + // Try to encrypt + ct, err := Encrypt(rand.Reader, pk, msg[:]) + checkErr(t, err, "PEK encryption") + pt, err := Decrypt(sk, ct) + checkErr(t, err, "PEK key decryption") + + if !bytes.Equal(pt[:], msg[:]) { + t.Fatalf("Decryption failed \n got : %X\n exp : %X", pt, msg) + } +} + +func TestNegativePKE(t *testing.T) { + var msg [40]byte + var err error + + // Generate key + sk := NewPrivateKey(KeyVariant_SIKE) + err = sk.Generate(rand.Reader) + checkErr(t, err, "key generation") + + pk := sk.GeneratePublicKey() + + // bytelen(msg) - 1 + ct, err := Encrypt(rand.Reader, pk, msg[:Params.KemSize+8-1]) + if err == nil { + t.Fatal("Error hasn't been returned") + } + if ct != nil { + t.Fatal("Ciphertext must be nil") + } + + // KemSize - 1 + pt, err := Decrypt(sk, msg[:Params.KemSize+8-1]) + if err == nil { + t.Fatal("Error hasn't been returned") + } + if pt != nil { + t.Fatal("Ciphertext must be nil") + } +} + +func testKEMRoundTrip(t *testing.T, pkB, skB []byte) { + // Import keys + pk := NewPublicKey(KeyVariant_SIKE) + sk := NewPrivateKey(KeyVariant_SIKE) + if pk.Import(pkB) != nil || sk.Import(skB) != nil { + t.Error("Import failed") + } + + ct, ss_e, err := Encapsulate(rand.Reader, pk) + if err != nil { + t.Error("Encapsulate failed") + } + + ss_d, err := Decapsulate(sk, pk, ct) + if err != nil { + t.Error("Decapsulate failed") + } + if !bytes.Equal(ss_e, ss_d) { + t.Error("Shared secrets from decapsulation and encapsulation differ") + } +} + +func TestKEMRoundTrip(t *testing.T) { + pk, err := hex.DecodeString(tdata.PkB_sike) + checkErr(t, err, "public key B not a number") + sk, err := hex.DecodeString(tdata.PrB_sike) + checkErr(t, err, "private key B not a number") + testKEMRoundTrip(t, pk, sk) +} + +func TestKEMKeyGeneration(t *testing.T) { + // Generate key + sk := NewPrivateKey(KeyVariant_SIKE) + checkErr(t, sk.Generate(rand.Reader), "error: key generation") + pk := sk.GeneratePublicKey() + + // calculated shared secret + ct, ss_e, err := Encapsulate(rand.Reader, pk) + checkErr(t, err, "encapsulation failed") + ss_d, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "decapsulation failed") + + if !bytes.Equal(ss_e, ss_d) { + t.Fatalf("KEM failed \n encapsulated: %X\n decapsulated: %X", ss_d, ss_e) + } +} + +func TestNegativeKEM(t *testing.T) { + sk := NewPrivateKey(KeyVariant_SIKE) + checkErr(t, sk.Generate(rand.Reader), "error: key generation") + pk := sk.GeneratePublicKey() + + ct, ss_e, err := Encapsulate(rand.Reader, pk) + checkErr(t, err, "pre-requisite for a test failed") + + ct[0] = ct[0] - 1 + ss_d, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "decapsulation returns error when invalid ciphertext provided") + + if bytes.Equal(ss_e, ss_d) { + // no idea how this could ever happen, but it would be very bad + t.Error("critical error") + } + + // Try encapsulating with SIDH key + pkSidh := NewPublicKey(KeyVariant_SIDH_B) + prSidh := NewPrivateKey(KeyVariant_SIDH_B) + _, _, err = Encapsulate(rand.Reader, pkSidh) + if err == nil { + t.Error("encapsulation accepts SIDH public key") + } + // Try decapsulating with SIDH key + _, err = Decapsulate(prSidh, pk, ct) + if err == nil { + t.Error("decapsulation accepts SIDH private key key") + } +} + +// In case invalid ciphertext is provided, SIKE's decapsulation must +// return same (but unpredictable) result for a given key. +func TestNegativeKEMSameWrongResult(t *testing.T) { + sk := NewPrivateKey(KeyVariant_SIKE) + checkErr(t, sk.Generate(rand.Reader), "error: key generation") + pk := sk.GeneratePublicKey() + + ct, encSs, err := Encapsulate(rand.Reader, pk) + checkErr(t, err, "pre-requisite for a test failed") + + // make ciphertext wrong + ct[0] = ct[0] - 1 + decSs1, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "pre-requisite for a test failed") + + // second decapsulation must be done with same, but imported private key + expSk := sk.Export() + + // creat new private key + sk = NewPrivateKey(KeyVariant_SIKE) + err = sk.Import(expSk) + checkErr(t, err, "import failed") + + // try decapsulating again. ss2 must be same as ss1 and different than + // original plaintext + decSs2, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "pre-requisite for a test failed") + + if !bytes.Equal(decSs1, decSs2) { + t.Error("decapsulation is insecure") + } + + if bytes.Equal(encSs, decSs1) || bytes.Equal(encSs, decSs2) { + // this test requires that decapsulation returns wrong result + t.Errorf("test implementation error") + } +} + +func readAndCheckLine(r *bufio.Reader) []byte { + // Read next line from buffer + line, isPrefix, err := r.ReadLine() + if err != nil || isPrefix { + panic("Wrong format of input file") + } + + // Function expects that line is in format "KEY = HEX_VALUE". Get + // value, which should be a hex string + hexst := strings.Split(string(line), "=")[1] + hexst = strings.TrimSpace(hexst) + // Convert value to byte string + ret, err := hex.DecodeString(hexst) + if err != nil { + panic("Wrong format of input file") + } + return ret +} + +func testKeygenSIKE(pk, sk []byte, id uint8) bool { + // Import provided private key + var prvKey = NewPrivateKey(KeyVariant_SIKE) + if prvKey.Import(sk) != nil { + panic("sike test: can't load KAT") + } + + // Generate public key + pubKey := prvKey.GeneratePublicKey() + return bytes.Equal(pubKey.Export(), pk) +} + +func testDecapsulation(pk, sk, ct, ssExpected []byte, id uint8) bool { + var pubKey = NewPublicKey(KeyVariant_SIKE) + var prvKey = NewPrivateKey(KeyVariant_SIKE) + if pubKey.Import(pk) != nil || prvKey.Import(sk) != nil { + panic("sike test: can't load KAT") + } + + ssGot, err := Decapsulate(prvKey, pubKey, ct) + if err != nil { + panic("sike test: can't perform degcapsulation KAT") + } + + if err != nil { + return false + } + return bytes.Equal(ssGot, ssExpected) +} + +func TestKeyAgreement(t *testing.T) { + testKeyAgreement(t, tdata.PkA_sike, tdata.PrA_sike, tdata.PkB_sidh, tdata.PrB_sidh) +} + +// Same values as in sike_test.cc +func TestDecapsulation(t *testing.T) { + + var sk = [56]byte{ + 0xDB, 0xAF, 0x2C, 0x89, 0xCA, 0x5A, 0xD4, 0x9D, 0x4F, 0x13, + 0x40, 0xDF, 0x2D, 0xB1, 0x5F, 0x4C, 0x91, 0xA7, 0x1F, 0x0B, + 0x29, 0x15, 0x01, 0x59, 0xBC, 0x5F, 0x0B, 0x4A, 0x03, 0x27, + 0x6F, 0x18} + + var pk = []byte{ + 0x07, 0xAA, 0x51, 0x45, 0x3E, 0x1F, 0x53, 0x2A, 0x0A, 0x05, + 0x46, 0xF6, 0x54, 0x7F, 0x5D, 0x56, 0xD6, 0x76, 0xD3, 0xEA, + 0x4B, 0x6B, 0x01, 0x9B, 0x11, 0x72, 0x6F, 0x75, 0xEA, 0x34, + 0x3C, 0x28, 0x2C, 0x36, 0xFD, 0x77, 0xDA, 0xBE, 0xB6, 0x20, + 0x18, 0xC1, 0x93, 0x98, 0x18, 0x86, 0x30, 0x2F, 0x2E, 0xD2, + 0x00, 0x61, 0xFF, 0xAE, 0x78, 0xAE, 0xFB, 0x6F, 0x32, 0xAC, + 0x06, 0xBF, 0x35, 0xF6, 0xF7, 0x5B, 0x98, 0x26, 0x95, 0xC2, + 0xD8, 0xD6, 0x1C, 0x0E, 0x47, 0xDA, 0x76, 0xCE, 0xB5, 0xF1, + 0x19, 0xCC, 0x01, 0xE1, 0x17, 0xA9, 0x62, 0xF7, 0x82, 0x6C, + 0x25, 0x51, 0x25, 0xAE, 0xFE, 0xE3, 0xE2, 0xE1, 0x35, 0xAE, + 0x2E, 0x8F, 0x38, 0xE0, 0x7C, 0x74, 0x3C, 0x1D, 0x39, 0x91, + 0x1B, 0xC7, 0x9F, 0x8E, 0x33, 0x4E, 0x84, 0x19, 0xB8, 0xD9, + 0xC2, 0x71, 0x35, 0x02, 0x47, 0x3E, 0x79, 0xEF, 0x47, 0xE1, + 0xD8, 0x21, 0x96, 0x1F, 0x11, 0x59, 0x39, 0x34, 0x76, 0xEF, + 0x3E, 0xB7, 0x4E, 0xFB, 0x7C, 0x55, 0xA1, 0x85, 0xAA, 0xAB, + 0xAD, 0xF0, 0x09, 0xCB, 0xD1, 0xE3, 0x7C, 0x4F, 0x5D, 0x2D, + 0xE1, 0x13, 0xF0, 0x71, 0xD9, 0xE5, 0xF6, 0xAF, 0x7F, 0xC1, + 0x27, 0x95, 0x8D, 0x52, 0xD5, 0x96, 0x42, 0x38, 0x41, 0xF7, + 0x24, 0x3F, 0x3A, 0xB5, 0x7E, 0x11, 0xE4, 0xF9, 0x33, 0xEE, + 0x4D, 0xBE, 0x74, 0x48, 0xF9, 0x98, 0x04, 0x01, 0x16, 0xEB, + 0xA9, 0x0D, 0x61, 0xC6, 0xFD, 0x4C, 0xCF, 0x98, 0x84, 0x4A, + 0x94, 0xAC, 0x69, 0x2C, 0x02, 0x8B, 0xE3, 0xD1, 0x41, 0x0D, + 0xF2, 0x2D, 0x46, 0x1F, 0x57, 0x1C, 0x77, 0x86, 0x18, 0xE3, + 0x63, 0xDE, 0xF3, 0xE3, 0x02, 0x30, 0x54, 0x73, 0xAE, 0xC2, + 0x32, 0xA2, 0xCE, 0xEB, 0xCF, 0x81, 0x46, 0x54, 0x5C, 0xF4, + 0x5D, 0x2A, 0x03, 0x5D, 0x9C, 0xAE, 0xE0, 0x60, 0x03, 0x80, + 0x11, 0x30, 0xA5, 0xAA, 0xD1, 0x75, 0x67, 0xE0, 0x1C, 0x2B, + 0x6B, 0x5D, 0x83, 0xDE, 0x92, 0x9B, 0x0E, 0xD7, 0x11, 0x0F, + 0x00, 0xC4, 0x59, 0xE4, 0x81, 0x04, 0x3B, 0xEE, 0x5C, 0x04, + 0xD1, 0x0E, 0xD0, 0x67, 0xF5, 0xCC, 0xAA, 0x72, 0x73, 0xEA, + 0xC4, 0x76, 0x99, 0x3B, 0x4C, 0x90, 0x2F, 0xCB, 0xD8, 0x0A, + 0x5B, 0xEC, 0x0E, 0x0E, 0x1F, 0x59, 0xEA, 0x14, 0x8D, 0x34, + 0x53, 0x65, 0x4C, 0x1A, 0x59, 0xA8, 0x95, 0x66, 0x60, 0xBB, + 0xC4, 0xCC, 0x32, 0xA9, 0x8D, 0x2A, 0xAA, 0x14, 0x6F, 0x0F, + 0x81, 0x4D, 0x32, 0x02, 0xFD, 0x33, 0x58, 0x42, 0xCF, 0xF3, + 0x67, 0xD0, 0x9F, 0x0B, 0xB1, 0xCC, 0x18, 0xA5, 0xC4, 0x19, + 0xB6, 0x00, 0xED, 0xFA, 0x32, 0x1A, 0x5F, 0x67, 0xC8, 0xC3, + 0xEB, 0x0D, 0xB5, 0x9A, 0x36, 0x47, 0x82, 0x00, + } + + var ct = []byte{ + 0xE6, 0xB7, 0xE5, 0x7B, 0xA9, 0x19, 0xD1, 0x2C, 0xB8, 0x5C, + 0x7B, 0x66, 0x74, 0xB0, 0x71, 0xA1, 0xFF, 0x71, 0x7F, 0x4B, + 0xB5, 0xA6, 0xAF, 0x48, 0x32, 0x52, 0xD5, 0x82, 0xEE, 0x8A, + 0xBB, 0x08, 0x1E, 0xF6, 0xAC, 0x91, 0xA2, 0xCB, 0x6B, 0x6A, + 0x09, 0x2B, 0xD9, 0xC6, 0x27, 0xD6, 0x3A, 0x6B, 0x8D, 0xFC, + 0xB8, 0x90, 0x8F, 0x72, 0xB3, 0xFA, 0x7D, 0x34, 0x7A, 0xC4, + 0x7E, 0xE3, 0x30, 0xC5, 0xA0, 0xFE, 0x3D, 0x43, 0x14, 0x4E, + 0x3A, 0x14, 0x76, 0x3E, 0xFB, 0xDF, 0xE3, 0xA8, 0xE3, 0x5E, + 0x38, 0xF2, 0xE0, 0x39, 0x67, 0x60, 0xFD, 0xFB, 0xB4, 0x19, + 0xCD, 0xE1, 0x93, 0xA2, 0x06, 0xCC, 0x65, 0xCD, 0x6E, 0xC8, + 0xB4, 0x5E, 0x41, 0x4B, 0x6C, 0xA5, 0xF4, 0xE4, 0x9D, 0x52, + 0x8C, 0x25, 0x60, 0xDD, 0x3D, 0xA9, 0x7F, 0xF2, 0x88, 0xC1, + 0x0C, 0xEE, 0x97, 0xE0, 0xE7, 0x3B, 0xB7, 0xD3, 0x6F, 0x28, + 0x79, 0x2F, 0x50, 0xB2, 0x4F, 0x74, 0x3A, 0x0C, 0x88, 0x27, + 0x98, 0x3A, 0x27, 0xD3, 0x26, 0x83, 0x59, 0x49, 0x81, 0x5B, + 0x0D, 0xA7, 0x0C, 0x4F, 0xEF, 0xFB, 0x1E, 0xAF, 0xE9, 0xD2, + 0x1C, 0x10, 0x25, 0xEC, 0x9E, 0xFA, 0x57, 0x36, 0xAA, 0x3F, + 0xC1, 0xA3, 0x2C, 0xE9, 0xB5, 0xC9, 0xED, 0x72, 0x51, 0x4C, + 0x02, 0xB4, 0x7B, 0xB3, 0xED, 0x9F, 0x45, 0x03, 0x34, 0xAC, + 0x9A, 0x9E, 0x62, 0x5F, 0x82, 0x7A, 0x77, 0x34, 0xF9, 0x21, + 0x94, 0xD2, 0x38, 0x3D, 0x05, 0xF0, 0x8A, 0x60, 0x1C, 0xB7, + 0x1D, 0xF5, 0xB7, 0x53, 0x77, 0xD3, 0x9D, 0x3D, 0x70, 0x6A, + 0xCB, 0x18, 0x20, 0x6B, 0x29, 0x17, 0x3A, 0x6D, 0xA1, 0xB2, + 0x64, 0xDB, 0x6C, 0xE6, 0x1A, 0x95, 0xA7, 0xF4, 0x1A, 0x78, + 0x1D, 0xA2, 0x40, 0x15, 0x41, 0x59, 0xDD, 0xEE, 0x23, 0x57, + 0xCE, 0x36, 0x0D, 0x55, 0xBD, 0xB8, 0xFD, 0x0F, 0x35, 0xBD, + 0x5B, 0x92, 0xD6, 0x1C, 0x84, 0x8C, 0x32, 0x64, 0xA6, 0x5C, + 0x45, 0x18, 0x07, 0x6B, 0xF9, 0xA9, 0x43, 0x9A, 0x83, 0xCD, + 0xB5, 0xB3, 0xD9, 0x17, 0x99, 0x2C, 0x2A, 0x8B, 0xE0, 0x8E, + 0xAF, 0xA6, 0x4C, 0x95, 0xBB, 0x70, 0x60, 0x1A, 0x3A, 0x97, + 0xAA, 0x2F, 0x3D, 0x22, 0x83, 0xB7, 0x4F, 0x59, 0xED, 0x3F, + 0x4E, 0xF4, 0x19, 0xC6, 0x25, 0x0B, 0x0A, 0x5E, 0x21, 0xB9, + 0x91, 0xB8, 0x19, 0x84, 0x48, 0x78, 0xCE, 0x27, 0xBF, 0x41, + 0x89, 0xF6, 0x30, 0xFD, 0x6B, 0xD9, 0xB8, 0x1D, 0x72, 0x8A, + 0x56, 0xCC, 0x2F, 0x82, 0xE4, 0x46, 0x4D, 0x75, 0xD8, 0x92, + 0xE6, 0x9C, 0xCC, 0xD2, 0xCD, 0x35, 0xE4, 0xFC, 0x2A, 0x85, + 0x6B, 0xA9, 0xB2, 0x27, 0xC9, 0xA1, 0xFF, 0xB3, 0x96, 0x3E, + 0x59, 0xF6, 0x4C, 0x66, 0x56, 0x2E, 0xF5, 0x1B, 0x97, 0x32, + 0xB0, 0x71, 0x5A, 0x9C, 0x50, 0x4B, 0x6F, 0xC4, 0xCA, 0x94, + 0x75, 0x37, 0x46, 0x10, 0x12, 0x2F, 0x4F, 0xA3, 0x82, 0xCD, + 0xBD, 0x7C, + } + var ss_exp = []byte{ + 0x74, 0x3D, 0x25, 0x36, 0x00, 0x24, 0x63, 0x1A, 0x39, 0x1A, + 0xB4, 0xAD, 0x01, 0x17, 0x78, 0xE9} + + var prvObj = NewPrivateKey(KeyVariant_SIKE) + var pubObj = NewPublicKey(KeyVariant_SIKE) + + if pubObj.Import(pk) != nil || prvObj.Import(sk[:]) != nil { + t.Error("Can't import one of the keys") + } + + res, _ := Decapsulate(prvObj, pubObj, ct) + if !bytes.Equal(ss_exp, res) { + t.Error("Wrong decapsulation result") + } +} + +/* ------------------------------------------------------------------------- + Benchmarking + -------------------------------------------------------------------------*/ + +func BenchmarkSidhKeyAgreementP503(b *testing.B) { + // KeyPairs + alicePublic := convToPub(tdata.PkA_sike, KeyVariant_SIDH_A) + alicePrivate := convToPrv(tdata.PrA_sike, KeyVariant_SIDH_A) + bobPublic := convToPub(tdata.PkB_sidh, KeyVariant_SIDH_B) + bobPrivate := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B) + + for i := 0; i < b.N; i++ { + // Derive shared secret + DeriveSecret(bobPrivate, alicePublic) + DeriveSecret(alicePrivate, bobPublic) + } +} + +func BenchmarkAliceKeyGenPrvP503(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_A) + for n := 0; n < b.N; n++ { + prv.Generate(rand.Reader) + } +} + +func BenchmarkBobKeyGenPrvP503(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_B) + for n := 0; n < b.N; n++ { + prv.Generate(rand.Reader) + } +} + +func BenchmarkAliceKeyGenPubP503(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_A) + prv.Generate(rand.Reader) + for n := 0; n < b.N; n++ { + prv.GeneratePublicKey() + } +} + +func BenchmarkBobKeyGenPubP503(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_B) + prv.Generate(rand.Reader) + for n := 0; n < b.N; n++ { + prv.GeneratePublicKey() + } +} + +func BenchmarkSharedSecretAliceP503(b *testing.B) { + aPr := convToPrv(tdata.PrA_sike, KeyVariant_SIDH_A) + bPk := convToPub(tdata.PkB_sike, KeyVariant_SIDH_B) + for n := 0; n < b.N; n++ { + DeriveSecret(aPr, bPk) + } +} + +func BenchmarkSharedSecretBobP503(b *testing.B) { + // m_B = 3*randint(0,3^238) + aPk := convToPub(tdata.PkA_sike, KeyVariant_SIDH_A) + bPr := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B) + for n := 0; n < b.N; n++ { + DeriveSecret(bPr, aPk) + } +} diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 70e061b0..247f977b 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -1622,6 +1622,9 @@ bssl::UniquePtr TestConfig::NewSSL( case SSL_CURVE_CECPQ2: nids.push_back(NID_CECPQ2); break; + case SSL_CURVE_CECPQ2b: + nids.push_back(NID_CECPQ2b); + break; } if (!SSL_set1_curves(ssl.get(), &nids[0], nids.size())) { return nullptr; @@ -1632,6 +1635,7 @@ bssl::UniquePtr TestConfig::NewSSL( static const int kAllCurves[] = { NID_secp224r1, NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, NID_X25519, NID_CECPQ2, + NID_CECPQ2b, }; if (!SSL_set1_curves(ssl.get(), kAllCurves, OPENSSL_ARRAY_SIZE(kAllCurves))) { diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc index caaf0c74..d408b3c2 100644 --- a/ssl/tls13_server.cc +++ b/ssl/tls13_server.cc @@ -103,7 +103,7 @@ class CipherScorer { public: CipherScorer(uint16_t group_id) : aes_is_fine_(EVP_has_aes_hardware()), - security_128_is_fine_(group_id != SSL_CURVE_CECPQ2) {} + security_128_is_fine_(!(group_id == SSL_CURVE_CECPQ2 || group_id == SSL_CURVE_CECPQ2b)) {} typedef std::tuple Score;