459 lines
14 KiB
C++
459 lines
14 KiB
C++
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0 OR ISC
|
|
|
|
#include <openssl/ssl.h>
|
|
#include "../crypto/test/test_util.h"
|
|
#include "ssl_common_test.h"
|
|
|
|
BSSL_NAMESPACE_BEGIN
|
|
|
|
struct HybridHandshakeTest {
|
|
// The curves rule string to apply to the client
|
|
const char *client_rule;
|
|
// TLS version that the client is configured with
|
|
uint16_t client_version;
|
|
// The curves rule string to apply to the server
|
|
const char *server_rule;
|
|
// TLS version that the server is configured with
|
|
uint16_t server_version;
|
|
// The group that is expected to be negotiated
|
|
uint16_t expected_group;
|
|
// Is a HelloRetryRequest expected?
|
|
bool is_hrr_expected;
|
|
};
|
|
|
|
|
|
static const HybridHandshakeTest kHybridHandshakeTests[] = {
|
|
// The corresponding hybrid group should be negotiated when client
|
|
// and server support only that group
|
|
{
|
|
"X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"SecP384r1MLKEM1024",
|
|
TLS1_3_VERSION,
|
|
"SecP384r1MLKEM1024",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP384R1_MLKEM1024,
|
|
false,
|
|
},
|
|
|
|
// Pure MLKEM at all key sizes
|
|
{
|
|
"MLKEM512",
|
|
TLS1_3_VERSION,
|
|
"MLKEM512",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_MLKEM512,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"MLKEM1024",
|
|
TLS1_3_VERSION,
|
|
"MLKEM1024",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_MLKEM1024,
|
|
false,
|
|
},
|
|
|
|
// The client's preferred hybrid group should be negotiated when also
|
|
// supported by the server, even if the server "prefers"/supports other
|
|
// groups.
|
|
{
|
|
"X25519MLKEM768:x25519",
|
|
TLS1_3_VERSION,
|
|
"x25519:prime256v1:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"X25519MLKEM768:x25519",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768:x25519",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768:secp384r1:x25519:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"X25519MLKEM768:SecP256r1MLKEM768:SecP384r1MLKEM1024",
|
|
TLS1_3_VERSION,
|
|
"SecP384r1MLKEM1024:SecP256r1MLKEM768:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
{
|
|
"SecP384r1MLKEM1024:SecP256r1MLKEM768:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768:SecP256r1MLKEM768:SecP384r1MLKEM1024",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP384R1_MLKEM1024,
|
|
false,
|
|
},
|
|
|
|
// The client lists PQ/hybrid groups as both first and second preferences.
|
|
// The key share logic is implemented such that the client will always
|
|
// attempt to send one hybrid key share and one classical key share.
|
|
// Therefore, the client will send key shares [SecP256r1MLKEM768, x25519],
|
|
// skipping X25519MLKEM768, and the server will choose to negotiate
|
|
// x25519 since it is the only mutually supported group.
|
|
{
|
|
"SecP256r1MLKEM768:X25519MLKEM768:x25519",
|
|
TLS1_3_VERSION,
|
|
"secp384r1:x25519",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519,
|
|
false,
|
|
},
|
|
|
|
// The client will send key shares [x25519, SecP256r1MLKEM768].
|
|
// The server will negotiate SecP256r1MLKEM768 since it is the only
|
|
// mutually supported group.
|
|
{
|
|
"x25519:secp384r1:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"SecP256r1MLKEM768:prime256v1",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
// The client will send key shares [x25519, SecP256r1MLKEM768]. The
|
|
// server will negotiate x25519 since the client listed it as its first
|
|
// preference, even though it supports SecP256r1MLKEM768.
|
|
{
|
|
"x25519:prime256v1:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"prime256v1:x25519:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519,
|
|
false,
|
|
},
|
|
|
|
// The client will send key shares [SecP256r1MLKEM768, x25519].
|
|
// The server will negotiate SecP256r1MLKEM768 since the client listed
|
|
// it as its first preference.
|
|
{
|
|
"SecP256r1MLKEM768:x25519:prime256v1",
|
|
TLS1_3_VERSION,
|
|
"prime256v1:x25519:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1_MLKEM768,
|
|
false,
|
|
},
|
|
|
|
// In the supported_groups extension, the client will indicate its
|
|
// preferences, in order, as [SecP256r1MLKEM768, X25519MLKEM768,
|
|
// x25519, prime256v1]. From those groups, it will send key shares
|
|
// [SecP256r1MLKEM768, x25519]. The server supports, and receives a
|
|
// key share for, x25519. However, when selecting a mutually supported group
|
|
// to negotiate, the server recognizes that the client prefers
|
|
// X25519MLKEM768 over x25519. Since the server also supports
|
|
// X25519MLKEM768, but did not receive a key share for it, it will
|
|
// select it and send an HRR. This ensures that the client's highest
|
|
// preference group will be negotiated, even at the expense of an additional
|
|
// round-trip.
|
|
//
|
|
// In our SSL implementation, this situation is unique to the case where the
|
|
// client supports both ECC and hybrid/PQ. When sending key shares, the
|
|
// client will send at most two key shares in one of the following ways:
|
|
|
|
// (a) one ECC key share - if the client supports only ECC;
|
|
// (b) one PQ key share - if the client supports only PQ;
|
|
// (c) one ECC and one PQ key share - if the client supports ECC and PQ.
|
|
//
|
|
// One of the above cases will be true irrespective of how many groups
|
|
// the client supports. If, say, the client supports four ECC groups
|
|
// and zero PQ groups, it will still only send a single ECC share. In cases
|
|
// (a) and (b), either the server supports that group and chooses to
|
|
// negotiate it, or it doesn't support it and sends an HRR. Case (c) is the
|
|
// only case where the server might receive a key share for a mutually
|
|
// supported group, but chooses to respect the client's preference order
|
|
// defined in the supported_groups extension at the expense of an additional
|
|
// round-trip.
|
|
{
|
|
"SecP256r1MLKEM768:X25519MLKEM768:x25519:prime256v1",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768:prime256v1:x25519",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519_MLKEM768,
|
|
true,
|
|
},
|
|
|
|
// Like the previous case, but the client's prioritization of ECC and PQ
|
|
// is inverted.
|
|
{
|
|
"x25519:prime256v1:SecP256r1MLKEM768:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768:prime256v1",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1,
|
|
true,
|
|
},
|
|
|
|
// The client will send key shares [SecP256r1MLKEM768, x25519]. The
|
|
// server will negotiate X25519MLKEM768 after an HRR.
|
|
{
|
|
"SecP256r1MLKEM768:X25519MLKEM768:x25519:prime256v1",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768:prime256v1",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519_MLKEM768,
|
|
true,
|
|
},
|
|
|
|
// EC should be negotiated when client prefers EC, or server does not
|
|
// support hybrid
|
|
{
|
|
"X25519MLKEM768:x25519",
|
|
TLS1_3_VERSION,
|
|
"x25519",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519,
|
|
false,
|
|
},
|
|
{
|
|
"x25519:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"x25519",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519,
|
|
false,
|
|
},
|
|
{
|
|
"prime256v1:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768:prime256v1",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1,
|
|
false,
|
|
},
|
|
{
|
|
"prime256v1:x25519:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"x25519:prime256v1:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1,
|
|
false,
|
|
},
|
|
|
|
// EC should be negotiated, after a HelloRetryRequest, if the server
|
|
// supports only curves for which it did not initially receive a key share
|
|
{
|
|
"X25519MLKEM768:x25519:SecP256r1MLKEM768:prime256v1",
|
|
TLS1_3_VERSION,
|
|
"prime256v1",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1,
|
|
true,
|
|
},
|
|
{
|
|
"X25519MLKEM768:SecP256r1MLKEM768:prime256v1:x25519",
|
|
TLS1_3_VERSION,
|
|
"secp224r1:secp384r1:secp521r1:x25519",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519,
|
|
true,
|
|
},
|
|
|
|
// Hybrid should be negotiated, after a HelloRetryRequest, if the server
|
|
// supports only curves for which it did not initially receive a key share
|
|
{
|
|
"x25519:prime256v1:SecP256r1MLKEM768:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"secp224r1:X25519MLKEM768:secp521r1",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519_MLKEM768,
|
|
true,
|
|
},
|
|
{
|
|
"X25519MLKEM768:x25519:prime256v1:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_SECP256R1_MLKEM768,
|
|
true,
|
|
},
|
|
|
|
// If there is no overlap between client and server groups,
|
|
// the handshake should fail
|
|
{
|
|
"SecP256r1MLKEM768:X25519MLKEM768:secp384r1",
|
|
TLS1_3_VERSION,
|
|
"prime256v1:x25519",
|
|
TLS1_3_VERSION,
|
|
0,
|
|
false,
|
|
},
|
|
{
|
|
"secp384r1:SecP256r1MLKEM768:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"prime256v1:x25519",
|
|
TLS1_3_VERSION,
|
|
0,
|
|
false,
|
|
},
|
|
{
|
|
"secp384r1:SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"prime256v1:x25519:X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
0,
|
|
false,
|
|
},
|
|
{
|
|
"SecP256r1MLKEM768",
|
|
TLS1_3_VERSION,
|
|
"X25519MLKEM768",
|
|
TLS1_3_VERSION,
|
|
0,
|
|
false,
|
|
},
|
|
|
|
// If the client supports hybrid TLS 1.3, but the server
|
|
// only supports TLS 1.2, then TLS 1.2 EC should be negotiated.
|
|
{
|
|
"SecP256r1MLKEM768:prime256v1",
|
|
TLS1_3_VERSION,
|
|
"prime256v1:x25519",
|
|
TLS1_2_VERSION,
|
|
SSL_GROUP_SECP256R1,
|
|
false,
|
|
},
|
|
|
|
// Same as above, but server also has SecP256r1MLKEM768 in it's
|
|
// supported list, but can't use it since TLS 1.3 is the minimum version
|
|
// that
|
|
// supports PQ.
|
|
{
|
|
"SecP256r1MLKEM768:prime256v1",
|
|
TLS1_3_VERSION,
|
|
"SecP256r1MLKEM768:prime256v1:x25519",
|
|
TLS1_2_VERSION,
|
|
SSL_GROUP_SECP256R1,
|
|
false,
|
|
},
|
|
|
|
// If the client configures the curve list to include a hybrid
|
|
// curve, then initiates a 1.2 handshake, it will not advertise
|
|
// hybrid groups because hybrid is not supported for 1.2. So
|
|
// a 1.2 EC handshake will be negotiated (even if the server
|
|
// supports 1.3 with corresponding hybrid group).
|
|
{
|
|
"SecP256r1MLKEM768:x25519",
|
|
TLS1_2_VERSION,
|
|
"SecP256r1MLKEM768:x25519",
|
|
TLS1_3_VERSION,
|
|
SSL_GROUP_X25519,
|
|
false,
|
|
},
|
|
{
|
|
"SecP256r1MLKEM768:prime256v1",
|
|
TLS1_2_VERSION,
|
|
"prime256v1:x25519",
|
|
TLS1_2_VERSION,
|
|
SSL_GROUP_SECP256R1,
|
|
false,
|
|
},
|
|
};
|
|
|
|
class PerformHybridHandshakeTest
|
|
: public testing::TestWithParam<HybridHandshakeTest> {};
|
|
INSTANTIATE_TEST_SUITE_P(PerformHybridHandshakeTests,
|
|
PerformHybridHandshakeTest,
|
|
testing::ValuesIn(kHybridHandshakeTests));
|
|
|
|
// This test runs through an overall handshake flow for all of the cases
|
|
// defined in kHybridHandshakeTests. This test runs through both positive and
|
|
// negative cases; refer to the comments inline in kHybridHandshakeTests for
|
|
// specifics about each test case.
|
|
TEST_P(PerformHybridHandshakeTest, PerformHybridHandshake) {
|
|
HybridHandshakeTest t = GetParam();
|
|
// Set up client and server with test case parameters
|
|
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
|
|
ASSERT_TRUE(client_ctx);
|
|
ASSERT_TRUE(SSL_CTX_set1_curves_list(client_ctx.get(), t.client_rule));
|
|
ASSERT_TRUE(
|
|
SSL_CTX_set_max_proto_version(client_ctx.get(), t.client_version));
|
|
|
|
bssl::UniquePtr<SSL_CTX> server_ctx =
|
|
CreateContextWithTestCertificate(TLS_method());
|
|
ASSERT_TRUE(server_ctx);
|
|
ASSERT_TRUE(SSL_CTX_set1_curves_list(server_ctx.get(), t.server_rule));
|
|
ASSERT_TRUE(
|
|
SSL_CTX_set_max_proto_version(server_ctx.get(), t.server_version));
|
|
|
|
bssl::UniquePtr<SSL> client, server;
|
|
ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
|
|
server_ctx.get()));
|
|
|
|
if (t.expected_group != 0) {
|
|
// In this case, assert that the handshake completes as expected.
|
|
ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
|
|
|
|
SSL_SESSION *client_session = SSL_get_session(client.get());
|
|
ASSERT_TRUE(client_session);
|
|
EXPECT_EQ(t.expected_group, client_session->group_id);
|
|
EXPECT_EQ(t.is_hrr_expected, SSL_used_hello_retry_request(client.get()));
|
|
EXPECT_EQ(std::min(t.client_version, t.server_version),
|
|
client_session->ssl_version);
|
|
|
|
SSL_SESSION *server_session = SSL_get_session(server.get());
|
|
ASSERT_TRUE(server_session);
|
|
EXPECT_EQ(t.expected_group, server_session->group_id);
|
|
EXPECT_EQ(t.is_hrr_expected, SSL_used_hello_retry_request(server.get()));
|
|
EXPECT_EQ(std::min(t.client_version, t.server_version),
|
|
server_session->ssl_version);
|
|
} else {
|
|
// In this case, we expect the handshake to fail because client and
|
|
// server configurations are not compatible.
|
|
ASSERT_FALSE(CompleteHandshakes(client.get(), server.get()));
|
|
|
|
ASSERT_FALSE(client.get()->s3->initial_handshake_complete);
|
|
EXPECT_EQ(t.is_hrr_expected, SSL_used_hello_retry_request(client.get()));
|
|
|
|
ASSERT_FALSE(server.get()->s3->initial_handshake_complete);
|
|
EXPECT_EQ(t.is_hrr_expected, SSL_used_hello_retry_request(server.get()));
|
|
}
|
|
}
|
|
|
|
BSSL_NAMESPACE_END
|