Skip to content

Commit

Permalink
Implement X25519MLKEM768 for TLS
Browse files Browse the repository at this point in the history
ML-KEM is now finalized, so uses of Kyber should migrate to ML-KEM. This
adds the new codepoint for TLS, X25519MLKEM768 from
draft-kwiatkowski-tls-ecdhe-mlkem-01.

One detail to call out: where the Kyber hybrid put X25519 first, this
one places MLKEM768 first. Section 3 of the draft discusses why.

Bug: 40910498
Change-Id: I18862cd5d25d6ab6c4b38514e8333684dc5e3778
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/70547
Reviewed-by: Adam Langley <[email protected]>
Commit-Queue: David Benjamin <[email protected]>
  • Loading branch information
davidben authored and Boringssl LUCI CQ committed Aug 27, 2024
1 parent 6f7c3de commit 7fb4d3d
Show file tree
Hide file tree
Showing 15 changed files with 355 additions and 126 deletions.
5 changes: 4 additions & 1 deletion crypto/obj/obj_dat.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
/* This file is generated by crypto/obj/objects.go. */


#define NUM_NID 965
#define NUM_NID 966

static const uint8_t kObjectData[] = {
/* NID_rsadsi */
Expand Down Expand Up @@ -8783,6 +8783,7 @@ static const ASN1_OBJECT kObjects[NUM_NID] = {
{"HKDF", "hkdf", NID_hkdf, 0, NULL, 0},
{"X25519Kyber768Draft00", "X25519Kyber768Draft00",
NID_X25519Kyber768Draft00, 0, NULL, 0},
{"X25519MLKEM768", "X25519MLKEM768", NID_X25519MLKEM768, 0, NULL, 0},
};

static const uint16_t kNIDsInShortNameOrder[] = {
Expand Down Expand Up @@ -8981,6 +8982,7 @@ static const uint16_t kNIDsInShortNameOrder[] = {
458 /* UID */,
948 /* X25519 */,
964 /* X25519Kyber768Draft00 */,
965 /* X25519MLKEM768 */,
961 /* X448 */,
11 /* X500 */,
378 /* X500algorithms */,
Expand Down Expand Up @@ -9852,6 +9854,7 @@ static const uint16_t kNIDsInLongNameOrder[] = {
375 /* Trust Root */,
948 /* X25519 */,
964 /* X25519Kyber768Draft00 */,
965 /* X25519MLKEM768 */,
961 /* X448 */,
12 /* X509 */,
402 /* X509v3 AC Targeting */,
Expand Down
1 change: 1 addition & 0 deletions crypto/obj/obj_mac.num
Original file line number Diff line number Diff line change
Expand Up @@ -952,3 +952,4 @@ X448 961
sha512_256 962
hkdf 963
X25519Kyber768Draft00 964
X25519MLKEM768 965
1 change: 1 addition & 0 deletions crypto/obj/objects.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,7 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme

# NIDs for post quantum hybrid KEMs in TLS (no corresponding OIDs).
: X25519Kyber768Draft00
: X25519MLKEM768

# See RFC 8410.
1 3 101 110 : X25519
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
module boringssl.googlesource.com/boringssl

go 1.21
go 1.22

require (
golang.org/x/crypto v0.25.0
golang.org/x/crypto v0.26.0
golang.org/x/net v0.27.0
)

require (
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
filippo.io/mlkem768 v0.0.0-20240821141156-859a9b3f2ff6 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.23.0 // indirect
)
14 changes: 8 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
filippo.io/mlkem768 v0.0.0-20240821141156-859a9b3f2ff6 h1:A7gTX0HxgkmTtCgRtpWlhIuMBBszxW/02MXv55wHk4U=
filippo.io/mlkem768 v0.0.0-20240821141156-859a9b3f2ff6/go.mod h1:IkpYfciLz5fI/S4/Z0NlhR4cpv6ubCMDnIwAe0XiojA=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
3 changes: 3 additions & 0 deletions include/openssl/nid.h
Original file line number Diff line number Diff line change
Expand Up @@ -4255,6 +4255,9 @@ extern "C" {
#define SN_X25519Kyber768Draft00 "X25519Kyber768Draft00"
#define NID_X25519Kyber768Draft00 964

#define SN_X25519MLKEM768 "X25519MLKEM768"
#define NID_X25519MLKEM768 965


#if defined(__cplusplus)
} /* extern C */
Expand Down
1 change: 1 addition & 0 deletions include/openssl/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2548,6 +2548,7 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx);
#define SSL_GROUP_SECP384R1 24
#define SSL_GROUP_SECP521R1 25
#define SSL_GROUP_X25519 29
#define SSL_GROUP_X25519_MLKEM768 0x11ec
#define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399

// SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|.
Expand Down
1 change: 1 addition & 0 deletions ssl/extensions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) {
static bool is_post_quantum_group(uint16_t id) {
switch (id) {
case SSL_GROUP_X25519_KYBER768_DRAFT00:
case SSL_GROUP_X25519_MLKEM768:
return true;
default:
return false;
Expand Down
100 changes: 97 additions & 3 deletions ssl/ssl_key_share.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <openssl/experimental/kyber.h>
#include <openssl/hrss.h>
#include <openssl/mem.h>
#include <openssl/mlkem.h>
#include <openssl/nid.h>
#include <openssl/rand.h>
#include <openssl/span.h>
Expand Down Expand Up @@ -192,6 +193,7 @@ class X25519KeyShare : public SSLKeyShare {
uint8_t private_key_[32];
};

// draft-tls-westerbaan-xyber768d00-03
class X25519Kyber768KeyShare : public SSLKeyShare {
public:
X25519Kyber768KeyShare() {}
Expand Down Expand Up @@ -225,9 +227,7 @@ class X25519Kyber768KeyShare : public SSLKeyShare {
uint8_t x25519_public_key[32];
X25519_keypair(x25519_public_key, x25519_private_key_);
KYBER_public_key peer_kyber_pub;
CBS peer_key_cbs;
CBS peer_x25519_cbs;
CBS peer_kyber_cbs;
CBS peer_key_cbs, peer_x25519_cbs, peer_kyber_cbs;
CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size());
if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) ||
!CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs,
Expand Down Expand Up @@ -282,6 +282,97 @@ class X25519Kyber768KeyShare : public SSLKeyShare {
KYBER_private_key kyber_private_key_;
};

// draft-kwiatkowski-tls-ecdhe-mlkem-01
class X25519MLKEM768KeyShare : public SSLKeyShare {
public:
X25519MLKEM768KeyShare() {}

uint16_t GroupID() const override { return SSL_GROUP_X25519_MLKEM768; }

bool Generate(CBB *out) override {
uint8_t mlkem_public_key[MLKEM768_PUBLIC_KEY_BYTES];
MLKEM768_generate_key(mlkem_public_key, /*optional_out_seed=*/nullptr,
&mlkem_private_key_);

uint8_t x25519_public_key[X25519_PUBLIC_VALUE_LEN];
X25519_keypair(x25519_public_key, x25519_private_key_);

if (!CBB_add_bytes(out, mlkem_public_key, sizeof(mlkem_public_key)) ||
!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key))) {
return false;
}

return true;
}

bool Encap(CBB *out_ciphertext, Array<uint8_t> *out_secret,
uint8_t *out_alert, Span<const uint8_t> peer_key) override {
Array<uint8_t> secret;
if (!secret.Init(MLKEM_SHARED_SECRET_BYTES + X25519_SHARED_KEY_LEN)) {
return false;
}

MLKEM768_public_key peer_mlkem_pub;
uint8_t x25519_public_key[X25519_PUBLIC_VALUE_LEN];
X25519_keypair(x25519_public_key, x25519_private_key_);
CBS peer_key_cbs, peer_mlkem_cbs, peer_x25519_cbs;
CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size());
if (!CBS_get_bytes(&peer_key_cbs, &peer_mlkem_cbs,
MLKEM768_PUBLIC_KEY_BYTES) ||
!MLKEM768_parse_public_key(&peer_mlkem_pub, &peer_mlkem_cbs) ||
!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs,
X25519_PUBLIC_VALUE_LEN) ||
CBS_len(&peer_key_cbs) != 0 ||
!X25519(secret.data() + MLKEM_SHARED_SECRET_BYTES, x25519_private_key_,
CBS_data(&peer_x25519_cbs))) {
*out_alert = SSL_AD_DECODE_ERROR;
OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
return false;
}

uint8_t mlkem_ciphertext[MLKEM768_CIPHERTEXT_BYTES];
MLKEM768_encap(mlkem_ciphertext, secret.data(), &peer_mlkem_pub);

if (!CBB_add_bytes(out_ciphertext, mlkem_ciphertext,
sizeof(mlkem_ciphertext)) ||
!CBB_add_bytes(out_ciphertext, x25519_public_key,
sizeof(x25519_public_key))) {
return false;
}

*out_secret = std::move(secret);
return true;
}

bool Decap(Array<uint8_t> *out_secret, uint8_t *out_alert,
Span<const uint8_t> ciphertext) override {
*out_alert = SSL_AD_INTERNAL_ERROR;

Array<uint8_t> secret;
if (!secret.Init(MLKEM_SHARED_SECRET_BYTES + X25519_SHARED_KEY_LEN)) {
return false;
}

if (ciphertext.size() !=
MLKEM768_CIPHERTEXT_BYTES + X25519_PUBLIC_VALUE_LEN ||
!MLKEM768_decap(secret.data(), ciphertext.data(),
MLKEM768_CIPHERTEXT_BYTES, &mlkem_private_key_) ||
!X25519(secret.data() + MLKEM_SHARED_SECRET_BYTES, x25519_private_key_,
ciphertext.data() + MLKEM768_CIPHERTEXT_BYTES)) {
*out_alert = SSL_AD_DECODE_ERROR;
OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
return false;
}

*out_secret = std::move(secret);
return true;
}

private:
uint8_t x25519_private_key_[32];
MLKEM768_private_key mlkem_private_key_;
};

constexpr NamedGroup kNamedGroups[] = {
{NID_secp224r1, SSL_GROUP_SECP224R1, "P-224", "secp224r1"},
{NID_X9_62_prime256v1, SSL_GROUP_SECP256R1, "P-256", "prime256v1"},
Expand All @@ -290,6 +381,7 @@ constexpr NamedGroup kNamedGroups[] = {
{NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"},
{NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00,
"X25519Kyber768Draft00", ""},
{NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, "X25519MLKEM768", ""},
};

} // namespace
Expand All @@ -312,6 +404,8 @@ UniquePtr<SSLKeyShare> SSLKeyShare::Create(uint16_t group_id) {
return MakeUnique<X25519KeyShare>();
case SSL_GROUP_X25519_KYBER768_DRAFT00:
return MakeUnique<X25519Kyber768KeyShare>();
case SSL_GROUP_X25519_MLKEM768:
return MakeUnique<X25519MLKEM768KeyShare>();
default:
return nullptr;
}
Expand Down
4 changes: 4 additions & 0 deletions ssl/ssl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ static const CurveTest kCurveTests[] = {
"P-256:X25519Kyber768Draft00",
{ SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER768_DRAFT00 },
},
{
"P-256:X25519MLKEM768",
{ SSL_GROUP_SECP256R1, SSL_GROUP_X25519_MLKEM768 },
},

{
"P-256:P-384:P-521:X25519",
Expand Down
5 changes: 3 additions & 2 deletions ssl/test/fuzzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,9 @@ class TLSFuzzer {
}

static const uint16_t kGroups[] = {
SSL_GROUP_X25519_KYBER768_DRAFT00, SSL_GROUP_X25519,
SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1, SSL_GROUP_SECP521R1};
SSL_GROUP_X25519_MLKEM768, SSL_GROUP_X25519_KYBER768_DRAFT00,
SSL_GROUP_X25519, SSL_GROUP_SECP256R1,
SSL_GROUP_SECP384R1, SSL_GROUP_SECP521R1};
if (!SSL_CTX_set1_group_ids(ctx_.get(), kGroups,
OPENSSL_ARRAY_SIZE(kGroups))) {
return false;
Expand Down
9 changes: 5 additions & 4 deletions ssl/test/runner/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ const (
CurveP384 CurveID = 24
CurveP521 CurveID = 25
CurveX25519 CurveID = 29
CurveX25519MLKEM768 CurveID = 0x11ec
CurveX25519Kyber768 CurveID = 0x6399
)

Expand Down Expand Up @@ -1962,9 +1963,9 @@ type ProtocolBugs struct {
// hello retry.
FailIfHelloRetryRequested bool

// FailedIfKyberOffered will cause a server to reject a ClientHello if Kyber
// is supported.
FailIfKyberOffered bool
// FailIfPostQuantumOffered will cause a server to reject a ClientHello if
// post-quantum curves are supported.
FailIfPostQuantumOffered bool

// ExpectKeyShares, if not nil, lists (in order) the curves that a ClientHello
// should have key shares for.
Expand Down Expand Up @@ -2067,7 +2068,7 @@ func (c *Config) maxVersion(isDTLS bool) uint16 {
return ret
}

var defaultCurvePreferences = []CurveID{CurveX25519Kyber768, CurveX25519, CurveP256, CurveP384, CurveP521}
var defaultCurvePreferences = []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768, CurveX25519, CurveP256, CurveP384, CurveP521}

func (c *Config) curvePreferences() []CurveID {
if c == nil || len(c.CurvePreferences) == 0 {
Expand Down
4 changes: 2 additions & 2 deletions ssl/test/runner/handshake_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,10 @@ func (hs *serverHandshakeState) readClientHello() error {
}
}

if config.Bugs.FailIfKyberOffered {
if config.Bugs.FailIfPostQuantumOffered {
for _, offeredCurve := range hs.clientHello.supportedCurves {
if isPqGroup(offeredCurve) {
return errors.New("tls: X25519Kyber768 was offered")
return errors.New("tls: post-quantum group was offered")
}
}
}
Expand Down
Loading

0 comments on commit 7fb4d3d

Please sign in to comment.