1
0
mirror of https://github.com/golang/go synced 2024-11-14 13:20:30 -07:00

crypto/tls: implement X25519Kyber768Draft00

Forced the testConfig CurvePreferences to exclude X25519Kyber768Draft00
to avoid bloating the transcripts, but I manually tested it and the
tests all update and pass successfully, causing 7436 insertions(+), 3251
deletions(-).

Fixes #67061

Change-Id: If6f13bca561835777ab0889a490487b7c2366c3c
Reviewed-on: https://go-review.googlesource.com/c/go/+/586656
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Filippo Valsorda 2024-05-18 20:15:38 +02:00 committed by Gopher Robot
parent 7c52c064df
commit d0edd9acc8
18 changed files with 498 additions and 102 deletions

View File

@ -179,6 +179,10 @@ Previous versions default to `winreadlinkvolume=0`.
Go 1.23 corrected the semantics of contention reports for runtime-internal locks, Go 1.23 corrected the semantics of contention reports for runtime-internal locks,
and so removed the [`runtimecontentionstacks` setting](/pkg/runtime#hdr-Environment_Variable). and so removed the [`runtimecontentionstacks` setting](/pkg/runtime#hdr-Environment_Variable).
Go 1.23 enabled the experimental post-quantum key exchange mechanism
X25519Kyber768Draft00 by default. The default can be reverted using the
[`tlskyber` setting](/pkg/crypto/tls/#Config.CurvePreferences).
### Go 1.22 ### Go 1.22
Go 1.22 adds a configurable limit to control the maximum acceptable RSA key size Go 1.22 adds a configurable limit to control the maximum acceptable RSA key size

View File

@ -6,6 +6,10 @@
"SendEmptyRecords*": "crypto/tls doesn't implement spam protections", "SendEmptyRecords*": "crypto/tls doesn't implement spam protections",
"SendWarningAlerts*": "crypto/tls doesn't implement spam protections", "SendWarningAlerts*": "crypto/tls doesn't implement spam protections",
"TooManyKeyUpdates": "crypto/tls doesn't implement spam protections (TODO: I think?)", "TooManyKeyUpdates": "crypto/tls doesn't implement spam protections (TODO: I think?)",
"KyberNotEnabledByDefaultInClients": "crypto/tls intentionally enables it",
"JustConfiguringKyberWorks": "we always send a X25519 key share with Kyber",
"KyberKeyShareIncludedSecond": "we always send the Kyber key share first",
"KyberKeyShareIncludedThird": "we always send the Kyber key share first",
"SkipNewSessionTicket": "TODO confusing? maybe bug", "SkipNewSessionTicket": "TODO confusing? maybe bug",
"SendUserCanceledAlerts*": "TODO may be a real bug?", "SendUserCanceledAlerts*": "TODO may be a real bug?",
"GREASE-Server-TLS13": "TODO ???", "GREASE-Server-TLS13": "TODO ???",
@ -14,6 +18,12 @@
"EchoTLS13CompatibilitySessionID": "TODO reject compat session ID", "EchoTLS13CompatibilitySessionID": "TODO reject compat session ID",
"*ECH-Server*": "no ECH server support", "*ECH-Server*": "no ECH server support",
"TLS-ECH-Client-UnsolictedHRRExtension": "TODO", "TLS-ECH-Client-UnsolictedHRRExtension": "TODO",
"*Client-P-224*": "no P-224 support",
"*Server-P-224*": "no P-224 support",
"CurveID-Resume*": "unexposed curveID is not stored in the ticket yet",
"CheckLeafCurve": "TODO: first pass, this should be fixed",
"DisabledCurve-HelloRetryRequest-TLS13": "TODO: first pass, this should be fixed",
"UnsupportedCurve": "TODO: first pass, this should be fixed",
"SupportTicketsWithSessionID": "TODO: first pass, this should be fixed", "SupportTicketsWithSessionID": "TODO: first pass, this should be fixed",
"NoNullCompression-TLS12": "TODO: first pass, this should be fixed", "NoNullCompression-TLS12": "TODO: first pass, this should be fixed",
"KeyUpdate-RequestACK": "TODO: first pass, this should be fixed", "KeyUpdate-RequestACK": "TODO: first pass, this should be fixed",
@ -172,4 +182,4 @@
"CheckClientCertificateTypes": "TODO: first pass, this should be fixed", "CheckClientCertificateTypes": "TODO: first pass, this should be fixed",
"CheckECDSACurve-TLS12": "TODO: first pass, this should be fixed" "CheckECDSACurve-TLS12": "TODO: first pass, this should be fixed"
} }
} }

View File

@ -15,6 +15,8 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings"
"testing" "testing"
) )
@ -40,6 +42,9 @@ var (
resumeCount = flag.Int("resume-count", 0, "") resumeCount = flag.Int("resume-count", 0, "")
curves = flagStringSlice("curves", "")
expectedCurve = flag.String("expect-curve-id", "", "")
shimID = flag.Uint64("shim-id", 0, "") shimID = flag.Uint64("shim-id", 0, "")
_ = flag.Bool("ipv6", false, "") _ = flag.Bool("ipv6", false, "")
@ -132,6 +137,23 @@ var (
// -verify-prefs // -verify-prefs
) )
type stringSlice []string
func flagStringSlice(name, usage string) *stringSlice {
f := &stringSlice{}
flag.Var(f, name, usage)
return f
}
func (saf stringSlice) String() string {
return strings.Join(saf, ",")
}
func (saf stringSlice) Set(s string) error {
saf = append(saf, s)
return nil
}
func bogoShim() { func bogoShim() {
if *isHandshakerSupported { if *isHandshakerSupported {
fmt.Println("No") fmt.Println("No")
@ -177,6 +199,16 @@ func bogoShim() {
cfg.ClientAuth = RequireAnyClientCert cfg.ClientAuth = RequireAnyClientCert
} }
if len(*curves) != 0 {
for _, curveStr := range *curves {
id, err := strconv.Atoi(curveStr)
if err != nil {
log.Fatalf("failed to parse curve id %q: %s", curveStr, err)
}
cfg.CurvePreferences = append(cfg.CurvePreferences, CurveID(id))
}
}
for i := 0; i < *resumeCount+1; i++ { for i := 0; i < *resumeCount+1; i++ {
conn, err := net.Dial("tcp", net.JoinHostPort("localhost", *port)) conn, err := net.Dial("tcp", net.JoinHostPort("localhost", *port))
if err != nil { if err != nil {
@ -221,6 +253,16 @@ func bogoShim() {
log.Fatalf("write err: %s", err) log.Fatalf("write err: %s", err)
} }
} }
if *expectedCurve != "" {
expectedCurveID, err := strconv.Atoi(*expectedCurve)
if err != nil {
log.Fatalf("failed to parse -expect-curve-id: %s", err)
}
if tlsConn.curveID != CurveID(expectedCurveID) {
log.Fatalf("unexpected curve id: want %d, got %d", expectedCurveID, tlsConn.curveID)
}
}
} }
} }
@ -238,7 +280,7 @@ func TestBogoSuite(t *testing.T) {
t.Skip("#66913: windows network connections are flakey on builders") t.Skip("#66913: windows network connections are flakey on builders")
} }
const boringsslModVer = "v0.0.0-20240412155355-1c6e10495e4f" const boringsslModVer = "v0.0.0-20240517213134-ba62c812f01f"
output, err := exec.Command("go", "mod", "download", "-json", "github.com/google/boringssl@"+boringsslModVer).CombinedOutput() output, err := exec.Command("go", "mod", "download", "-json", "github.com/google/boringssl@"+boringsslModVer).CombinedOutput()
if err != nil { if err != nil {
t.Fatalf("failed to download boringssl: %s", err) t.Fatalf("failed to download boringssl: %s", err)
@ -263,6 +305,8 @@ func TestBogoSuite(t *testing.T) {
"-shim-extra-flags=-bogo-mode", "-shim-extra-flags=-bogo-mode",
"-allow-unimplemented", "-allow-unimplemented",
"-loose-errors", // TODO(roland): this should be removed eventually "-loose-errors", // TODO(roland): this should be removed eventually
"-pipe",
"-v",
} }
if *bogoFilter != "" { if *bogoFilter != "" {
args = append(args, fmt.Sprintf("-test=%s", *bogoFilter)) args = append(args, fmt.Sprintf("-test=%s", *bogoFilter))
@ -273,10 +317,22 @@ func TestBogoSuite(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
cmd := exec.Command(goCmd, args...) cmd := exec.Command(goCmd, args...)
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr out := &strings.Builder{}
cmd.Stdout, cmd.Stderr = io.MultiWriter(os.Stdout, out), os.Stderr
cmd.Dir = filepath.Join(j.Dir, "ssl/test/runner") cmd.Dir = filepath.Join(j.Dir, "ssl/test/runner")
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
t.Fatalf("bogo failed: %s", err) t.Fatalf("bogo failed: %s", err)
} }
if *bogoFilter == "" {
assertPass := func(t *testing.T, name string) {
t.Helper()
if !strings.Contains(out.String(), "PASSED ("+name+")\n") {
t.Errorf("Expected test %s did not run", name)
}
}
assertPass(t, "CurveTest-Client-Kyber-TLS13")
assertPass(t, "CurveTest-Server-Kyber-TLS13")
}
} }

View File

@ -165,6 +165,10 @@ func TestBoringServerCurves(t *testing.T) {
t.Run(fmt.Sprintf("curve=%d", curveid), func(t *testing.T) { t.Run(fmt.Sprintf("curve=%d", curveid), func(t *testing.T) {
clientConfig := testConfig.Clone() clientConfig := testConfig.Clone()
clientConfig.CurvePreferences = []CurveID{curveid} clientConfig.CurvePreferences = []CurveID{curveid}
if curveid == x25519Kyber768Draft00 {
// x25519Kyber768Draft00 is not supported standalone.
clientConfig.CurvePreferences = append(clientConfig.CurvePreferences, X25519)
}
if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil { if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil {
t.Fatalf("got error: %v, expected success", err) t.Fatalf("got error: %v, expected success", err)
} }

View File

@ -130,11 +130,13 @@ const (
scsvRenegotiation uint16 = 0x00ff scsvRenegotiation uint16 = 0x00ff
) )
// CurveID is the type of a TLS identifier for an elliptic curve. See // CurveID is the type of a TLS identifier for a key exchange mechanism. See
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8. // https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8.
// //
// In TLS 1.3, this type is called NamedGroup, but at this time this library // In TLS 1.2, this registry used to support only elliptic curves. In TLS 1.3,
// only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7. // it was extended to other groups and renamed NamedGroup. See RFC 8446, Section
// 4.2.7. It was then also extended to other mechanisms, such as hybrid
// post-quantum KEMs.
type CurveID uint16 type CurveID uint16
const ( const (
@ -142,6 +144,11 @@ const (
CurveP384 CurveID = 24 CurveP384 CurveID = 24
CurveP521 CurveID = 25 CurveP521 CurveID = 25
X25519 CurveID = 29 X25519 CurveID = 29
// Experimental codepoint for X25519Kyber768Draft00, specified in
// draft-tls-westerbaan-xyber768d00-03. Not exported, as support might be
// removed in the future.
x25519Kyber768Draft00 CurveID = 0x6399 // X25519Kyber768Draft00
) )
// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8. // TLS 1.3 Key Share. See RFC 8446, Section 4.2.8.
@ -302,6 +309,10 @@ type ConnectionState struct {
// testingOnlyDidHRR is true if a HelloRetryRequest was sent/received. // testingOnlyDidHRR is true if a HelloRetryRequest was sent/received.
testingOnlyDidHRR bool testingOnlyDidHRR bool
// testingOnlyCurveID is the selected CurveID, or zero if an RSA exchanges
// is performed.
testingOnlyCurveID CurveID
} }
// ExportKeyingMaterial returns length bytes of exported key material in a new // ExportKeyingMaterial returns length bytes of exported key material in a new
@ -375,7 +386,7 @@ type ClientSessionCache interface {
Put(sessionKey string, cs *ClientSessionState) Put(sessionKey string, cs *ClientSessionState)
} }
//go:generate stringer -type=SignatureScheme,CurveID,ClientAuthType -output=common_string.go //go:generate stringer -linecomment -type=SignatureScheme,CurveID,ClientAuthType -output=common_string.go
// SignatureScheme identifies a signature algorithm supported by TLS. See // SignatureScheme identifies a signature algorithm supported by TLS. See
// RFC 8446, Section 4.2.3. // RFC 8446, Section 4.2.3.
@ -757,6 +768,10 @@ type Config struct {
// an ECDHE handshake, in preference order. If empty, the default will // an ECDHE handshake, in preference order. If empty, the default will
// be used. The client will use the first preference as the type for // be used. The client will use the first preference as the type for
// its key share in TLS 1.3. This may change in the future. // its key share in TLS 1.3. This may change in the future.
//
// From Go 1.23, the default includes the X25519Kyber768Draft00 hybrid
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
// or use the GODEBUG=tlskyber=0 environment variable.
CurvePreferences []CurveID CurvePreferences []CurveID
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records. // DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
@ -1084,20 +1099,27 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 {
return versions return versions
} }
var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521} var tlskyber = godebug.New("tlskyber")
func (c *Config) curvePreferences() []CurveID { var defaultCurvePreferences = []CurveID{x25519Kyber768Draft00, X25519, CurveP256, CurveP384, CurveP521}
var defaultCurvePreferencesWithoutKyber = []CurveID{X25519, CurveP256, CurveP384, CurveP521}
func (c *Config) curvePreferences(version uint16) []CurveID {
if needFIPS() { if needFIPS() {
return fipsCurvePreferences(c) return fipsCurvePreferences(c)
} }
if c == nil || len(c.CurvePreferences) == 0 { if c == nil || len(c.CurvePreferences) == 0 {
if version < VersionTLS13 || tlskyber.Value() == "0" {
return defaultCurvePreferencesWithoutKyber
}
return defaultCurvePreferences return defaultCurvePreferences
} }
return c.CurvePreferences return c.CurvePreferences
} }
func (c *Config) supportsCurve(curve CurveID) bool { func (c *Config) supportsCurve(version uint16, curve CurveID) bool {
for _, cc := range c.curvePreferences() { for _, cc := range c.curvePreferences(version) {
if cc == curve { if cc == curve {
return true return true
} }
@ -1256,7 +1278,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
} }
// The only signed key exchange we support is ECDHE. // The only signed key exchange we support is ECDHE.
if !supportsECDHE(config, chi.SupportedCurves, chi.SupportedPoints) { if !supportsECDHE(config, vers, chi.SupportedCurves, chi.SupportedPoints) {
return supportsRSAFallback(errors.New("client doesn't support ECDHE, can only use legacy RSA key exchange")) return supportsRSAFallback(errors.New("client doesn't support ECDHE, can only use legacy RSA key exchange"))
} }
@ -1277,7 +1299,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
} }
var curveOk bool var curveOk bool
for _, c := range chi.SupportedCurves { for _, c := range chi.SupportedCurves {
if c == curve && config.supportsCurve(c) { if c == curve && config.supportsCurve(vers, c) {
curveOk = true curveOk = true
break break
} }

View File

@ -1,4 +1,4 @@
// Code generated by "stringer -type=SignatureScheme,CurveID,ClientAuthType -output=common_string.go"; DO NOT EDIT. // Code generated by "stringer -linecomment -type=SignatureScheme,CurveID,ClientAuthType -output=common_string.go"; DO NOT EDIT.
package tls package tls
@ -71,11 +71,13 @@ func _() {
_ = x[CurveP384-24] _ = x[CurveP384-24]
_ = x[CurveP521-25] _ = x[CurveP521-25]
_ = x[X25519-29] _ = x[X25519-29]
_ = x[x25519Kyber768Draft00-25497]
} }
const ( const (
_CurveID_name_0 = "CurveP256CurveP384CurveP521" _CurveID_name_0 = "CurveP256CurveP384CurveP521"
_CurveID_name_1 = "X25519" _CurveID_name_1 = "X25519"
_CurveID_name_2 = "X25519Kyber768Draft00"
) )
var ( var (
@ -89,6 +91,8 @@ func (i CurveID) String() string {
return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]] return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]]
case i == 29: case i == 29:
return _CurveID_name_1 return _CurveID_name_1
case i == 25497:
return _CurveID_name_2
default: default:
return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")" return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")"
} }

View File

@ -50,6 +50,7 @@ type Conn struct {
didResume bool // whether this connection was a session resumption didResume bool // whether this connection was a session resumption
didHRR bool // whether a HelloRetryRequest was sent/received didHRR bool // whether a HelloRetryRequest was sent/received
cipherSuite uint16 cipherSuite uint16
curveID CurveID
ocspResponse []byte // stapled OCSP response ocspResponse []byte // stapled OCSP response
scts [][]byte // signed certificate timestamps from server scts [][]byte // signed certificate timestamps from server
peerCertificates []*x509.Certificate peerCertificates []*x509.Certificate
@ -1610,6 +1611,8 @@ func (c *Conn) connectionStateLocked() ConnectionState {
state.NegotiatedProtocol = c.clientProtocol state.NegotiatedProtocol = c.clientProtocol
state.DidResume = c.didResume state.DidResume = c.didResume
state.testingOnlyDidHRR = c.didHRR state.testingOnlyDidHRR = c.didHRR
// c.curveID is not set on TLS 1.01.2 resumptions. Fix that before exposing it.
state.testingOnlyCurveID = c.curveID
state.NegotiatedProtocolIsMutual = true state.NegotiatedProtocolIsMutual = true
state.ServerName = c.serverName state.ServerName = c.serverName
state.CipherSuite = c.cipherSuite state.CipherSuite = c.cipherSuite

View File

@ -8,15 +8,16 @@ import (
"bytes" "bytes"
"context" "context"
"crypto" "crypto"
"crypto/ecdh"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519" "crypto/ed25519"
"crypto/internal/mlkem768"
"crypto/rsa" "crypto/rsa"
"crypto/subtle" "crypto/subtle"
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
"internal/byteorder"
"internal/godebug" "internal/godebug"
"io" "io"
"net" "net"
@ -39,7 +40,7 @@ type clientHandshakeState struct {
var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme
func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) { func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, error) {
config := c.config config := c.config
if len(config.ServerName) == 0 && !config.InsecureSkipVerify { if len(config.ServerName) == 0 && !config.InsecureSkipVerify {
return nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config") return nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")
@ -61,30 +62,30 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
if len(supportedVersions) == 0 { if len(supportedVersions) == 0 {
return nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion") return nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion")
} }
maxVersion := config.maxSupportedVersion(roleClient)
clientHelloVersion := config.maxSupportedVersion(roleClient)
// The version at the beginning of the ClientHello was capped at TLS 1.2
// for compatibility reasons. The supported_versions extension is used
// to negotiate versions now. See RFC 8446, Section 4.2.1.
if clientHelloVersion > VersionTLS12 {
clientHelloVersion = VersionTLS12
}
hello := &clientHelloMsg{ hello := &clientHelloMsg{
vers: clientHelloVersion, vers: maxVersion,
compressionMethods: []uint8{compressionNone}, compressionMethods: []uint8{compressionNone},
random: make([]byte, 32), random: make([]byte, 32),
extendedMasterSecret: true, extendedMasterSecret: true,
ocspStapling: true, ocspStapling: true,
scts: true, scts: true,
serverName: hostnameInSNI(config.ServerName), serverName: hostnameInSNI(config.ServerName),
supportedCurves: config.curvePreferences(), supportedCurves: config.curvePreferences(maxVersion),
supportedPoints: []uint8{pointFormatUncompressed}, supportedPoints: []uint8{pointFormatUncompressed},
secureRenegotiationSupported: true, secureRenegotiationSupported: true,
alpnProtocols: config.NextProtos, alpnProtocols: config.NextProtos,
supportedVersions: supportedVersions, supportedVersions: supportedVersions,
} }
// The version at the beginning of the ClientHello was capped at TLS 1.2
// for compatibility reasons. The supported_versions extension is used
// to negotiate versions now. See RFC 8446, Section 4.2.1.
if hello.vers > VersionTLS12 {
hello.vers = VersionTLS12
}
if c.handshakes > 0 { if c.handshakes > 0 {
hello.secureRenegotiation = c.clientFinished[:] hello.secureRenegotiation = c.clientFinished[:]
} }
@ -103,7 +104,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
} }
// Don't advertise TLS 1.2-only cipher suites unless // Don't advertise TLS 1.2-only cipher suites unless
// we're attempting TLS 1.2. // we're attempting TLS 1.2.
if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 { if maxVersion < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
continue continue
} }
hello.cipherSuites = append(hello.cipherSuites, suiteId) hello.cipherSuites = append(hello.cipherSuites, suiteId)
@ -126,14 +127,14 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
} }
} }
if hello.vers >= VersionTLS12 { if maxVersion >= VersionTLS12 {
hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms() hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
} }
if testingOnlyForceClientHelloSignatureAlgorithms != nil { if testingOnlyForceClientHelloSignatureAlgorithms != nil {
hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms
} }
var key *ecdh.PrivateKey var keyShareKeys *keySharePrivateKeys
if hello.supportedVersions[0] == VersionTLS13 { if hello.supportedVersions[0] == VersionTLS13 {
// Reset the list of ciphers when the client only supports TLS 1.3. // Reset the list of ciphers when the client only supports TLS 1.3.
if len(hello.supportedVersions) == 1 { if len(hello.supportedVersions) == 1 {
@ -145,15 +146,40 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...) hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)
} }
curveID := config.curvePreferences()[0] curveID := config.curvePreferences(maxVersion)[0]
if _, ok := curveForCurveID(curveID); !ok { keyShareKeys = &keySharePrivateKeys{curveID: curveID}
return nil, nil, errors.New("tls: CurvePreferences includes unsupported curve") if curveID == x25519Kyber768Draft00 {
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519)
if err != nil {
return nil, nil, err
}
seed := make([]byte, mlkem768.SeedSize)
if _, err := io.ReadFull(config.rand(), seed); err != nil {
return nil, nil, err
}
keyShareKeys.kyber, err = mlkem768.NewKeyFromSeed(seed)
if err != nil {
return nil, nil, err
}
// For draft-tls-westerbaan-xyber768d00-03, we send both a hybrid
// and a standard X25519 key share, since most servers will only
// support the latter. We reuse the same X25519 ephemeral key for
// both, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
hello.keyShares = []keyShare{
{group: x25519Kyber768Draft00, data: append(keyShareKeys.ecdhe.PublicKey().Bytes(),
keyShareKeys.kyber.EncapsulationKey()...)},
{group: X25519, data: keyShareKeys.ecdhe.PublicKey().Bytes()},
}
} else {
if _, ok := curveForCurveID(curveID); !ok {
return nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
}
keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), curveID)
if err != nil {
return nil, nil, err
}
hello.keyShares = []keyShare{{group: curveID, data: keyShareKeys.ecdhe.PublicKey().Bytes()}}
} }
key, err = generateECDHEKey(config.rand(), curveID)
if err != nil {
return nil, nil, err
}
hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
} }
if c.quic != nil { if c.quic != nil {
@ -167,7 +193,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
hello.quicTransportParameters = p hello.quicTransportParameters = p
} }
return hello, key, nil return hello, keyShareKeys, nil
} }
func (c *Conn) clientHandshake(ctx context.Context) (err error) { func (c *Conn) clientHandshake(ctx context.Context) (err error) {
@ -179,7 +205,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
// need to be reset. // need to be reset.
c.didResume = false c.didResume = false
hello, ecdheKey, err := c.makeClientHello() hello, keyShareKeys, err := c.makeClientHello()
if err != nil { if err != nil {
return err return err
} }
@ -249,17 +275,15 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
if c.vers == VersionTLS13 { if c.vers == VersionTLS13 {
hs := &clientHandshakeStateTLS13{ hs := &clientHandshakeStateTLS13{
c: c, c: c,
ctx: ctx, ctx: ctx,
serverHello: serverHello, serverHello: serverHello,
hello: hello, hello: hello,
ecdheKey: ecdheKey, keyShareKeys: keyShareKeys,
session: session, session: session,
earlySecret: earlySecret, earlySecret: earlySecret,
binderKey: binderKey, binderKey: binderKey,
} }
// In TLS 1.3, session tickets are delivered after the handshake.
return hs.handshake() return hs.handshake()
} }
@ -270,12 +294,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
hello: hello, hello: hello,
session: session, session: session,
} }
return hs.handshake()
if err := hs.handshake(); err != nil {
return err
}
return nil
} }
func (c *Conn) loadSession(hello *clientHelloMsg) ( func (c *Conn) loadSession(hello *clientHelloMsg) (
@ -603,6 +622,9 @@ func (hs *clientHandshakeState) doFullHandshake() error {
c.sendAlert(alertUnexpectedMessage) c.sendAlert(alertUnexpectedMessage)
return err return err
} }
if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ {
c.curveID = CurveID(byteorder.BeUint16(skx.key[1:]))
}
msg, err = c.readHandshake(&hs.finishedHash) msg, err = c.readHandshake(&hs.finishedHash)
if err != nil { if err != nil {

View File

@ -8,20 +8,21 @@ import (
"bytes" "bytes"
"context" "context"
"crypto" "crypto"
"crypto/ecdh"
"crypto/hmac" "crypto/hmac"
"crypto/internal/mlkem768"
"crypto/rsa" "crypto/rsa"
"errors" "errors"
"hash" "hash"
"slices"
"time" "time"
) )
type clientHandshakeStateTLS13 struct { type clientHandshakeStateTLS13 struct {
c *Conn c *Conn
ctx context.Context ctx context.Context
serverHello *serverHelloMsg serverHello *serverHelloMsg
hello *clientHelloMsg hello *clientHelloMsg
ecdheKey *ecdh.PrivateKey keyShareKeys *keySharePrivateKeys
session *SessionState session *SessionState
earlySecret []byte earlySecret []byte
@ -36,7 +37,7 @@ type clientHandshakeStateTLS13 struct {
trafficSecret []byte // client_application_traffic_secret_0 trafficSecret []byte // client_application_traffic_secret_0
} }
// handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheKey, and, // handshake requires hs.c, hs.hello, hs.serverHello, hs.keyShareKeys, and,
// optionally, hs.session, hs.earlySecret and hs.binderKey to be set. // optionally, hs.session, hs.earlySecret and hs.binderKey to be set.
func (hs *clientHandshakeStateTLS13) handshake() error { func (hs *clientHandshakeStateTLS13) handshake() error {
c := hs.c c := hs.c
@ -53,7 +54,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
} }
// Consistency check on the presence of a keyShare and its parameters. // Consistency check on the presence of a keyShare and its parameters.
if hs.ecdheKey == nil || len(hs.hello.keyShares) != 1 { if hs.keyShareKeys == nil || hs.keyShareKeys.ecdhe == nil || len(hs.hello.keyShares) == 0 {
return c.sendAlert(alertInternalError) return c.sendAlert(alertInternalError)
} }
@ -221,21 +222,22 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
// a group we advertised but did not send a key share for, and send a key // a group we advertised but did not send a key share for, and send a key
// share for it this time. // share for it this time.
if curveID := hs.serverHello.selectedGroup; curveID != 0 { if curveID := hs.serverHello.selectedGroup; curveID != 0 {
curveOK := false if !slices.Contains(hs.hello.supportedCurves, curveID) {
for _, id := range hs.hello.supportedCurves {
if id == curveID {
curveOK = true
break
}
}
if !curveOK {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: server selected unsupported group") return errors.New("tls: server selected unsupported group")
} }
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); sentID == curveID { if slices.ContainsFunc(hs.hello.keyShares, func(ks keyShare) bool {
return ks.group == curveID
}) {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share") return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
} }
// Note: we don't support selecting X25519Kyber768Draft00 in a HRR,
// because we currently only support it at all when CurvePreferences is
// empty, which will cause us to also send a key share for it.
//
// This will have to change once we support selecting hybrid KEMs
// without sending key shares for them.
if _, ok := curveForCurveID(curveID); !ok { if _, ok := curveForCurveID(curveID); !ok {
c.sendAlert(alertInternalError) c.sendAlert(alertInternalError)
return errors.New("tls: CurvePreferences includes unsupported curve") return errors.New("tls: CurvePreferences includes unsupported curve")
@ -245,7 +247,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
c.sendAlert(alertInternalError) c.sendAlert(alertInternalError)
return err return err
} }
hs.ecdheKey = key hs.keyShareKeys = &keySharePrivateKeys{curveID: curveID, ecdhe: key}
hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}} hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
} }
@ -333,7 +335,9 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: server did not send a key share") return errors.New("tls: server did not send a key share")
} }
if sentID, _ := curveIDForCurve(hs.ecdheKey.Curve()); hs.serverHello.serverShare.group != sentID { if !slices.ContainsFunc(hs.hello.keyShares, func(ks keyShare) bool {
return ks.group == hs.serverHello.serverShare.group
}) {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: server selected unsupported group") return errors.New("tls: server selected unsupported group")
} }
@ -372,16 +376,37 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
c := hs.c c := hs.c
peerKey, err := hs.ecdheKey.Curve().NewPublicKey(hs.serverHello.serverShare.data) ecdhePeerData := hs.serverHello.serverShare.data
if hs.serverHello.serverShare.group == x25519Kyber768Draft00 {
if len(ecdhePeerData) != x25519PublicKeySize+mlkem768.CiphertextSize {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid server key share")
}
ecdhePeerData = hs.serverHello.serverShare.data[:x25519PublicKeySize]
}
peerKey, err := hs.keyShareKeys.ecdhe.Curve().NewPublicKey(ecdhePeerData)
if err != nil { if err != nil {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid server key share") return errors.New("tls: invalid server key share")
} }
sharedKey, err := hs.ecdheKey.ECDH(peerKey) sharedKey, err := hs.keyShareKeys.ecdhe.ECDH(peerKey)
if err != nil { if err != nil {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid server key share") return errors.New("tls: invalid server key share")
} }
if hs.serverHello.serverShare.group == x25519Kyber768Draft00 {
if hs.keyShareKeys.kyber == nil {
return c.sendAlert(alertInternalError)
}
ciphertext := hs.serverHello.serverShare.data[x25519PublicKeySize:]
kyberShared, err := kyberDecapsulate(hs.keyShareKeys.kyber, ciphertext)
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid Kyber server key share")
}
sharedKey = append(sharedKey, kyberShared...)
}
c.curveID = hs.serverHello.serverShare.group
earlySecret := hs.earlySecret earlySecret := hs.earlySecret
if !hs.usingPSK { if !hs.usingPSK {

View File

@ -15,6 +15,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
"internal/byteorder"
"io" "io"
"time" "time"
) )
@ -247,7 +248,7 @@ func (hs *serverHandshakeState) processClientHello() error {
hs.hello.scts = hs.cert.SignedCertificateTimestamps hs.hello.scts = hs.cert.SignedCertificateTimestamps
} }
hs.ecdheOk = supportsECDHE(c.config, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints) hs.ecdheOk = supportsECDHE(c.config, c.vers, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints)
if hs.ecdheOk && len(hs.clientHello.supportedPoints) > 0 { if hs.ecdheOk && len(hs.clientHello.supportedPoints) > 0 {
// Although omitting the ec_point_formats extension is permitted, some // Although omitting the ec_point_formats extension is permitted, some
@ -318,10 +319,10 @@ func negotiateALPN(serverProtos, clientProtos []string, quic bool) (string, erro
// supportsECDHE returns whether ECDHE key exchanges can be used with this // supportsECDHE returns whether ECDHE key exchanges can be used with this
// pre-TLS 1.3 client. // pre-TLS 1.3 client.
func supportsECDHE(c *Config, supportedCurves []CurveID, supportedPoints []uint8) bool { func supportsECDHE(c *Config, version uint16, supportedCurves []CurveID, supportedPoints []uint8) bool {
supportsCurve := false supportsCurve := false
for _, curve := range supportedCurves { for _, curve := range supportedCurves {
if c.supportsCurve(curve) { if c.supportsCurve(version, curve) {
supportsCurve = true supportsCurve = true
break break
} }
@ -587,6 +588,9 @@ func (hs *serverHandshakeState) doFullHandshake() error {
return err return err
} }
if skx != nil { if skx != nil {
if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ {
c.curveID = CurveID(byteorder.BeUint16(skx.key[1:]))
}
if _, err := hs.c.writeHandshakeRecord(skx, &hs.finishedHash); err != nil { if _, err := hs.c.writeHandshakeRecord(skx, &hs.finishedHash); err != nil {
return err return err
} }

View File

@ -81,6 +81,7 @@ func testClientHelloFailure(t *testing.T, serverConfig *Config, m handshakeMessa
} }
} }
s.Close() s.Close()
t.Helper()
if len(expectedSubStr) == 0 { if len(expectedSubStr) == 0 {
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
t.Errorf("Got error: %s; expected to succeed", err) t.Errorf("Got error: %s; expected to succeed", err)

View File

@ -9,6 +9,7 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/hmac" "crypto/hmac"
"crypto/internal/mlkem768"
"crypto/rsa" "crypto/rsa"
"errors" "errors"
"hash" "hash"
@ -178,11 +179,11 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
hs.hello.cipherSuite = hs.suite.id hs.hello.cipherSuite = hs.suite.id
hs.transcript = hs.suite.hash.New() hs.transcript = hs.suite.hash.New()
// Pick the ECDHE group in server preference order, but give priority to // Pick the key exchange method in server preference order, but give
// groups with a key share, to avoid a HelloRetryRequest round-trip. // priority to key shares, to avoid a HelloRetryRequest round-trip.
var selectedGroup CurveID var selectedGroup CurveID
var clientKeyShare *keyShare var clientKeyShare *keyShare
preferredGroups := c.config.curvePreferences() preferredGroups := c.config.curvePreferences(c.vers)
for _, preferredGroup := range preferredGroups { for _, preferredGroup := range preferredGroups {
ki := slices.IndexFunc(hs.clientHello.keyShares, func(ks keyShare) bool { ki := slices.IndexFunc(hs.clientHello.keyShares, func(ks keyShare) bool {
return ks.group == preferredGroup return ks.group == preferredGroup
@ -210,23 +211,35 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
return errors.New("tls: no ECDHE curve supported by both client and server") return errors.New("tls: no ECDHE curve supported by both client and server")
} }
if clientKeyShare == nil { if clientKeyShare == nil {
if err := hs.doHelloRetryRequest(selectedGroup); err != nil { ks, err := hs.doHelloRetryRequest(selectedGroup)
if err != nil {
return err return err
} }
clientKeyShare = &hs.clientHello.keyShares[0] clientKeyShare = ks
} }
c.curveID = selectedGroup
if _, ok := curveForCurveID(selectedGroup); !ok { ecdhGroup := selectedGroup
ecdhData := clientKeyShare.data
if selectedGroup == x25519Kyber768Draft00 {
ecdhGroup = X25519
if len(ecdhData) != x25519PublicKeySize+mlkem768.EncapsulationKeySize {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid Kyber client key share")
}
ecdhData = ecdhData[:x25519PublicKeySize]
}
if _, ok := curveForCurveID(ecdhGroup); !ok {
c.sendAlert(alertInternalError) c.sendAlert(alertInternalError)
return errors.New("tls: CurvePreferences includes unsupported curve") return errors.New("tls: CurvePreferences includes unsupported curve")
} }
key, err := generateECDHEKey(c.config.rand(), selectedGroup) key, err := generateECDHEKey(c.config.rand(), ecdhGroup)
if err != nil { if err != nil {
c.sendAlert(alertInternalError) c.sendAlert(alertInternalError)
return err return err
} }
hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()} hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()}
peerKey, err := key.Curve().NewPublicKey(clientKeyShare.data) peerKey, err := key.Curve().NewPublicKey(ecdhData)
if err != nil { if err != nil {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid client key share") return errors.New("tls: invalid client key share")
@ -236,6 +249,15 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid client key share") return errors.New("tls: invalid client key share")
} }
if selectedGroup == x25519Kyber768Draft00 {
ciphertext, kyberShared, err := kyberEncapsulate(clientKeyShare.data[x25519PublicKeySize:])
if err != nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid Kyber client key share")
}
hs.sharedKey = append(hs.sharedKey, kyberShared...)
hs.hello.serverShare.data = append(hs.hello.serverShare.data, ciphertext...)
}
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil) selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
if err != nil { if err != nil {
@ -479,13 +501,13 @@ func (hs *serverHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
return hs.c.writeChangeCipherRecord() return hs.c.writeChangeCipherRecord()
} }
func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) error { func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) (*keyShare, error) {
c := hs.c c := hs.c
// The first ClientHello gets double-hashed into the transcript upon a // The first ClientHello gets double-hashed into the transcript upon a
// HelloRetryRequest. See RFC 8446, Section 4.4.1. // HelloRetryRequest. See RFC 8446, Section 4.4.1.
if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil { if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
return err return nil, err
} }
chHash := hs.transcript.Sum(nil) chHash := hs.transcript.Sum(nil)
hs.transcript.Reset() hs.transcript.Reset()
@ -503,43 +525,49 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID)
} }
if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil { if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil {
return err return nil, err
} }
if err := hs.sendDummyChangeCipherSpec(); err != nil { if err := hs.sendDummyChangeCipherSpec(); err != nil {
return err return nil, err
} }
// clientHelloMsg is not included in the transcript. // clientHelloMsg is not included in the transcript.
msg, err := c.readHandshake(nil) msg, err := c.readHandshake(nil)
if err != nil { if err != nil {
return err return nil, err
} }
clientHello, ok := msg.(*clientHelloMsg) clientHello, ok := msg.(*clientHelloMsg)
if !ok { if !ok {
c.sendAlert(alertUnexpectedMessage) c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(clientHello, msg) return nil, unexpectedMessageError(clientHello, msg)
} }
if len(clientHello.keyShares) != 1 || clientHello.keyShares[0].group != selectedGroup { if len(clientHello.keyShares) != 1 {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: client sent invalid key share in second ClientHello") return nil, errors.New("tls: client didn't send one key share in second ClientHello")
}
ks := &clientHello.keyShares[0]
if ks.group != selectedGroup {
c.sendAlert(alertIllegalParameter)
return nil, errors.New("tls: client sent unexpected key share in second ClientHello")
} }
if clientHello.earlyData { if clientHello.earlyData {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: client indicated early data in second ClientHello") return nil, errors.New("tls: client indicated early data in second ClientHello")
} }
if illegalClientHelloChange(clientHello, hs.clientHello) { if illegalClientHelloChange(clientHello, hs.clientHello) {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return errors.New("tls: client illegally modified second ClientHello") return nil, errors.New("tls: client illegally modified second ClientHello")
} }
c.didHRR = true c.didHRR = true
hs.clientHello = clientHello hs.clientHello = clientHello
return nil return ks, nil
} }
// illegalClientHelloChange reports whether the two ClientHello messages are // illegalClientHelloChange reports whether the two ClientHello messages are

View File

@ -378,6 +378,7 @@ func runMain(m *testing.M) int {
Certificates: make([]Certificate, 2), Certificates: make([]Certificate, 2),
InsecureSkipVerify: true, InsecureSkipVerify: true,
CipherSuites: allCipherSuites(), CipherSuites: allCipherSuites(),
CurvePreferences: []CurveID{X25519, CurveP256, CurveP384, CurveP521},
MinVersion: VersionTLS10, MinVersion: VersionTLS10,
MaxVersion: VersionTLS13, MaxVersion: VersionTLS13,
} }

View File

@ -16,8 +16,8 @@ import (
"io" "io"
) )
// a keyAgreement implements the client and server side of a TLS key agreement // A keyAgreement implements the client and server side of a TLS 1.01.2 key
// protocol by generating and processing key exchange messages. // agreement protocol by generating and processing key exchange messages.
type keyAgreement interface { type keyAgreement interface {
// On the server side, the first two methods are called in order. // On the server side, the first two methods are called in order.
@ -126,7 +126,7 @@ func md5SHA1Hash(slices [][]byte) []byte {
} }
// hashForServerKeyExchange hashes the given slices and returns their digest // hashForServerKeyExchange hashes the given slices and returns their digest
// using the given hash function (for >= TLS 1.2) or using a default based on // using the given hash function (for TLS 1.2) or using a default based on
// the sigType (for earlier TLS versions). For Ed25519 signatures, which don't // the sigType (for earlier TLS versions). For Ed25519 signatures, which don't
// do pre-hashing, it returns the concatenation of the slices. // do pre-hashing, it returns the concatenation of the slices.
func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint16, slices ...[]byte) []byte { func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint16, slices ...[]byte) []byte {
@ -169,7 +169,7 @@ type ecdheKeyAgreement struct {
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
var curveID CurveID var curveID CurveID
for _, c := range clientHello.supportedCurves { for _, c := range clientHello.supportedCurves {
if config.supportsCurve(c) { if config.supportsCurve(ka.version, c) {
curveID = c curveID = c
break break
} }

View File

@ -7,6 +7,7 @@ package tls
import ( import (
"crypto/ecdh" "crypto/ecdh"
"crypto/hmac" "crypto/hmac"
"crypto/internal/mlkem768"
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
@ -14,6 +15,7 @@ import (
"golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/crypto/sha3"
) )
// This file contains the functions necessary to compute the TLS 1.3 key // This file contains the functions necessary to compute the TLS 1.3 key
@ -117,6 +119,45 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript
} }
} }
type keySharePrivateKeys struct {
curveID CurveID
ecdhe *ecdh.PrivateKey
kyber *mlkem768.DecapsulationKey
}
// kyberDecapsulate implements decapsulation according to Kyber Round 3.
func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) {
K, err := mlkem768.Decapsulate(dk, c)
if err != nil {
return nil, err
}
return kyberSharedSecret(K, c), nil
}
// kyberEncapsulate implements encapsulation according to Kyber Round 3.
func kyberEncapsulate(ek []byte) (c, ss []byte, err error) {
c, ss, err = mlkem768.Encapsulate(ek)
if err != nil {
return nil, nil, err
}
return c, kyberSharedSecret(ss, c), nil
}
func kyberSharedSecret(K, c []byte) []byte {
// Package mlkem768 implements ML-KEM, which compared to Kyber removed a
// final hashing step. Compute SHAKE-256(K || SHA3-256(c), 32) to match Kyber.
// See https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3.
h := sha3.NewShake256()
h.Write(K)
ch := sha3.Sum256(c)
h.Write(ch[:])
out := make([]byte, 32)
h.Read(out)
return out
}
const x25519PublicKeySize = 32
// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman // generateECDHEKey returns a PrivateKey that implements Diffie-Hellman
// according to RFC 8446, Section 4.2.8.2. // according to RFC 8446, Section 4.2.8.2.
func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) { func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) {

View File

@ -6,6 +6,7 @@ package tls
import ( import (
"bytes" "bytes"
"crypto/internal/mlkem768"
"encoding/hex" "encoding/hex"
"hash" "hash"
"strings" "strings"
@ -173,3 +174,39 @@ func TestExtract(t *testing.T) {
}) })
} }
} }
func TestKyberDecapsulate(t *testing.T) {
// From https://pq-crystals.org/kyber/data/kyber-submission-nist-round3.zip
dkBytes, _ := hex.DecodeString("07638FB69868F3D320E5862BD96933FEB311B362093C9B5D50170BCED43F1B536D9A204BB1F22695950BA1F2A9E8EB828B284488760B3FC84FABA04275D5628E39C5B2471374283C503299C0AB49B66B8BBB56A4186624F919A2BA59BB08D8551880C2BEFC4F87F25F59AB587A79C327D792D54C974A69262FF8A78938289E9A87B688B083E0595FE218B6BB1505941CE2E81A5A64C5AAC60417256985349EE47A52420A5F97477B7236AC76BC70E8288729287EE3E34A3DBC3683C0B7B10029FC203418537E7466BA6385A8FF301EE12708F82AAA1E380FC7A88F8F205AB7E88D7E95952A55BA20D09B79A47141D62BF6EB7DD307B08ECA13A5BC5F6B68581C6865B27BBCDDAB142F4B2CBFF488C8A22705FAA98A2B9EEA3530C76662335CC7EA3A00777725EBCCCD2A4636B2D9122FF3AB77123CE0883C1911115E50C9E8A94194E48DD0D09CFFB3ADCD2C1E92430903D07ADBF00532031575AA7F9E7B5A1F3362DEC936D4043C05F2476C07578BC9CBAF2AB4E382727AD41686A96B2548820BB03B32F11B2811AD62F489E951632ABA0D1DF89680CC8A8B53B481D92A68D70B4EA1C3A6A561C0692882B5CA8CC942A8D495AFCB06DE89498FB935B775908FE7A03E324D54CC19D4E1AABD3593B38B19EE1388FE492B43127E5A504253786A0D69AD32601C28E2C88504A5BA599706023A61363E17C6B9BB59BDC697452CD059451983D738CA3FD034E3F5988854CA05031DB09611498988197C6B30D258DFE26265541C89A4B31D6864E9389B03CB74F7EC4323FB9421A4B9790A26D17B0398A26767350909F84D57B6694DF830664CA8B3C3C03ED2AE67B89006868A68527CCD666459AB7F056671000C6164D3A7F266A14D97CBD7004D6C92CACA770B844A4FA9B182E7B18CA885082AC5646FCB4A14E1685FEB0C9CE3372AB95365C04FD83084F80A23FF10A05BF15F7FA5ACC6C0CB462C33CA524FA6B8BB359043BA68609EAA2536E81D08463B19653B5435BA946C9ADDEB202B04B031CC960DCC12E4518D428B32B257A4FC7313D3A7980D80082E934F9D95C32B0A0191A23604384DD9E079BBBAA266D14C3F756B9F2133107433A4E83FA7187282A809203A4FAF841851833D121AC383843A5E55BC2381425E16C7DB4CC9AB5C1B0D91A47E2B8DE0E582C86B6B0D907BB360B97F40AB5D038F6B75C814B27D9B968D419832BC8C2BEE605EF6E5059D33100D90485D378450014221736C07407CAC260408AA64926619788B8601C2A752D1A6CBF820D7C7A04716203225B3895B9342D147A8185CFC1BB65BA06B4142339903C0AC4651385B45D98A8B19D28CD6BAB088787F7EE1B12461766B43CBCCB96434427D93C065550688F6948ED1B5475A425F1B85209D061C08B56C1CC069F6C0A7C6F29358CAB911087732A649D27C9B98F9A48879387D9B00C25959A71654D6F6A946164513E47A75D005986C2363C09F6B537ECA78B9303A5FA457608A586A653A347DB04DFCC19175B3A301172536062A658A95277570C8852CA8973F4AE123A334047DD711C8927A634A03388A527B034BF7A8170FA702C1F7C23EC32D18A2374890BE9C787A9409C82D192C4BB705A2F996CE405DA72C2D9C843EE9F8313ECC7F86D6294D59159D9A879A542E260922ADF999051CC45200C9FFDB60449C49465979272367C083A7D6267A3ED7A7FD47957C219327F7CA73A4007E1627F00B11CC80573C15AEE6640FB8562DFA6B240CA0AD351AC4AC155B96C14C8AB13DD262CDFD51C4BB5572FD616553D17BDD430ACBEA3E95F0B698D66990AB51E5D03783A8B3D278A5720454CF9695CFDCA08485BA099C51CD92A7EA7587C1D15C28E609A81852601B0604010679AA482D51261EC36E36B8719676217FD74C54786488F4B4969C05A8BA27CA3A77CCE73B965923CA554E422B9B61F4754641608AC16C9B8587A32C1C5DD788F88B36B717A46965635DEB67F45B129B99070909C93EB80B42C2B3F3F70343A7CF37E8520E7BCFC416ACA4F18C7981262BA2BFC756AE03278F0EC66DC2057696824BA6769865A601D7148EF6F54E5AF5686AA2906F994CE38A5E0B938F239007003022C03392DF3401B1E4A3A7EBC6161449F73374C8B0140369343D9295FDF511845C4A46EBAAB6CA5492F6800B98C0CC803653A4B1D6E6AAED1932BACC5FEFAA818BA502859BA5494C5F5402C8536A9C4C1888150617F80098F6B2A99C39BC5DC7CF3B5900A21329AB59053ABAA64ED163E859A8B3B3CA3359B750CCC3E710C7AC43C8191CB5D68870C06391C0CB8AEC72B897AC6BE7FBAACC676ED66314C83630E89448C88A1DF04ACEB23ABF2E409EF333C622289C18A2134E650C45257E47475FA33AA537A5A8F7680214716C50D470E3284963CA64F54677AEC54B5272162BF52BC8142E1D4183FC017454A6B5A496831759064024745978CBD51A6CEDC8955DE4CC6D363670A47466E82BE5C23603A17BF22ACDB7CC984AF08C87E14E27753CF587A8EC3447E62C649E887A67C36C9CE98721B697213275646B194F36758673A8ED11284455AFC7A8529F69C97A3C2D7B8C636C0BA55614B768E624E712930F776169B01715725351BC74B47395ED52B25A1313C95164814C34C979CBDFAB85954662CAB485E75087A98CC74BB82CA2D1B5BF2803238480638C40E90B43C7460E7AA917F010151FAB1169987B372ABB59271F7006C24E60236B84B9DDD600623704254617FB498D89E58B0368BCB2103E79353EB587860C1422E476162E425BC2381DB82C6592737E1DD602864B0167A71EC1F223305C02FE25052AF2B3B5A55A0D7A2022D9A798DC0C5874A98702AAF4054C5D80338A5248B5B7BD09C53B5E2A084B047D277A861B1A73BB51488DE04EF573C85230A0470B73175C9FA50594F66A5F50B4150054C93B68186F8B5CBC49316C8548A642B2B36A1D454C7489AC33B2D2CE6668096782A2C1E0866D21A65E16B585E7AF8618BDF3184C1986878508917277B93E10706B1614972B2A94C7310FE9C708C231A1A8AC8D9314A529A97F469BF64962D820648443099A076D55D4CEA824A58304844F99497C10A25148618A315D72CA857D1B04D575B94F85C01D19BEF211BF0AA3362E7041FD16596D808E867B44C4C00D1CDA3418967717F147D0EB21B42AAEE74AC35D0B92414B958531AADF463EC6305AE5ECAF79174002F26DDECC813BF32672E8529D95A4E730A7AB4A3E8F8A8AF979A665EAFD465FC64A0C5F8F3F9003489415899D59A543D8208C54A3166529B53922D4EC143B50F01423B177895EDEE22BB739F647ECF85F50BC25EF7B5A725DEE868626ED79D451140800E03B59B956F8210E556067407D13DC90FA9E8B872BFB8F")
dk, err := mlkem768.NewKeyFromExtendedEncoding(dkBytes)
if err != nil {
t.Fatal(err)
}
ct, _ := hex.DecodeString("B52C56B92A4B7CE9E4CB7C5B1B163167A8A1675B2FDEF84A5B67CA15DB694C9F11BD027C30AE22EC921A1D911599AF0585E48D20DA70DF9F39E32EF95D4C8F44BFEFDAA5DA64F1054631D04D6D3CFD0A540DD7BA3886E4B5F13E878788604C95C096EAB3919F427521419A946C26CC041475D7124CDC01D0373E5B09C7A70603CFDB4FB3405023F2264DC3F983C4FC02A2D1B268F2208A1F6E2A6209BFF12F6F465F0B069C3A7F84F606D8A94064003D6EC114C8E808D3053884C1D5A142FBF20112EB360FDA3F0F28B172AE50F5E7D83801FB3F0064B687187074BD7FE30EDDAA334CF8FC04FA8CED899CEADE4B4F28B68372BAF98FF482A415B731155B75CEB976BE0EA0285BA01A27F1857A8FB377A3AE0C23B2AA9A079BFABFF0D5B2F1CD9B718BEA03C42F343A39B4F142D01AD8ACBB50E38853CF9A50C8B44C3CF671A4A9043B26DDBB24959AD6715C08521855C79A23B9C3D6471749C40725BDD5C2776D43AED20204BAA141EFB3304917474B7F9F7A4B08B1A93DAED98C67495359D37D67F7438BEE5E43585634B26C6B3810D7CDCBC0F6EB877A6087E68ACB8480D3A8CF6900447E49B417F15A53B607A0E216B855970D37406870B4568722DA77A4084703816784E2F16BED18996532C5D8B7F5D214464E5F3F6E905867B0CE119E252A66713253544685D208E1723908A0CE97834652E08AE7BDC881A131B73C71E84D20D68FDEFF4F5D70CD1AF57B78E3491A9865942321800A203C05ED1FEEB5A28E584E19F6535E7F84E4A24F84A72DCAF5648B4A4235DD664464482F03176E888C28BFC6C1CB238CFFA35A321E71791D9EA8ED0878C61121BF8D2A4AB2C1A5E120BC40ABB1892D1715090A0EE48252CA297A99AA0E510CF26B1ADD06CA543E1C5D6BDCD3B9C585C8538045DB5C252EC3C8C3C954D9BE5907094A894E60EAB43538CFEE82E8FFC0791B0D0F43AC1627830A61D56DAD96C62958B0DE780B78BD47A604550DAB83FFF227C324049471F35248CFB849B25724FF704D5277AA352D550958BE3B237DFF473EC2ADBAEA48CA2658AEFCC77BBD4264AB374D70EAE5B964416CE8226A7E3255A0F8D7E2ADCA062BCD6D78D60D1B32E11405BE54B66EF0FDDD567702A3BCCFEDE3C584701269ED14809F06F8968356BB9267FE86E514252E88BB5C30A7ECB3D0E621021EE0FBF7871B09342BF84F55C97EAF86C48189C7FF4DF389F077E2806E5FA73B3E9458A16C7E275F4F602275580EB7B7135FB537FA0CD95D6EA58C108CD8943D70C1643111F4F01CA8A8276A902666ED81B78D168B006F16AAA3D8E4CE4F4D0FB0997E41AEFFB5B3DAA838732F357349447F387776C793C0479DE9E99498CC356FDB0075A703F23C55D47B550EC89B02ADE89329086A50843456FEDC3788AC8D97233C54560467EE1D0F024B18428F0D73B30E19F5C63B9ABF11415BEA4D0170130BAABD33C05E6524E5FB5581B22B0433342248266D0F1053B245CC2462DC44D34965102482A8ED9E4E964D5683E5D45D0C8269")
ss, err := kyberDecapsulate(dk, ct)
if err != nil {
t.Fatal(err)
}
exp, _ := hex.DecodeString("914CB67FE5C38E73BF74181C0AC50428DEDF7750A98058F7D536708774535B29")
if !bytes.Equal(ss, exp) {
t.Fatalf("got %x, want %x", ss, exp)
}
}
func TestKyberEncapsulate(t *testing.T) {
dk, err := mlkem768.GenerateKey()
if err != nil {
t.Fatal(err)
}
ct, ss, err := kyberEncapsulate(dk.EncapsulationKey())
if err != nil {
t.Fatal(err)
}
dkSS, err := kyberDecapsulate(dk, ct)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ss, dkSS) {
t.Fatalf("got %x, want %x", ss, dkSS)
}
}

View File

@ -18,6 +18,7 @@ import (
"net" "net"
"os" "os"
"reflect" "reflect"
"slices"
"sort" "sort"
"strings" "strings"
"testing" "testing"
@ -1805,3 +1806,135 @@ func testVerifyCertificates(t *testing.T, version uint16) {
}) })
} }
} }
func TestHandshakeKyber(t *testing.T) {
if x25519Kyber768Draft00.String() != "X25519Kyber768Draft00" {
t.Fatalf("unexpected CurveID string: %v", x25519Kyber768Draft00.String())
}
var tests = []struct {
name string
clientConfig func(*Config)
serverConfig func(*Config)
preparation func(*testing.T)
expectClientSupport bool
expectKyber bool
expectHRR bool
}{
{
name: "Default",
expectClientSupport: true,
expectKyber: true,
expectHRR: false,
},
{
name: "ClientCurvePreferences",
clientConfig: func(config *Config) {
config.CurvePreferences = []CurveID{X25519}
},
expectClientSupport: false,
},
{
name: "ServerCurvePreferencesX25519",
serverConfig: func(config *Config) {
config.CurvePreferences = []CurveID{X25519}
},
expectClientSupport: true,
expectKyber: false,
expectHRR: false,
},
{
name: "ServerCurvePreferencesHRR",
serverConfig: func(config *Config) {
config.CurvePreferences = []CurveID{CurveP256}
},
expectClientSupport: true,
expectKyber: false,
expectHRR: true,
},
{
name: "ClientTLSv12",
clientConfig: func(config *Config) {
config.MaxVersion = VersionTLS12
},
expectClientSupport: false,
},
{
name: "ServerTLSv12",
serverConfig: func(config *Config) {
config.MaxVersion = VersionTLS12
},
expectClientSupport: true,
expectKyber: false,
},
{
name: "GODEBUG",
preparation: func(t *testing.T) {
t.Setenv("GODEBUG", "tlskyber=0")
},
expectClientSupport: false,
},
}
baseConfig := testConfig.Clone()
baseConfig.CurvePreferences = nil
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.preparation != nil {
test.preparation(t)
} else {
t.Parallel()
}
serverConfig := baseConfig.Clone()
if test.serverConfig != nil {
test.serverConfig(serverConfig)
}
serverConfig.GetConfigForClient = func(hello *ClientHelloInfo) (*Config, error) {
if !test.expectClientSupport && slices.Contains(hello.SupportedCurves, x25519Kyber768Draft00) {
return nil, errors.New("client supports Kyber768Draft00")
} else if test.expectClientSupport && !slices.Contains(hello.SupportedCurves, x25519Kyber768Draft00) {
return nil, errors.New("client does not support Kyber768Draft00")
}
return nil, nil
}
clientConfig := baseConfig.Clone()
if test.clientConfig != nil {
test.clientConfig(clientConfig)
}
ss, cs, err := testHandshake(t, clientConfig, serverConfig)
if err != nil {
t.Fatal(err)
}
if test.expectKyber {
if ss.testingOnlyCurveID != x25519Kyber768Draft00 {
t.Errorf("got CurveID %v (server), expected %v", ss.testingOnlyCurveID, x25519Kyber768Draft00)
}
if cs.testingOnlyCurveID != x25519Kyber768Draft00 {
t.Errorf("got CurveID %v (client), expected %v", cs.testingOnlyCurveID, x25519Kyber768Draft00)
}
} else {
if ss.testingOnlyCurveID == x25519Kyber768Draft00 {
t.Errorf("got CurveID %v (server), expected not Kyber", ss.testingOnlyCurveID)
}
if cs.testingOnlyCurveID == x25519Kyber768Draft00 {
t.Errorf("got CurveID %v (client), expected not Kyber", cs.testingOnlyCurveID)
}
}
if test.expectHRR {
if !ss.testingOnlyDidHRR {
t.Error("server did not use HRR")
}
if !cs.testingOnlyDidHRR {
t.Error("client did not use HRR")
}
} else {
if ss.testingOnlyDidHRR {
t.Error("server used HRR")
}
if cs.testingOnlyDidHRR {
t.Error("client used HRR")
}
}
})
}
}

View File

@ -47,6 +47,7 @@ var All = []Info{
{Name: "randautoseed", Package: "math/rand"}, {Name: "randautoseed", Package: "math/rand"},
{Name: "tarinsecurepath", Package: "archive/tar"}, {Name: "tarinsecurepath", Package: "archive/tar"},
{Name: "tls10server", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "tls10server", Package: "crypto/tls", Changed: 22, Old: "1"},
{Name: "tlskyber", Package: "crypto/tls", Changed: 23, Old: "0", Opaque: true},
{Name: "tlsmaxrsasize", Package: "crypto/tls"}, {Name: "tlsmaxrsasize", Package: "crypto/tls"},
{Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"},
{Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"},