Bug 1296239 - SSL_NamedGroupConfig to set custom list of curves, r=mt
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Mon, 12 Sep 2016 17:41:06 -0700
changeset 12534 07593068fee9873825832f12de3f2d21c9c245c1
parent 12533 b5f038cd950840a5289c568053cba64aeef3c000
child 12535 1f0d761475d2f37ca54b45bf91c284c04832a534
push id1525
push userfranziskuskiefer@gmail.com
push dateTue, 13 Sep 2016 19:56:56 +0000
reviewersmt
bugs1296239
Bug 1296239 - SSL_NamedGroupConfig to set custom list of curves, r=mt try: -b do -p linux,linux64,linux64-asan,win64 -u all -t all -q all
external_tests/ssl_gtest/ssl_dhe_unittest.cc
external_tests/ssl_gtest/ssl_ecdh_unittest.cc
external_tests/ssl_gtest/ssl_hrr_unittest.cc
external_tests/ssl_gtest/ssl_loopback_unittest.cc
external_tests/ssl_gtest/tls_agent.cc
external_tests/ssl_gtest/tls_agent.h
external_tests/ssl_gtest/tls_connect.cc
external_tests/ssl_gtest/tls_connect.h
lib/ssl/derive.c
lib/ssl/ssl.def
lib/ssl/ssl.h
lib/ssl/ssl3con.c
lib/ssl/ssl3ecc.c
lib/ssl/ssl3ext.c
lib/ssl/sslimpl.h
lib/ssl/sslsock.c
lib/ssl/tls13con.c
--- a/external_tests/ssl_gtest/ssl_dhe_unittest.cc
+++ b/external_tests/ssl_gtest/ssl_dhe_unittest.cc
@@ -21,62 +21,16 @@
 namespace nss_test {
 
 TEST_P(TlsConnectGeneric, ConnectDhe) {
   EnableOnlyDheCiphers();
   Connect();
   CheckKeys(ssl_kea_dh, ssl_auth_rsa_sign);
 }
 
-// Track groups and make sure that there are no duplicates.
-class CheckDuplicateGroup {
- public:
-  void AddAndCheckGroup(uint16_t group) {
-    EXPECT_EQ(groups_.end(), groups_.find(group))
-        << "Group " << group << " should not be duplicated";
-    groups_.insert(group);
-  }
-
- private:
-  std::set<uint16_t> groups_;
-};
-
-// Check the group of each of the supported groups
-static void CheckGroups(const DataBuffer& groups,
-                        std::function<void(uint16_t)> check_group) {
-  CheckDuplicateGroup group_set;
-  uint32_t tmp;
-  EXPECT_TRUE(groups.Read(0, 2, &tmp));
-  EXPECT_EQ(groups.len() - 2, static_cast<size_t>(tmp));
-  for (size_t i = 2; i < groups.len(); i += 2) {
-    EXPECT_TRUE(groups.Read(i, 2, &tmp));
-    uint16_t group = static_cast<uint16_t>(tmp);
-    group_set.AddAndCheckGroup(group);
-    check_group(group);
-  }
-}
-
-// Check the group of each of the shares
-static void CheckShares(const DataBuffer& shares,
-                        std::function<void(uint16_t)> check_group) {
-  CheckDuplicateGroup group_set;
-  uint32_t tmp;
-  EXPECT_TRUE(shares.Read(0, 2, &tmp));
-  EXPECT_EQ(shares.len() - 2, static_cast<size_t>(tmp));
-  size_t i;
-  for (i = 2; i < shares.len(); i += 4 + tmp) {
-    ASSERT_TRUE(shares.Read(i, 2, &tmp));
-    uint16_t group = static_cast<uint16_t>(tmp);
-    group_set.AddAndCheckGroup(group);
-    check_group(group);
-    ASSERT_TRUE(shares.Read(i + 2, 2, &tmp));
-  }
-  EXPECT_EQ(shares.len(), i);
-}
-
 TEST_P(TlsConnectTls13, SharesForBothEcdheAndDhe) {
   EnsureTlsSetup();
   client_->DisableAllCiphers();
   client_->EnableCiphersByKeyExchange(ssl_kea_ecdh);
   client_->EnableCiphersByKeyExchange(ssl_kea_dh);
 
   auto groups_capture = new TlsExtensionCapture(ssl_supported_groups_xtn);
   auto shares_capture = new TlsExtensionCapture(ssl_tls13_key_share_xtn);
@@ -85,17 +39,17 @@ TEST_P(TlsConnectTls13, SharesForBothEcd
   captures.push_back(shares_capture);
   client_->SetPacketFilter(new ChainedPacketFilter(captures));
 
   Connect();
 
   CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign);
 
   bool ec, dh;
-  auto track_group_type = [&ec, &dh](uint16_t group) {
+  auto track_group_type = [&ec, &dh](SSLNamedGroup group) {
     if ((group & 0xff00U) == 0x100U) {
       dh = true;
     } else {
       ec = true;
     }
   };
   CheckGroups(groups_capture->extension(), track_group_type);
   CheckShares(shares_capture->extension(), track_group_type);
@@ -113,17 +67,17 @@ TEST_P(TlsConnectTls13, NoDheOnEcdheConn
   std::vector<PacketFilter*> captures;
   captures.push_back(groups_capture);
   captures.push_back(shares_capture);
   client_->SetPacketFilter(new ChainedPacketFilter(captures));
 
   Connect();
 
   CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign);
-  auto is_ecc = [](uint16_t group) { EXPECT_NE(0x100U, group & 0xff00U); };
+  auto is_ecc = [](SSLNamedGroup group) { EXPECT_NE(0x100U, group & 0xff00U); };
   CheckGroups(groups_capture->extension(), is_ecc);
   CheckShares(shares_capture->extension(), is_ecc);
 }
 
 TEST_P(TlsConnectGeneric, ConnectFfdheClient) {
   EnableOnlyDheCiphers();
   EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(),
                                       SSL_REQUIRE_DH_NAMED_GROUPS, PR_TRUE));
@@ -132,17 +86,17 @@ TEST_P(TlsConnectGeneric, ConnectFfdheCl
   std::vector<PacketFilter*> captures;
   captures.push_back(groups_capture);
   captures.push_back(shares_capture);
   client_->SetPacketFilter(new ChainedPacketFilter(captures));
 
   Connect();
 
   CheckKeys(ssl_kea_dh, ssl_auth_rsa_sign);
-  auto is_ffdhe = [](uint16_t group) {
+  auto is_ffdhe = [](SSLNamedGroup group) {
     // The group has to be in this range.
     EXPECT_LE(ssl_grp_ffdhe_2048, group);
     EXPECT_GE(ssl_grp_ffdhe_8192, group);
   };
   CheckGroups(groups_capture->extension(), is_ffdhe);
   if (version_ == SSL_LIBRARY_VERSION_TLS_1_3) {
     CheckShares(shares_capture->extension(), is_ffdhe);
   } else {
@@ -502,32 +456,49 @@ TEST_P(TlsConnectGenericPre13, WeakDHGro
   EXPECT_EQ(SECSuccess,
             SSL_EnableWeakDHEPrimeGroup(server_->ssl_fd(), PR_TRUE));
 
   Connect();
 }
 
 TEST_P(TlsConnectGeneric, Ffdhe3072) {
   EnableOnlyDheCiphers();
-  client_->ConfigNamedGroup(ssl_grp_ffdhe_2048, false);
+  SSLNamedGroup groups[] = {ssl_grp_ffdhe_3072};
+  client_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
 
   Connect();
 }
 
 TEST_P(TlsConnectGenericPre13, PreferredFfdhe) {
   EnableOnlyDheCiphers();
   static const SSLDHEGroupType groups[] = {ssl_ff_dhe_3072_group,
                                            ssl_ff_dhe_2048_group};
   EXPECT_EQ(SECSuccess, SSL_DHEGroupPrefSet(server_->ssl_fd(), groups,
                                             PR_ARRAY_SIZE(groups)));
 
   Connect();
   CheckKeys(ssl_kea_dh, ssl_auth_rsa_sign, 3072);
 }
 
+TEST_P(TlsConnectGenericPre13, MismatchDHE) {
+  EnableOnlyDheCiphers();
+  EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(),
+                                      SSL_REQUIRE_DH_NAMED_GROUPS, PR_TRUE));
+  static const SSLDHEGroupType serverGroups[] = {ssl_ff_dhe_3072_group};
+  EXPECT_EQ(SECSuccess, SSL_DHEGroupPrefSet(server_->ssl_fd(), serverGroups,
+                                            PR_ARRAY_SIZE(serverGroups)));
+  static const SSLDHEGroupType clientGroups[] = {ssl_ff_dhe_2048_group};
+  EXPECT_EQ(SECSuccess, SSL_DHEGroupPrefSet(client_->ssl_fd(), clientGroups,
+                                            PR_ARRAY_SIZE(clientGroups)));
+
+  ConnectExpectFail();
+  server_->CheckErrorCode(SSL_ERROR_NO_CYPHER_OVERLAP);
+  client_->CheckErrorCode(SSL_ERROR_NO_CYPHER_OVERLAP);
+}
+
 TEST_P(TlsConnectTls13, ResumeFfdhe) {
   EnableOnlyDheCiphers();
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   Connect();
   SendReceive();  // Need to read so that we absorb the session ticket.
   CheckKeys(ssl_kea_dh, ssl_auth_rsa_sign);
 
   Reset();
--- a/external_tests/ssl_gtest/ssl_ecdh_unittest.cc
+++ b/external_tests/ssl_gtest/ssl_ecdh_unittest.cc
@@ -54,47 +54,231 @@ TEST_P(TlsConnectGeneric, ConnectEcdhe) 
 TEST_P(TlsConnectTls12Plus, ConnectEcdheP384) {
   Reset(TlsAgent::kServerEcdsa384);
   ConnectWithCipherSuite(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256);
   CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa, 384);
 }
 
 TEST_P(TlsConnectGeneric, ConnectEcdheP384Client) {
   EnsureTlsSetup();
-  client_->ConfigNamedGroup(ssl_grp_ec_secp256r1, false);
+  const SSLNamedGroup groups[] = {ssl_grp_ec_secp384r1, ssl_grp_ffdhe_2048};
+  client_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
+  server_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
   Connect();
   CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
 }
 
 // This causes a HelloRetryRequest in TLS 1.3.  Earlier versions don't care.
 TEST_P(TlsConnectGeneric, ConnectEcdheP384Server) {
   EnsureTlsSetup();
   auto hrr_capture =
       new TlsInspectorRecordHandshakeMessage(kTlsHandshakeHelloRetryRequest);
   server_->SetPacketFilter(hrr_capture);
-  server_->ConfigNamedGroup(ssl_grp_ec_secp256r1, false);
+  const SSLNamedGroup groups[] = {ssl_grp_ec_secp384r1};
+  server_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
   Connect();
   CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
   EXPECT_EQ(version_ == SSL_LIBRARY_VERSION_TLS_1_3,
             hrr_capture->buffer().len() != 0);
 }
 
 // This enables only P-256 on the client and disables it on the server.
 // This test will fail when we add other groups that identify as ECDHE.
 TEST_P(TlsConnectGeneric, ConnectEcdheGroupMismatch) {
   EnsureTlsSetup();
-  client_->ConfigNamedGroup(ssl_grp_ec_secp256r1, true);
-  client_->ConfigNamedGroup(ssl_grp_ec_secp384r1, false);
-  client_->ConfigNamedGroup(ssl_grp_ec_secp521r1, false);
-  server_->ConfigNamedGroup(ssl_grp_ec_secp256r1, false);
+  const SSLNamedGroup clientGroups[] = {ssl_grp_ec_secp256r1,
+                                        ssl_grp_ffdhe_2048};
+  const SSLNamedGroup serverGroups[] = {ssl_grp_ffdhe_2048};
+  client_->ConfigNamedGroups(clientGroups, PR_ARRAY_SIZE(clientGroups));
+  server_->ConfigNamedGroups(serverGroups, PR_ARRAY_SIZE(serverGroups));
 
   Connect();
   CheckKeys(ssl_kea_dh, ssl_auth_rsa_sign);
 }
 
+class TlsKeyExchangeTest : public TlsConnectGeneric {
+ protected:
+  TlsExtensionCapture *groups_capture_;
+  TlsExtensionCapture *shares_capture_;
+  TlsInspectorRecordHandshakeMessage *capture_hrr_;
+
+  void EnsureKeyShareSetup(const SSLNamedGroup *groups, size_t num) {
+    EnsureTlsSetup();
+    groups_capture_ = new TlsExtensionCapture(ssl_supported_groups_xtn);
+    shares_capture_ = new TlsExtensionCapture(ssl_tls13_key_share_xtn);
+    std::vector<PacketFilter *> captures;
+    captures.push_back(groups_capture_);
+    captures.push_back(shares_capture_);
+    client_->SetPacketFilter(new ChainedPacketFilter(captures));
+    capture_hrr_ =
+        new TlsInspectorRecordHandshakeMessage(kTlsHandshakeHelloRetryRequest);
+    server_->SetPacketFilter(capture_hrr_);
+
+    if (groups) {
+      client_->ConfigNamedGroups(groups, num);
+      server_->ConfigNamedGroups(groups, num);
+    }
+  }
+
+  std::vector<SSLNamedGroup> GetGroupDetails(const DataBuffer &ext) {
+    uint32_t tmp = 0;
+    EXPECT_TRUE(ext.Read(0, 2, &tmp));
+    EXPECT_EQ(ext.len() - 2, static_cast<size_t>(tmp));
+    EXPECT_TRUE(ext.len() % 2 == 0);
+    std::vector<SSLNamedGroup> groups;
+    for (size_t i = 1; i < ext.len() / 2; i += 1) {
+      EXPECT_TRUE(ext.Read(2 * i, 2, &tmp));
+      groups.push_back(static_cast<SSLNamedGroup>(tmp));
+    }
+    return groups;
+  }
+
+  std::vector<SSLNamedGroup> GetShareDetails(const DataBuffer &ext) {
+    uint32_t tmp = 0;
+    EXPECT_TRUE(ext.Read(0, 2, &tmp));
+    EXPECT_EQ(ext.len() - 2, static_cast<size_t>(tmp));
+    std::vector<SSLNamedGroup> shares;
+    size_t i = 2;
+    while (i < ext.len()) {
+      EXPECT_TRUE(ext.Read(i, 2, &tmp));
+      shares.push_back(static_cast<SSLNamedGroup>(tmp));
+      EXPECT_TRUE(ext.Read(i + 2, 2, &tmp));
+      i += 4 + tmp;
+    }
+    EXPECT_EQ(ext.len(), i);
+    return shares;
+  }
+
+  void CheckKEXDetails(std::vector<SSLNamedGroup> expectedGroups,
+                       std::vector<SSLNamedGroup> expectedShares) {
+    std::vector<SSLNamedGroup> groups =
+        GetGroupDetails(groups_capture_->extension());
+    EXPECT_EQ(expectedGroups, groups);
+
+    if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+      ASSERT_TRUE(expectedShares.size());
+      std::vector<SSLNamedGroup> shares =
+          GetShareDetails(shares_capture_->extension());
+      EXPECT_EQ(expectedShares, shares);
+    } else {
+      EXPECT_EQ(0U, shares_capture_->extension().len());
+    }
+
+    EXPECT_EQ(0U, capture_hrr_->buffer().len())
+        << "we didn't expect a hello retry request.";
+  }
+};
+
+TEST_P(TlsKeyExchangeTest, P384Priority) {
+  /* P256, P384 and P521 are enabled. Both prefer P384. */
+  const SSLNamedGroup groups[] = {ssl_grp_ec_secp384r1, ssl_grp_ec_secp256r1,
+                                  ssl_grp_ec_secp521r1};
+  EnsureKeyShareSetup(groups, PR_ARRAY_SIZE(groups));
+  client_->DisableAllCiphers();
+  client_->EnableCiphersByKeyExchange(ssl_kea_ecdh);
+  Connect();
+
+  CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
+
+  std::vector<SSLNamedGroup> shares = {ssl_grp_ec_secp384r1};
+  std::vector<SSLNamedGroup> expected_groups(groups,
+                                             groups + PR_ARRAY_SIZE(groups));
+  CheckKEXDetails(expected_groups, shares);
+}
+
+TEST_P(TlsKeyExchangeTest, DuplicateGroupConfig) {
+  const SSLNamedGroup groups[] = {ssl_grp_ec_secp384r1, ssl_grp_ec_secp384r1,
+                                  ssl_grp_ec_secp384r1, ssl_grp_ec_secp256r1,
+                                  ssl_grp_ec_secp256r1};
+  EnsureKeyShareSetup(groups, PR_ARRAY_SIZE(groups));
+  client_->DisableAllCiphers();
+  client_->EnableCiphersByKeyExchange(ssl_kea_ecdh);
+  Connect();
+
+  CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
+
+  std::vector<SSLNamedGroup> shares = {ssl_grp_ec_secp384r1};
+  std::vector<SSLNamedGroup> expectedGroups = {ssl_grp_ec_secp384r1,
+                                               ssl_grp_ec_secp256r1};
+  CheckKEXDetails(expectedGroups, shares);
+}
+
+TEST_P(TlsKeyExchangeTest, P384PriorityDHEnabled) {
+  /* P256, P384,  P521, and FFDHE2048 are enabled. Both prefer P384. */
+  const SSLNamedGroup groups[] = {ssl_grp_ec_secp384r1, ssl_grp_ffdhe_2048,
+                                  ssl_grp_ec_secp256r1, ssl_grp_ec_secp521r1};
+  EnsureKeyShareSetup(groups, PR_ARRAY_SIZE(groups));
+  Connect();
+
+  CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
+
+  if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+    std::vector<SSLNamedGroup> shares = {ssl_grp_ec_secp384r1};
+    std::vector<SSLNamedGroup> expected_groups(groups,
+                                               groups + PR_ARRAY_SIZE(groups));
+    CheckKEXDetails(expected_groups, shares);
+  } else {
+    std::vector<SSLNamedGroup> oldtlsgroups = {
+        ssl_grp_ec_secp384r1, ssl_grp_ec_secp256r1, ssl_grp_ec_secp521r1};
+    CheckKEXDetails(oldtlsgroups, std::vector<SSLNamedGroup>());
+  }
+}
+
+TEST_P(TlsConnectGenericPre13, P384PriorityOnServer) {
+  EnsureTlsSetup();
+  client_->DisableAllCiphers();
+  client_->EnableCiphersByKeyExchange(ssl_kea_ecdh);
+
+  /* The server prefers P384. It has to win. */
+  const SSLNamedGroup serverGroups[] = {
+      ssl_grp_ec_secp384r1, ssl_grp_ec_secp256r1, ssl_grp_ec_secp521r1};
+  server_->ConfigNamedGroups(serverGroups, PR_ARRAY_SIZE(serverGroups));
+
+  Connect();
+
+  CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
+}
+
+TEST_P(TlsConnectGenericPre13, P384PriorityFromModelSocket) {
+  EnsureModelSockets();
+
+  /* Both prefer P384, set on the model socket. */
+  const SSLNamedGroup groups[] = {ssl_grp_ec_secp384r1, ssl_grp_ec_secp256r1,
+                                  ssl_grp_ec_secp521r1, ssl_grp_ffdhe_2048};
+  client_model_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
+  server_model_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
+
+  Connect();
+
+  CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
+}
+
+TEST_P(TlsConnectStreamPre13, ConfiguredGroupsRenegotiate) {
+  EnsureTlsSetup();
+  client_->DisableAllCiphers();
+  client_->EnableCiphersByKeyExchange(ssl_kea_ecdh);
+
+  const SSLNamedGroup serverGroups[] = {ssl_grp_ec_secp256r1,
+                                        ssl_grp_ec_secp384r1};
+  const SSLNamedGroup clientGroups[] = {ssl_grp_ec_secp384r1};
+  server_->ConfigNamedGroups(clientGroups, PR_ARRAY_SIZE(clientGroups));
+  client_->ConfigNamedGroups(serverGroups, PR_ARRAY_SIZE(serverGroups));
+
+  Connect();
+
+  CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
+  CheckConnected();
+
+  // The renegotiation has to use the same preferences as the original session.
+  server_->PrepareForRenegotiate();
+  client_->StartRenegotiate();
+  Handshake();
+  CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign, 384);
+}
+
 // Replace the point in the client key exchange message with an empty one
 class ECCClientKEXFilter : public TlsHandshakeFilter {
  public:
   ECCClientKEXFilter() {}
 
  protected:
   virtual PacketFilter::Action FilterHandshake(const HandshakeHeader &header,
                                                const DataBuffer &input,
@@ -143,9 +327,13 @@ TEST_P(TlsConnectGenericPre13, ConnectEC
 
 TEST_P(TlsConnectGenericPre13, ConnectECDHEmptyClientPoint) {
   // add packet filter
   client_->SetPacketFilter(new ECCClientKEXFilter());
   ConnectExpectFail();
   server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_KEY_EXCH);
 }
 
-}  // namespace nspr_test
+INSTANTIATE_TEST_CASE_P(KeyExchangeTest, TlsKeyExchangeTest,
+                        ::testing::Combine(TlsConnectTestBase::kTlsModesAll,
+                                           TlsConnectTestBase::kTlsV11Plus));
+
+}  // namespace nss_test
--- a/external_tests/ssl_gtest/ssl_hrr_unittest.cc
+++ b/external_tests/ssl_gtest/ssl_hrr_unittest.cc
@@ -18,17 +18,18 @@
 namespace nss_test {
 
 TEST_P(TlsConnectTls13, HelloRetryRequestAbortsZeroRtt) {
   const char* k0RttData = "Such is life";
   const PRInt32 k0RttDataLen = static_cast<PRInt32>(strlen(k0RttData));
 
   SetupForZeroRtt();  // initial handshake as normal
 
-  server_->ConfigNamedGroup(ssl_grp_ec_secp256r1, false);
+  const SSLNamedGroup groups[2] = {ssl_grp_ec_secp384r1, ssl_grp_ec_secp521r1};
+  server_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
   client_->Set0RttEnabled(true);
   server_->Set0RttEnabled(true);
   ExpectResumption(RESUME_TICKET);
 
   // Send first ClientHello and send 0-RTT data
   auto capture_early_data = new TlsExtensionCapture(ssl_tls13_early_data_xtn);
   client_->SetPacketFilter(capture_early_data);
   client_->Handshake();
@@ -85,37 +86,40 @@ class KeyShareReplayer : public TlsExten
 };
 
 // This forces a HelloRetryRequest by disabling P-256 on the server.  However,
 // the second ClientHello is modified so that it omits the requested share.  The
 // server should reject this.
 TEST_P(TlsConnectTls13, RetryWithSameKeyShare) {
   EnsureTlsSetup();
   client_->SetPacketFilter(new KeyShareReplayer());
-  server_->ConfigNamedGroup(ssl_grp_ec_secp256r1, false);
+  const SSLNamedGroup groups[2] = {ssl_grp_ec_secp384r1, ssl_grp_ec_secp521r1};
+  server_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
   ConnectExpectFail();
   EXPECT_EQ(SSL_ERROR_BAD_2ND_CLIENT_HELLO, server_->error_code());
   EXPECT_EQ(SSL_ERROR_ILLEGAL_PARAMETER_ALERT, client_->error_code());
 }
 
 // This tests that the second attempt at sending a ClientHello (after receiving
 // a HelloRetryRequest) is correctly retransmitted.
 TEST_F(TlsConnectDatagram13, DropClientSecondFlightWithHelloRetry) {
-  client_->ConfigNamedGroup(ssl_grp_ec_secp256r1, false);
+  const SSLNamedGroup groups[2] = {ssl_grp_ec_secp384r1, ssl_grp_ec_secp521r1};
+  server_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
   server_->SetPacketFilter(new SelectiveDropFilter(0x2));
   Connect();
 }
 
 TEST_F(TlsConnectTest, Select12AfterHelloRetryRequest) {
   EnsureTlsSetup();
   client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
                            SSL_LIBRARY_VERSION_TLS_1_3);
   server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
                            SSL_LIBRARY_VERSION_TLS_1_3);
-  server_->ConfigNamedGroup(ssl_grp_ec_secp256r1, false);
+  const SSLNamedGroup groups[2] = {ssl_grp_ec_secp384r1, ssl_grp_ec_secp521r1};
+  server_->ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
   client_->StartConnect();
   server_->StartConnect();
 
   client_->Handshake();
   server_->Handshake();
 
   // Here we replace the TLS server with one that does TLS 1.2 only.
   // This will happily send the client a TLS 1.2 ServerHello.
--- a/external_tests/ssl_gtest/ssl_loopback_unittest.cc
+++ b/external_tests/ssl_gtest/ssl_loopback_unittest.cc
@@ -48,16 +48,18 @@ TEST_P(TlsConnectGenericPre13, ConnectFa
 TEST_P(TlsConnectGeneric, ConnectAlpn) {
   EnableAlpn();
   Connect();
   CheckAlpn("a");
 }
 
 TEST_P(TlsConnectGeneric, ConnectAlpnClone) {
   EnsureModelSockets();
+  client_model_->EnableAlpn(alpn_dummy_val_, sizeof(alpn_dummy_val_));
+  server_model_->EnableAlpn(alpn_dummy_val_, sizeof(alpn_dummy_val_));
   Connect();
   CheckAlpn("a");
 }
 
 TEST_P(TlsConnectDatagram, ConnectSrtp) {
   EnableSrtp();
   Connect();
   CheckSrtp();
--- a/external_tests/ssl_gtest/tls_agent.cc
+++ b/external_tests/ssl_gtest/tls_agent.cc
@@ -123,28 +123,19 @@ bool TlsAgent::ConfigServerCert(const st
                             serverCertData ? sizeof(*serverCertData) : 0);
   return rv == SECSuccess;
 }
 
 // The tests expect that only curves secp256r1, secp384r1, and secp521r1
 // (NIST P-256, P-384, and P-521) are enabled. Disable all other curves.
 void TlsAgent::DisableLameGroups() {
 #ifdef NSS_ECC_MORE_THAN_SUITE_B
-  static const SSLNamedGroup lame_groups[] = {
-      ssl_grp_ec_sect163k1, ssl_grp_ec_sect163r1, ssl_grp_ec_sect163r2,
-      ssl_grp_ec_sect193r1, ssl_grp_ec_sect193r2, ssl_grp_ec_sect233k1,
-      ssl_grp_ec_sect233r1, ssl_grp_ec_sect239k1, ssl_grp_ec_sect283k1,
-      ssl_grp_ec_sect283r1, ssl_grp_ec_sect409k1, ssl_grp_ec_sect409r1,
-      ssl_grp_ec_sect571k1, ssl_grp_ec_sect571r1, ssl_grp_ec_secp160k1,
-      ssl_grp_ec_secp160r1, ssl_grp_ec_secp160r2, ssl_grp_ec_secp192k1,
-      ssl_grp_ec_secp192r1, ssl_grp_ec_secp224k1, ssl_grp_ec_secp224r1,
-      ssl_grp_ec_secp256k1};
-  for (size_t i = 0; i < PR_ARRAY_SIZE(lame_groups); ++i) {
-    ConfigNamedGroup(lame_groups[i], false);
-  }
+  static const SSLNamedGroup groups[] = {
+      ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1, ssl_grp_ec_secp521r1};
+  ConfigNamedGroups(groups, PR_ARRAY_SIZE(groups));
 #endif
 }
 
 bool TlsAgent::EnsureTlsSetup(PRFileDesc* modelSocket) {
   // Don't set up twice
   if (ssl_fd_) return true;
 
   if (adapter_->mode() == STREAM) {
@@ -292,19 +283,19 @@ void TlsAgent::EnableCiphersByAuthType(S
 }
 
 void TlsAgent::EnableSingleCipher(uint16_t cipher) {
   DisableAllCiphers();
   SECStatus rv = SSL_CipherPrefSet(ssl_fd_, cipher, PR_TRUE);
   EXPECT_EQ(SECSuccess, rv);
 }
 
-void TlsAgent::ConfigNamedGroup(SSLNamedGroup group, bool en) {
+void TlsAgent::ConfigNamedGroups(const SSLNamedGroup* groups, size_t num) {
   EXPECT_TRUE(EnsureTlsSetup());
-  SECStatus rv = SSL_NamedGroupPrefSet(ssl_fd_, group, en ? PR_TRUE : PR_FALSE);
+  SECStatus rv = SSL_NamedGroupConfig(ssl_fd_, groups, num);
   EXPECT_EQ(SECSuccess, rv);
 }
 
 void TlsAgent::SetSessionTicketsEnabled(bool en) {
   EXPECT_TRUE(EnsureTlsSetup());
 
   SECStatus rv = SSL_OptionSet(ssl_fd_, SSL_ENABLE_SESSION_TICKETS,
                                en ? PR_TRUE : PR_FALSE);
--- a/external_tests/ssl_gtest/tls_agent.h
+++ b/external_tests/ssl_gtest/tls_agent.h
@@ -136,17 +136,17 @@ class TlsAgent : public PollTarget {
   void ResetSentBytes();  // Hack to test drops.
   void EnableExtendedMasterSecret();
   void CheckExtendedMasterSecret(bool expected);
   void CheckEarlyDataAccepted(bool expected);
   void DisableRollbackDetection();
   void EnableCompression();
   void SetDowngradeCheckVersion(uint16_t version);
   void CheckSecretsDestroyed();
-  void ConfigNamedGroup(SSLNamedGroup group, bool en);
+  void ConfigNamedGroups(const SSLNamedGroup* groups, size_t num);
 
   const std::string& name() const { return name_; }
 
   Role role() const { return role_; }
   std::string role_str() const { return role_ == SERVER ? "server" : "client"; }
 
   State state() const { return state_; }
 
--- a/external_tests/ssl_gtest/tls_connect.cc
+++ b/external_tests/ssl_gtest/tls_connect.cc
@@ -6,16 +6,17 @@
 
 #include "tls_connect.h"
 extern "C" {
 #include "libssl_internals.h"
 }
 
 #include <iostream>
 
+#include "databuffer.h"
 #include "gtest_utils.h"
 #include "sslproto.h"
 
 extern std::string g_working_dir_path;
 
 namespace nss_test {
 
 static const std::string kTlsModesStreamArr[] = {"TLS"};
@@ -115,16 +116,49 @@ TlsConnectTestBase::TlsConnectTestBase(M
   } else {
     v = VersionString(version_);
   }
   std::cerr << "Version: " << mode_ << " " << v << std::endl;
 }
 
 TlsConnectTestBase::~TlsConnectTestBase() {}
 
+// Check the group of each of the supported groups
+void TlsConnectTestBase::CheckGroups(
+    const DataBuffer& groups, std::function<void(SSLNamedGroup)> check_group) {
+  DuplicateGroupChecker group_set;
+  uint32_t tmp = 0;
+  EXPECT_TRUE(groups.Read(0, 2, &tmp));
+  EXPECT_EQ(groups.len() - 2, static_cast<size_t>(tmp));
+  for (size_t i = 2; i < groups.len(); i += 2) {
+    EXPECT_TRUE(groups.Read(i, 2, &tmp));
+    SSLNamedGroup group = static_cast<SSLNamedGroup>(tmp);
+    group_set.AddAndCheckGroup(group);
+    check_group(group);
+  }
+}
+
+// Check the group of each of the shares
+void TlsConnectTestBase::CheckShares(
+    const DataBuffer& shares, std::function<void(SSLNamedGroup)> check_group) {
+  DuplicateGroupChecker group_set;
+  uint32_t tmp = 0;
+  EXPECT_TRUE(shares.Read(0, 2, &tmp));
+  EXPECT_EQ(shares.len() - 2, static_cast<size_t>(tmp));
+  size_t i;
+  for (i = 2; i < shares.len(); i += 4 + tmp) {
+    ASSERT_TRUE(shares.Read(i, 2, &tmp));
+    SSLNamedGroup group = static_cast<SSLNamedGroup>(tmp);
+    group_set.AddAndCheckGroup(group);
+    check_group(group);
+    ASSERT_TRUE(shares.Read(i + 2, 2, &tmp));
+  }
+  EXPECT_EQ(shares.len(), i);
+}
+
 void TlsConnectTestBase::ClearStats() {
   // Clear statistics.
   SSL3Statistics* stats = SSL_GetStatistics();
   memset(stats, 0, sizeof(*stats));
 }
 
 void TlsConnectTestBase::ClearServerCache() {
   SSL_ShutdownServerSessionIDCache();
@@ -384,21 +418,16 @@ void TlsConnectTestBase::EnsureModelSock
     ASSERT_EQ(server_model_, nullptr);
     client_model_ = new TlsAgent(TlsAgent::kClient, TlsAgent::CLIENT, mode_);
     server_model_ = new TlsAgent(TlsAgent::kServerRsa, TlsAgent::SERVER, mode_);
   }
 
   // Initialise agents.
   ASSERT_TRUE(client_model_->Init());
   ASSERT_TRUE(server_model_->Init());
-
-  // Set desired properties on the models.
-  // For now only ALPN.
-  client_model_->EnableAlpn(alpn_dummy_val_, sizeof(alpn_dummy_val_));
-  server_model_->EnableAlpn(alpn_dummy_val_, sizeof(alpn_dummy_val_));
 }
 
 void TlsConnectTestBase::CheckAlpn(const std::string& val) {
   client_->CheckAlpn(SSL_NEXT_PROTO_SELECTED, val);
   server_->CheckAlpn(SSL_NEXT_PROTO_NEGOTIATED, val);
 }
 
 void TlsConnectTestBase::EnableSrtp() {
--- a/external_tests/ssl_gtest/tls_connect.h
+++ b/external_tests/ssl_gtest/tls_connect.h
@@ -68,16 +68,20 @@ class TlsConnectTestBase : public ::test
   void Connect();
   // Check that the connection was successfully established.
   void CheckConnected();
   // Connect and expect it to fail.
   void ConnectExpectFail();
   void ConnectWithCipherSuite(uint16_t cipher_suite);
   void CheckKeys(SSLKEAType kea_type, SSLAuthType auth_type,
                  size_t kea_size = 0) const;
+  void CheckGroups(const DataBuffer& groups,
+                   std::function<void(SSLNamedGroup)> check_group);
+  void CheckShares(const DataBuffer& shares,
+                   std::function<void(SSLNamedGroup)> check_group);
 
   void SetExpectedVersion(uint16_t version);
   // Expect resumption of a particular type.
   void ExpectResumption(SessionResumptionMode expected);
   void DisableAllCiphers();
   void EnableOnlyStaticRsaCiphers();
   void EnableOnlyDheCiphers();
   void EnableSomeEcdhCiphers();
@@ -117,16 +121,29 @@ class TlsConnectTestBase : public ::test
 
  private:
   void CheckResumption(SessionResumptionMode expected);
   void CheckExtendedMasterSecret();
   void CheckEarlyDataAccepted();
 
   bool expect_extended_master_secret_;
   bool expect_early_data_accepted_;
+
+  // Track groups and make sure that there are no duplicates.
+  class DuplicateGroupChecker {
+   public:
+    void AddAndCheckGroup(SSLNamedGroup group) {
+      EXPECT_EQ(groups_.end(), groups_.find(group))
+          << "Group " << group << " should not be duplicated";
+      groups_.insert(group);
+    }
+
+   private:
+    std::set<SSLNamedGroup> groups_;
+  };
 };
 
 // A non-parametrized TLS test base.
 class TlsConnectTest : public TlsConnectTestBase {
  public:
   TlsConnectTest() : TlsConnectTestBase(STREAM, 0) {}
 };
 
--- a/lib/ssl/derive.c
+++ b/lib/ssl/derive.c
@@ -785,19 +785,17 @@ SSL_CanBypass(CERTCertificate *cert, SEC
                 serverKeyStrengthInBits *= BPB;
 
                 signatureKeyStrength =
                     SSL_RSASTRENGTH_TO_ECSTRENGTH(serverKeyStrengthInBits);
 
                 if (requiredECCbits > signatureKeyStrength)
                     requiredECCbits = signatureKeyStrength;
 
-                ecGroup =
-                    ssl_GetECGroupWithStrength(PR_UINT32_MAX,
-                                               requiredECCbits);
+                ecGroup = ssl_GetECGroupWithStrength(NULL, requiredECCbits);
                 rv = ssl_NamedGroup2ECParams(NULL, ecGroup, &ecParams);
                 if (rv == SECFailure) {
                     break;
                 }
                 pecParams = &ecParams;
             }
 
             if (testecdhe) {
--- a/lib/ssl/ssl.def
+++ b/lib/ssl/ssl.def
@@ -203,12 +203,12 @@ SSL_SetDowngradeCheckVersion;
 ;+NSS_3.24 {    # NSS 3.24 release
 ;+    global:
 SSL_ConfigServerCert;
 ;+    local:
 ;+*;
 ;+};
 ;+NSS_3.27 {    # NSS 3.27 release
 ;+    global:
-SSL_NamedGroupPrefSet;
+SSL_NamedGroupConfig;
 ;+    local:
 ;+*;
 ;+};
--- a/lib/ssl/ssl.h
+++ b/lib/ssl/ssl.h
@@ -211,17 +211,17 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRF
  * (formerly supported_curves) to signal which pre-defined groups are OK.
  *
  * This option causes an NSS client to use this extension and demand that those
  * groups be used.  A client will signal any enabled DHE groups in the
  * supported_groups extension and reject groups that don't match what it has
  * enabled.  A server will only negotiate TLS_DHE_* cipher suites if the
  * client includes the extension.
  *
- * See SSL_NamedGroupPrefSet() for how to control which groups are enabled.
+ * See SSL_NamedGroupConfig() for how to control which groups are enabled.
  *
  * This option cannot be enabled if NSS is not compiled with ECC support.
  */
 #define SSL_REQUIRE_DH_NAMED_GROUPS 32
 
 /* Allow 0-RTT data (for TLS 1.3).
  *
  * When this option is set, the server's session tickets will contain
@@ -387,26 +387,26 @@ SSL_IMPORT SECStatus SSL_SignaturePrefGe
 
 /*
 ** Returns the maximum number of signature algorithms that are supported and
 ** can be set or retrieved using SSL_SignaturePrefSet or SSL_SignaturePrefGet.
 */
 SSL_IMPORT unsigned int SSL_SignatureMaxCount();
 
 /*
-** Enable or disable a named group (see SSLNamedGroup).  This includes both EC
-** and FF groups using in Diffie-Hellman key exchange, as well as the EC groups
-** used in ECDSA signatures.  By default libssl enables all supported groups.
-** NSS uses its own preferences to select a group though it will select the
-** first group from SSL_DHEGroupPrefSet if that was called.
+** Define custom priorities for EC and FF groups used in DH key exchange and EC
+** groups for ECDSA. This only changes the order of enabled lists (and thus
+** their priorities) and enables all groups in |groups| while disabling all other
+** groups.
 */
-SSL_IMPORT SECStatus SSL_NamedGroupPrefSet(PRFileDesc *fd, SSLNamedGroup group,
-                                           PRBool enable);
+SSL_IMPORT SECStatus SSL_NamedGroupConfig(PRFileDesc *fd,
+                                          const SSLNamedGroup *groups,
+                                          unsigned int num_groups);
 
-/* Deprecated: use SSL_NamedGroupPrefSet() instead.
+/* Deprecated: use SSL_NamedGroupConfig() instead.
 ** SSL_DHEGroupPrefSet is used to configure the set of allowed/enabled DHE group
 ** parameters that can be used by NSS for the given server socket.
 ** The first item in the array is used as the default group, if no other
 ** selection criteria can be used by NSS.
 ** The set is provided as an array of identifiers as defined by SSLDHEGroupType.
 ** If more than one group identifier is provided, NSS will select the one to use.
 ** For example, a TLS extension sent by the client might indicate a preference.
 */
@@ -427,21 +427,21 @@ SSL_IMPORT SECStatus SSL_DHEGroupPrefSet
 ** It is allowed to call this API will a NULL value for parameter fd,
 ** which will prepare the global parameters that NSS will reuse for the remainder
 ** of the process lifetime. This can be used early after startup of a process,
 ** to avoid a delay when handling incoming client connections.
 ** This preparation with a NULL for parameter fd will NOT enable the weak group
 ** on sockets. The function needs to be called again for every socket that
 ** should use the weak group.
 **
-** It is allowed to use this API in combination with the SSL_NamedGroupPrefSet API.
-** If both APIs have been called, the weakest group will be used,
-** unless it is certain that the client supports larger group parameters.
-** The weak group will be used as the default group, overriding the preference
-** for the first group potentially set with a call to SSL_DHEGroupPrefSet.
+** It is allowed to use this API in combination with the SSL_NamedGroupConfig API.
+** If both APIs have been called, the weakest group will be used, unless it is
+** certain that the client supports larger group parameters. The weak group will
+** be used as the default group for TLS <= 1.2, overriding the preference for
+** the first group potentially set with a call to SSL_NamedGroupConfig.
 */
 SSL_IMPORT SECStatus SSL_EnableWeakDHEPrimeGroup(PRFileDesc *fd, PRBool enabled);
 
 /* SSL Version Range API
 **
 ** This API should be used to control SSL 3.0 & TLS support instead of the
 ** older SSL_Option* API; however, the SSL_Option* API MUST still be used to
 ** control SSL 2.0 support. In this version of libssl, SSL 3.0 and TLS 1.0 are
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -833,19 +833,20 @@ ssl_LookupCipherSuiteCfg(ssl3CipherSuite
     PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
     return NULL;
 }
 
 static PRBool
 ssl_NamedGroupTypeEnabled(const sslSocket *ss, NamedGroupType groupType)
 {
     unsigned int i;
-    for (i = 0; i < ssl_named_group_count; ++i) {
-        if (ssl_named_groups[i].type == groupType &&
-            ssl_NamedGroupEnabled(ss, &ssl_named_groups[i])) {
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        if (ss->namedGroupPreferences[i] &&
+            ss->namedGroupPreferences[i]->type == groupType &&
+            ssl_NamedGroupEnabled(ss, ss->namedGroupPreferences[i])) {
             return PR_TRUE;
         }
     }
     return PR_FALSE;
 }
 
 static PRBool
 ssl_KEAEnabled(const sslSocket *ss, SSLKEAType keaType)
@@ -859,16 +860,21 @@ ssl_KEAEnabled(const sslSocket *ss, SSLK
             if (ss->sec.isServer && !ss->opt.enableServerDhe) {
                 return PR_FALSE;
             }
 
             /* No need to check for a common FFDHE group if we are in TLS 1.2 or
              * earlier and named groups aren't required. */
             if (!ss->opt.requireDHENamedGroups &&
                 ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
+                /* If the client indicates support for named FFDHE groups, check
+                 * that we have one in common. */
+                if (ss->sec.isServer && ss->ssl3.hs.peerSupportsFfdheGroups) {
+                    return ssl_NamedGroupTypeEnabled(ss, group_type_ff);
+                }
                 return PR_TRUE;
             }
             /* If the server requires the extension, then the client must have
              * already sent a ffdhe group. peerSupportsFfdheGroups is set to true in
              * ssl_HandleSupportedGroupsXtn(). */
             if (ss->sec.isServer && !ss->ssl3.hs.peerSupportsFfdheGroups) {
                 return PR_FALSE;
             }
@@ -12661,17 +12667,16 @@ ssl3_FillInCachedSID(sslSocket *ss, sslS
         ss->version >= SSL_LIBRARY_VERSION_TLS_1_3 ? ss->ssl3.hs.origCipherSuite : ss->ssl3.hs.cipher_suite;
     sid->u.ssl3.compression = ss->ssl3.hs.compression;
     sid->u.ssl3.policy = ss->ssl3.policy;
     sid->version = ss->version;
     sid->authType = ss->sec.authType;
     sid->authKeyBits = ss->sec.authKeyBits;
     sid->keaType = ss->sec.keaType;
     sid->keaKeyBits = ss->sec.keaKeyBits;
-    sid->namedGroups = ss->namedGroups;
     sid->lastAccessTime = sid->creationTime = ssl_Time();
     sid->expirationTime = sid->creationTime + ssl3_sid_timeout;
     sid->localCert = CERT_DupCertificate(ss->sec.localCert);
     if (ss->sec.isServer) {
         memcpy(&sid->certType, &ss->sec.serverCert->certType, sizeof(sid->certType));
     } else {
         sid->certType.authType = ssl_auth_null;
     }
--- a/lib/ssl/ssl3ecc.c
+++ b/lib/ssl/ssl3ecc.c
@@ -124,17 +124,17 @@ ssl_ECPubKey2NamedGroup(const SECKEYPubl
     oid.data = params->data + 2;
     if ((oidData = SECOID_FindOID(&oid)) == NULL)
         return NULL;
     if ((NSS_GetAlgorithmPolicy(oidData->offset, &policyFlags) ==
          SECSuccess) &&
         !(policyFlags & NSS_USE_ALG_IN_SSL_KX)) {
         return NULL;
     }
-    for (i = 0; i < ssl_named_group_count; ++i) {
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
         if (ssl_named_groups[i].oidTag == oidData->offset) {
             return &ssl_named_groups[i];
         }
     }
 
     return NULL;
 }
 
@@ -419,27 +419,33 @@ tls13_ImportECDHKeyShare(sslSocket *ss, 
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     return SECSuccess;
 }
 
 const namedGroupDef *
-ssl_GetECGroupWithStrength(PRUint32 curvemsk, unsigned int requiredECCbits)
+ssl_GetECGroupWithStrength(sslSocket *ss, unsigned int requiredECCbits)
 {
     int i;
 
-    for (i = 0; i < ssl_named_group_count; i++) {
-        if (ssl_named_groups[i].type != group_type_ec ||
-            ssl_named_groups[i].bits < requiredECCbits) {
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        const namedGroupDef *group;
+        if (ss) {
+            group = ss->namedGroupPreferences[i];
+        } else {
+            group = &ssl_named_groups[i];
+        }
+        if (!group || group->type != group_type_ec ||
+            group->bits < requiredECCbits) {
             continue;
         }
-        if ((curvemsk & (1U << i))) {
-            return &ssl_named_groups[i];
+        if (!ss || ssl_NamedGroupEnabled(ss, group)) {
+            return group;
         }
     }
     PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
     return NULL;
 }
 
 /* Find the "weakest link".  Get the strength of the signature and symmetric
  * keys and choose a curve based on the weakest of those two. */
@@ -481,17 +487,17 @@ ssl_GetECGroupForServerSocket(sslSocket 
     bulkCipher = ssl_GetBulkCipherDef(ss->ssl3.hs.suite_def);
     requiredECCbits = bulkCipher->key_size * BPB * 2;
     PORT_Assert(requiredECCbits ||
                 ss->ssl3.hs.suite_def->bulk_cipher_alg == cipher_null);
     if (requiredECCbits > certKeySize) {
         requiredECCbits = certKeySize;
     }
 
-    return ssl_GetECGroupWithStrength(ss->namedGroups, requiredECCbits);
+    return ssl_GetECGroupWithStrength(ss, requiredECCbits);
 }
 
 /* function to clear out the lists */
 static SECStatus
 ssl_ShutdownECDHECurves(void *appData, void *nssData)
 {
     int i;
 
@@ -503,17 +509,17 @@ ssl_ShutdownECDHECurves(void *appData, v
     memset(gECDHEKeyPairs, 0, sizeof(gECDHEKeyPairs));
     return SECSuccess;
 }
 
 static PRStatus
 ssl_ECRegister(void)
 {
     SECStatus rv;
-    PORT_Assert(PR_ARRAY_SIZE(gECDHEKeyPairs) == ssl_named_group_count);
+    PORT_Assert(PR_ARRAY_SIZE(gECDHEKeyPairs) == SSL_NAMED_GROUP_COUNT);
     rv = NSS_RegisterShutdown(ssl_ShutdownECDHECurves, gECDHEKeyPairs);
     if (rv != SECSuccess) {
         gECDHEInitError = PORT_GetError();
     }
     return (PRStatus)rv;
 }
 
 /* Create an ECDHE key pair for a given curve */
@@ -1009,21 +1015,21 @@ ssl_DisableNonSuiteBGroups(sslSocket *ss
     slot = PK11_GetBestSlotWithAttributes(CKM_ECDH1_DERIVE, 0, 163,
                                           ss->pkcs11PinArg);
     if (slot) {
         /* Looks like we're committed to having lots of curves. */
         PK11_FreeSlot(slot);
         return;
     }
 
-    for (i = 0; i < ssl_named_group_count; ++i) {
-        PORT_Assert(ssl_named_groups[i].index == i);
-        if (ssl_named_groups[i].type == group_type_ec &&
-            !ssl_named_groups[i].suiteb) {
-            ss->namedGroups &= ~(1U << ssl_named_groups[i].index);
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        if (ss->namedGroupPreferences[i] &&
+            ss->namedGroupPreferences[i]->type == group_type_ec &&
+            !ss->namedGroupPreferences[i]->suiteb) {
+            ss->namedGroupPreferences[i] = NULL;
         }
     }
 }
 
 /* Send our Supported Groups extension. */
 PRInt32
 ssl_SendSupportedGroupsXtn(sslSocket *ss, PRBool append, PRUint32 maxBytes)
 {
@@ -1043,34 +1049,36 @@ ssl_SendSupportedGroupsXtn(sslSocket *ss
     if (ss->opt.requireDHENamedGroups ||
         ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3) {
         ff = ssl_IsDHEEnabled(ss);
     }
     if (!ec && !ff) {
         return 0;
     }
 
-    PORT_Assert(sizeof(enabledGroups) > ssl_named_group_count * 2);
-    for (i = 0; i < ssl_named_group_count; ++i) {
-        if (ssl_named_groups[i].type == group_type_ec && !ec) {
+    PORT_Assert(sizeof(enabledGroups) > SSL_NAMED_GROUP_COUNT * 2);
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        const namedGroupDef *group = ss->namedGroupPreferences[i];
+        if (!group) {
             continue;
         }
-        if (ssl_named_groups[i].type == group_type_ff && !ff) {
+        if (group->type == group_type_ec && !ec) {
             continue;
         }
-        if (!ssl_NamedGroupEnabled(ss, &ssl_named_groups[i])) {
+        if (group->type == group_type_ff && !ff) {
+            continue;
+        }
+        if (!ssl_NamedGroupEnabled(ss, group)) {
             continue;
         }
 
         if (append) {
-            enabledGroups[enabledGroupsLen++] = ssl_named_groups[i].name >> 8;
-            enabledGroups[enabledGroupsLen++] = ssl_named_groups[i].name & 0xff;
-        } else {
-            enabledGroupsLen += 2;
+            (void)ssl_EncodeUintX(group->name, 2, &enabledGroups[enabledGroupsLen]);
         }
+        enabledGroupsLen += 2;
     }
 
     extension_length =
         2 /* extension type */ +
         2 /* extension length */ +
         2 /* enabled groups length */ +
         enabledGroupsLen;
 
@@ -1162,66 +1170,77 @@ ssl3_HandleSupportedPointFormatsXtn(sslS
     PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
     return SECFailure;
 }
 
 static SECStatus
 ssl_UpdateSupportedGroups(sslSocket *ss, SECItem *data)
 {
     PRInt32 list_len;
-    PRUint32 peerGroups = 0;
+    unsigned int i;
+    const namedGroupDef *enabled[SSL_NAMED_GROUP_COUNT] = { 0 };
+    PORT_Assert(SSL_NAMED_GROUP_COUNT == PR_ARRAY_SIZE(enabled));
 
     if (!data->data || data->len < 4) {
         (void)ssl3_DecodeError(ss);
         return SECFailure;
     }
 
     /* get the length of elliptic_curve_list */
     list_len = ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
     if (list_len < 0 || data->len != list_len || (data->len % 2) != 0) {
         (void)ssl3_DecodeError(ss);
         return SECFailure;
     }
 
-    /* build bit vector of peer's supported groups */
+    /* disable all groups and remember the enabled groups */
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        enabled[i] = ss->namedGroupPreferences[i];
+        ss->namedGroupPreferences[i] = NULL;
+    }
+
+    /* Read groups from data and enable if in |enabled| */
     while (data->len) {
         const namedGroupDef *group;
         PRInt32 curve_name =
             ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
         if (curve_name < 0) {
             return SECFailure; /* fatal alert already sent */
         }
         group = ssl_LookupNamedGroup(curve_name);
         if (group) {
-            peerGroups |= (1U << group->index);
+            for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+                if (enabled[i] && group == enabled[i]) {
+                    ss->namedGroupPreferences[i] = enabled[i];
+                    break;
+                }
+            }
         }
 
         /* "Codepoints in the NamedCurve registry with a high byte of 0x01 (that
          * is, between 256 and 511 inclusive) are set aside for FFDHE groups,"
          * -- https://tools.ietf.org/html/draft-ietf-tls-negotiated-ff-dhe-10
          */
         if ((curve_name & 0xff00) == 0x0100) {
             ss->ssl3.hs.peerSupportsFfdheGroups = PR_TRUE;
         }
     }
+
     /* Note: if ss->opt.requireDHENamedGroups is set, we disable DHE cipher
      * suites, but we do that in ssl3_config_match(). */
     if (!ss->opt.requireDHENamedGroups && !ss->ssl3.hs.peerSupportsFfdheGroups) {
         /* If we don't require that DHE use named groups, and no FFDHE was
          * included, we pretend that they support all the FFDHE groups we do. */
-        unsigned int i;
-        for (i = 0; i < ssl_named_group_count; ++i) {
-            if (ssl_named_groups[i].type == group_type_ff) {
-                peerGroups |= (1U << ssl_named_groups[i].index);
+        for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+            if (enabled[i] && enabled[i]->type == group_type_ff) {
+                ss->namedGroupPreferences[i] = enabled[i];
             }
         }
     }
 
-    /* What curves do we support in common? */
-    ss->namedGroups &= peerGroups;
     return SECSuccess;
 }
 
 /* Ensure that the curve in our server cert is one of the ones supported
  * by the remote client, and disable all ECC cipher suites if not.
  */
 SECStatus
 ssl_HandleSupportedGroupsXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
--- a/lib/ssl/ssl3ext.c
+++ b/lib/ssl/ssl3ext.c
@@ -3298,25 +3298,26 @@ tls13_HandleKeyShareEntry(sslSocket *ss,
     SECItem share = { siBuffer, NULL, 0 };
 
     group = ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
     if (group < 0) {
         PORT_SetError(SSL_ERROR_RX_MALFORMED_KEY_SHARE);
         goto loser;
     }
     groupDef = ssl_LookupNamedGroup(group);
+    rv = ssl3_ConsumeHandshakeVariable(ss, &share, 2, &data->data,
+                                       &data->len);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    /* If the group is disabled, continue. */
     if (!groupDef) {
         return SECSuccess;
     }
 
-    rv = ssl3_ConsumeHandshakeVariable(ss, &share, 2, &data->data,
-                                       &data->len);
-    if (rv != SECSuccess)
-        goto loser;
-
     ks = PORT_ZNew(TLS13KeyShareEntry);
     if (!ks)
         goto loser;
     ks->group = groupDef;
 
     rv = SECITEM_CopyItem(NULL, &ks->key_exchange, &share);
     if (rv != SECSuccess)
         goto loser;
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -142,16 +142,19 @@ typedef enum { SSLAppOpRead = 0,
 
 /* The default value from RFC 4347 is 1s, which is too slow. */
 #define DTLS_RETRANSMIT_INITIAL_MS 50
 /* The maximum time to wait between retransmissions. */
 #define DTLS_RETRANSMIT_MAX_MS 10000
 /* Time to wait in FINISHED state for retransmissions. */
 #define DTLS_RETRANSMIT_FINISHED_MS 30000
 
+/* default number of entries in namedGroupPreferences */
+#define SSL_NAMED_GROUP_COUNT 30
+
 /* Types and names of elliptic curves used in TLS */
 typedef enum {
     ec_type_explicitPrime = 1,      /* not supported */
     ec_type_explicitChar2Curve = 2, /* not supported */
     ec_type_named = 3
 } ECType;
 
 /* TODO: decide if SSLKEAType might be better here. */
@@ -602,17 +605,16 @@ struct sslSessionIDStr {
 
     PRUint32 creationTime;   /* seconds since Jan 1, 1970 */
     PRUint32 expirationTime; /* seconds since Jan 1, 1970 */
 
     SSLAuthType authType;
     PRUint32 authKeyBits;
     SSLKEAType keaType;
     PRUint32 keaKeyBits;
-    PRUint32 namedGroups;
 
     union {
         struct {
             /* values that are copied into the server's on-disk SID cache. */
             PRUint8 sessionIDLength;
             SSL3Opaque sessionID[SSL3_SESSIONID_BYTES];
 
             ssl3CipherSuite cipherSuite;
@@ -1320,21 +1322,28 @@ struct sslSocketStr {
     sslBuffer saveBuf;    /*xmitBufLock*/
     sslBuffer pendingBuf; /*xmitBufLock*/
 
     /* Configuration state for server sockets */
     /* One server cert and key for each authentication type. */
     PRCList /* <sslServerCert> */ serverCerts;
 
     ssl3CipherSuiteCfg cipherSuites[ssl_V3_SUITES_IMPLEMENTED];
-    /* This bit mask determines what EC and FFDHE groups are enabled.  This
+
+    /* A list of groups that are sorted according to user preferences pointing
+     * to entries of ssl_named_groups. By default this list contains pointers
+     * to all elements in ssl_named_groups in the default order.
+     * This list also determines which groups are enabled. This
      * starts with all being enabled and can be modified either by negotiation
      * (in which case groups not supported by a peer are masked off), or by
-     * calling SSL_DHEGroupPrefSet(), which will alter the mask for FFDHE. */
-    PRUint32 namedGroups;
+     * calling SSL_DHEGroupPrefSet().
+     * Note that renegotiation will ignore groups that were disabled in the
+     * first handshake.
+     */
+    const namedGroupDef *namedGroupPreferences[SSL_NAMED_GROUP_COUNT];
 
     /* SSL3 state info.  Formerly was a pointer */
     ssl3State ssl3;
 
     /*
      * TLS extension related data.
      */
     /* True when the current session is a stateless resume. */
@@ -1359,17 +1368,16 @@ extern PRUint32 ssl3_sid_timeout;
 
 extern const char *const ssl3_cipherName[];
 
 extern sslSessionIDLookupFunc ssl_sid_lookup;
 extern sslSessionIDCacheFunc ssl_sid_cache;
 extern sslSessionIDUncacheFunc ssl_sid_uncache;
 
 extern const namedGroupDef ssl_named_groups[];
-extern const unsigned int ssl_named_group_count;
 
 /************************************************************************/
 
 SEC_BEGIN_PROTOS
 
 /* Internal initialization and installation of the SSL error tables */
 extern SECStatus ssl_Init(void);
 extern SECStatus ssl_InitializePRErrorTable(void);
@@ -1742,17 +1750,17 @@ extern PRBool ssl_IsDHEEnabled(sslSocket
 extern const namedGroupDef *ssl_LookupNamedGroup(SSLNamedGroup group);
 extern PRBool ssl_NamedGroupEnabled(const sslSocket *ss, const namedGroupDef *group);
 extern SECStatus ssl_NamedGroup2ECParams(PLArenaPool *arena,
                                          const namedGroupDef *curve,
                                          SECKEYECParams *params);
 extern const namedGroupDef *ssl_ECPubKey2NamedGroup(
     const SECKEYPublicKey *pubKey);
 
-extern const namedGroupDef *ssl_GetECGroupWithStrength(PRUint32 curvemsk,
+extern const namedGroupDef *ssl_GetECGroupWithStrength(sslSocket *ss,
                                                        unsigned int requiredECCbits);
 extern const namedGroupDef *ssl_GetECGroupForServerSocket(sslSocket *ss);
 extern void ssl_DisableNonSuiteBGroups(sslSocket *ss);
 
 extern SECStatus ssl3_CipherPrefSetDefault(ssl3CipherSuite which, PRBool on);
 extern SECStatus ssl3_CipherPrefGetDefault(ssl3CipherSuite which, PRBool *on);
 
 extern SECStatus ssl3_CipherPrefSet(sslSocket *ss, ssl3CipherSuite which, PRBool on);
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -139,16 +139,17 @@ static const PRUint16 srtpCiphers[] = {
 /* This list is in rough order of speed.  Note that while some smaller groups
  * appear early in the list, smaller groups are generally ignored when iterating
  * through this list. ffdhe_custom must not appear in this list. */
 #define ECGROUP(name, size, oid) \
     ssl_grp_ec_##name, size, group_type_ec, SEC_OID_SECG_EC_##oid
 #define FFGROUP(size, oid) \
     ssl_grp_ffdhe_##size, size, group_type_ff, SEC_OID_TLS_FFDHE_##oid
 
+/* update SSL_NAMED_GROUP_COUNT when changing the number of entries */
 const namedGroupDef ssl_named_groups[] = {
     { 0, ECGROUP(secp192r1, 192, SECP192R1), PR_FALSE },
     { 1, ECGROUP(secp160r2, 160, SECP160R2), PR_FALSE },
     { 2, ECGROUP(secp160k1, 160, SECP160K1), PR_FALSE },
     { 3, ECGROUP(secp160r1, 160, SECP160R1), PR_FALSE },
     { 4, ECGROUP(sect163k1, 163, SECT163K1), PR_FALSE },
     { 5, ECGROUP(sect163r1, 163, SECT163R1), PR_FALSE },
     { 6, ECGROUP(sect163r2, 163, SECT163R2), PR_FALSE },
@@ -174,17 +175,16 @@ const namedGroupDef ssl_named_groups[] =
     { 26, FFGROUP(3072, 3072), PR_FALSE },
     { 27, FFGROUP(4096, 4096), PR_FALSE },
     { 28, FFGROUP(6144, 6144), PR_FALSE },
     { 29, FFGROUP(8192, 8192), PR_FALSE }
 };
 #undef ECGROUP
 #undef FFGROUP
 
-const unsigned int ssl_named_group_count = PR_ARRAY_SIZE(ssl_named_groups);
 /* Check that the supported groups bits will fit into ss->namedGroups. */
 PR_STATIC_ASSERT(PR_ARRAY_SIZE(ssl_named_groups) < (sizeof(PRUint32) * 8));
 
 /* forward declarations. */
 static sslSocket *ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant variant);
 static SECStatus ssl_MakeLocks(sslSocket *ss);
 static void ssl_SetDefaultsFromEnvironment(void);
 static PRStatus ssl_PushIOLayer(sslSocket *ns, PRFileDesc *stack,
@@ -252,16 +252,17 @@ ssl_FindSocket(PRFileDesc *fd)
     return ss;
 }
 
 static sslSocket *
 ssl_DupSocket(sslSocket *os)
 {
     sslSocket *ss;
     SECStatus rv;
+    unsigned int i;
 
     ss = ssl_NewSocket((PRBool)(!os->opt.noLocks), os->protocolVariant);
     if (!ss) {
         return NULL;
     }
 
     ss->opt = os->opt;
     ss->opt.useSocks = PR_FALSE;
@@ -310,17 +311,16 @@ ssl_DupSocket(sslSocket *os)
              cursor != &os->ephemeralKeyPairs;
              cursor = PR_NEXT_LINK(cursor)) {
             sslEphemeralKeyPair *okp = (sslEphemeralKeyPair *)cursor;
             sslEphemeralKeyPair *skp = ssl_CopyEphemeralKeyPair(okp);
             if (!skp)
                 goto loser;
             PR_APPEND_LINK(&skp->link, &ss->ephemeralKeyPairs);
         }
-        ss->namedGroups = os->namedGroups;
 
         /*
          * XXX the preceding CERT_ and SECKEY_ functions can fail and return NULL.
          * XXX We should detect this, and not just march on with NULL pointers.
          */
         ss->authCertificate = os->authCertificate;
         ss->authCertificateArg = os->authCertificateArg;
         ss->getClientAuthData = os->getClientAuthData;
@@ -331,16 +331,19 @@ ssl_DupSocket(sslSocket *os)
         ss->badCertArg = os->badCertArg;
         ss->handshakeCallback = os->handshakeCallback;
         ss->handshakeCallbackData = os->handshakeCallbackData;
         ss->canFalseStartCallback = os->canFalseStartCallback;
         ss->canFalseStartCallbackData = os->canFalseStartCallbackData;
         ss->pkcs11PinArg = os->pkcs11PinArg;
         ss->nextProtoCallback = os->nextProtoCallback;
         ss->nextProtoArg = os->nextProtoArg;
+        for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+            ss->namedGroupPreferences[i] = os->namedGroupPreferences[i];
+        }
 
         /* Create security data */
         rv = ssl_CopySecurityInfo(ss, os);
         if (rv != SECSuccess) {
             goto loser;
         }
     }
     return ss;
@@ -1493,59 +1496,71 @@ NSS_SetExportPolicy(void)
 
 SECStatus
 NSS_SetFrancePolicy(void)
 {
     return NSS_SetDomesticPolicy();
 }
 
 SECStatus
-SSL_NamedGroupPrefSet(PRFileDesc *fd, SSLNamedGroup group, PRBool enable)
+SSL_NamedGroupConfig(PRFileDesc *fd, const SSLNamedGroup *groups,
+                     unsigned int numGroups)
 {
-    sslSocket *ss;
-    unsigned int i;
-
-    ss = ssl_FindSocket(fd);
+    unsigned int i, j;
+    sslSocket *ss = ssl_FindSocket(fd);
     if (!ss) {
+        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+        return SECFailure;
+    }
+
+    if (!groups) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+    if (numGroups > SSL_NAMED_GROUP_COUNT) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
     }
 
-    for (i = 0; i < ssl_named_group_count; ++i) {
-        if (ssl_named_groups[i].name == group) {
-            PRUint32 bit = 1U << ssl_named_groups[i].index;
-            if (enable) {
-                ss->namedGroups |= bit;
-            } else {
-                ss->namedGroups &= ~bit;
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        ss->namedGroupPreferences[i] = NULL;
+    }
+
+    for (i = 0; i < numGroups; ++i) {
+        for (j = 0; j < SSL_NAMED_GROUP_COUNT; ++j) {
+            /* skip duplicate groups */
+            if (ss->namedGroupPreferences[j] &&
+                ss->namedGroupPreferences[j]->name == groups[i]) {
+                break;
             }
-            return SECSuccess;
+            if (ssl_named_groups[j].name == groups[i]) {
+                ss->namedGroupPreferences[i] = &ssl_named_groups[j];
+                break;
+            }
         }
     }
 
-    SSL_DBG(("%d: SSL[%d]: unsupported group %d in SSL_NamedGroupPrefSet",
-             SSL_GETPID(), fd, group));
-    PORT_SetError(SEC_ERROR_INVALID_ARGS);
-    return SECFailure;
+    return SECSuccess;
 }
 
 SECStatus
-SSL_DHEGroupPrefSet(PRFileDesc *fd,
-                    const SSLDHEGroupType *groups,
+SSL_DHEGroupPrefSet(PRFileDesc *fd, const SSLDHEGroupType *groups,
                     PRUint16 num_groups)
 {
     sslSocket *ss;
     const SSLDHEGroupType *list;
     unsigned int count;
-    unsigned int i;
-    PRUint32 supportedGroups;
+    int i, k;
+    const namedGroupDef *enabled[SSL_NAMED_GROUP_COUNT] = { 0 };
     static const SSLDHEGroupType default_dhe_groups[] = {
         ssl_ff_dhe_2048_group
     };
 
-    if ((num_groups && !groups) || (!num_groups && groups)) {
+    if ((num_groups && !groups) || (!num_groups && groups) ||
+        num_groups > SSL_NAMED_GROUP_COUNT) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
     }
 
     ss = ssl_FindSocket(fd);
     if (!ss) {
         SSL_DBG(("%d: SSL[%d]: bad socket in SSL_DHEGroupPrefSet", SSL_GETPID(), fd));
         return SECFailure;
@@ -1554,24 +1569,29 @@ SSL_DHEGroupPrefSet(PRFileDesc *fd,
     if (groups) {
         list = groups;
         count = num_groups;
     } else {
         list = default_dhe_groups;
         count = PR_ARRAY_SIZE(default_dhe_groups);
     }
 
-    supportedGroups = ss->namedGroups;
-    for (i = 0; i < ssl_named_group_count; ++i) {
-        if (ssl_named_groups[i].type == group_type_ff) {
-            supportedGroups &= ~(1U << ssl_named_groups[i].index);
+    /* save enabled ec groups and clear ss->namedGroupPreferences */
+    k = 0;
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        if (ss->namedGroupPreferences[i] &&
+            ss->namedGroupPreferences[i]->type != group_type_ff) {
+            enabled[k++] = ss->namedGroupPreferences[i];
         }
+        ss->namedGroupPreferences[i] = NULL;
     }
+
     ss->ssl3.dhePreferredGroup = NULL;
     for (i = 0; i < count; ++i) {
+        PRBool duplicate = PR_FALSE;
         SSLNamedGroup name;
         const namedGroupDef *groupDef;
         switch (list[i]) {
             case ssl_ff_dhe_2048_group:
                 name = ssl_grp_ffdhe_2048;
                 break;
             case ssl_ff_dhe_3072_group:
                 name = ssl_grp_ffdhe_3072;
@@ -1589,19 +1609,32 @@ SSL_DHEGroupPrefSet(PRFileDesc *fd,
                 PORT_SetError(SEC_ERROR_INVALID_ARGS);
                 return SECFailure;
         }
         groupDef = ssl_LookupNamedGroup(name);
         PORT_Assert(groupDef);
         if (!ss->ssl3.dhePreferredGroup) {
             ss->ssl3.dhePreferredGroup = groupDef;
         }
-        supportedGroups |= (1U << groupDef->index);
+        PORT_Assert(k < SSL_NAMED_GROUP_COUNT);
+        for (i = 0; i < k; ++i) {
+            /* skip duplicates */
+            if (enabled[i] == groupDef) {
+                duplicate = PR_TRUE;
+                break;
+            }
+        }
+        if (!duplicate) {
+            enabled[k++] = groupDef;
+        }
     }
-    ss->namedGroups = supportedGroups;
+    for (i = 0; i < k; ++i) {
+        ss->namedGroupPreferences[i] = enabled[i];
+    }
+
     return SECSuccess;
 }
 
 PRCallOnceType gWeakDHParamsRegisterOnce;
 int gWeakDHParamsRegisterError;
 
 PRCallOnceType gWeakDHParamsOnce;
 int gWeakDHParamsError;
@@ -1787,33 +1820,36 @@ SECStatus
 ssl_ValidateDHENamedGroup(sslSocket *ss,
                           const SECItem *dh_p,
                           const SECItem *dh_g,
                           const namedGroupDef **groupDef,
                           const ssl3DHParams **dhParams)
 {
     unsigned int i;
 
-    for (i = 0; i < ssl_named_group_count; ++i) {
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
         const ssl3DHParams *params;
-        if (ssl_named_groups[i].type != group_type_ff) {
+        if (!ss->namedGroupPreferences[i]) {
             continue;
         }
-        if (!ssl_NamedGroupEnabled(ss, &ssl_named_groups[i])) {
+        if (ss->namedGroupPreferences[i]->type != group_type_ff) {
             continue;
         }
-
-        params = ssl_GetDHEParams(&ssl_named_groups[i]);
+        if (!ssl_NamedGroupEnabled(ss, ss->namedGroupPreferences[i])) {
+            continue;
+        }
+
+        params = ssl_GetDHEParams(ss->namedGroupPreferences[i]);
         PORT_Assert(params);
         if (SECITEM_ItemsAreEqual(&params->prime, dh_p)) {
             if (!SECITEM_ItemsAreEqual(&params->base, dh_g)) {
                 return SECFailure;
             }
             if (groupDef)
-                *groupDef = &ssl_named_groups[i];
+                *groupDef = ss->namedGroupPreferences[i];
             if (dhParams)
                 *dhParams = params;
             return SECSuccess;
         }
     }
 
     return SECFailure;
 }
@@ -1837,20 +1873,21 @@ ssl_SelectDHEGroup(sslSocket *ss, const 
         *groupDef = &weak_group_def;
         return SECSuccess;
     }
     if (ss->ssl3.dhePreferredGroup &&
         ssl_NamedGroupEnabled(ss, ss->ssl3.dhePreferredGroup)) {
         *groupDef = ss->ssl3.dhePreferredGroup;
         return SECSuccess;
     }
-    for (i = 0; i < ssl_named_group_count; ++i) {
-        if (ssl_named_groups[i].type == group_type_ff &&
-            ssl_NamedGroupEnabled(ss, &ssl_named_groups[i])) {
-            *groupDef = &ssl_named_groups[i];
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        if (ss->namedGroupPreferences[i] &&
+            ss->namedGroupPreferences[i]->type == group_type_ff &&
+            ssl_NamedGroupEnabled(ss, ss->namedGroupPreferences[i])) {
+            *groupDef = ss->namedGroupPreferences[i];
             return SECSuccess;
         }
     }
 
     *groupDef = NULL;
     PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
     return SECFailure;
 }
@@ -3561,37 +3598,44 @@ ssl_SetDefaultsFromEnvironment(void)
 #endif /* NSS_HAVE_GETENV */
 }
 
 const namedGroupDef *
 ssl_LookupNamedGroup(SSLNamedGroup group)
 {
     unsigned int i;
 
-    for (i = 0; i < ssl_named_group_count; ++i) {
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
         if (ssl_named_groups[i].name == group) {
             return &ssl_named_groups[i];
         }
     }
     return NULL;
 }
 
 PRBool
 ssl_NamedGroupEnabled(const sslSocket *ss, const namedGroupDef *groupDef)
 {
     PRUint32 policy;
     SECStatus rv;
+    unsigned int i;
 
     PORT_Assert(groupDef);
 
     rv = NSS_GetAlgorithmPolicy(groupDef->oidTag, &policy);
     if (rv == SECSuccess && !(policy & NSS_USE_ALG_IN_SSL_KX)) {
         return PR_FALSE;
     }
-    return (ss->namedGroups & (1U << groupDef->index)) != 0;
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        if (ss->namedGroupPreferences[i] &&
+            ss->namedGroupPreferences[i] == groupDef) {
+            return PR_TRUE;
+        }
+    }
+    return PR_FALSE;
 }
 
 /* Returns a reference counted object that contains a key pair.
  * Or NULL on failure.  Initial ref count is 1.
  * Uses the keys in the pair as input.  Adopts the keys given.
  */
 sslKeyPair *
 ssl_NewKeyPair(SECKEYPrivateKey *privKey, SECKEYPublicKey *pubKey)
@@ -3712,16 +3756,17 @@ ssl_FreeEphemeralKeyPairs(sslSocket *ss)
 /*
 ** Create a newsocket structure for a file descriptor.
 */
 static sslSocket *
 ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant)
 {
     SECStatus rv;
     sslSocket *ss;
+    int i;
 
     ssl_SetDefaultsFromEnvironment();
 
     if (ssl_force_locks)
         makeLocks = PR_TRUE;
 
     /* Make a new socket and get it ready */
     ss = (sslSocket *)PORT_ZAlloc(sizeof(sslSocket));
@@ -3756,17 +3801,19 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
     ss->sniSocketConfigArg = NULL;
     ss->getClientAuthData = NULL;
     ss->handleBadCert = NULL;
     ss->badCertArg = NULL;
     ss->pkcs11PinArg = NULL;
 
     ssl_ChooseOps(ss);
     ssl3_InitSocketPolicy(ss);
-    ss->namedGroups = PR_UINT32_MAX; /* All groups enabled to start. */
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
+        ss->namedGroupPreferences[i] = &ssl_named_groups[i];
+    }
     PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
     PR_INIT_CLIST(&ss->ssl3.hs.remoteKeyShares);
     PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs);
     PR_INIT_CLIST(&ss->ssl3.hs.bufferedEarlyData);
     if (makeLocks) {
         rv = ssl_MakeLocks(ss);
         if (rv != SECSuccess)
             goto loser;
--- a/lib/ssl/tls13con.c
+++ b/lib/ssl/tls13con.c
@@ -352,19 +352,22 @@ tls13_SetupClientHello(sslSocket *ss)
     /* Only generate an FFDHE share when EC suites are disabled. */
     PRBool ffNeeded = !ecNeeded;
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
     PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
 
     PORT_Assert(PR_CLIST_IS_EMPTY(&ss->ephemeralKeyPairs));
 
-    for (i = 0; i < ssl_named_group_count; ++i) {
+    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
         SECStatus rv;
-        const namedGroupDef *groupDef = &ssl_named_groups[i];
+        const namedGroupDef *groupDef = ss->namedGroupPreferences[i];
+        if (!groupDef) {
+            continue;
+        }
 
         if (!ssl_NamedGroupEnabled(ss, groupDef)) {
             continue;
         }
         switch (groupDef->type) {
             case group_type_ec:
                 if (!ecNeeded) {
                     continue;