Go 1.23 corrected the semantics of contention reports for runtime-internal locks,
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 adds a configurable limit to control the maximum acceptable RSA key size
"SendEmptyRecords*": "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?)",
+ "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",
"SendUserCanceledAlerts*": "TODO may be a real bug?",
"GREASE-Server-TLS13": "TODO ???",
"EchoTLS13CompatibilitySessionID": "TODO reject compat session ID",
"*ECH-Server*": "no ECH server support",
"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",
"NoNullCompression-TLS12": "TODO: first pass, this should be fixed",
"KeyUpdate-RequestACK": "TODO: first pass, this should be fixed",
"CheckClientCertificateTypes": "TODO: first pass, this should be fixed",
"CheckECDSACurve-TLS12": "TODO: first pass, this should be fixed"
}
-}
\ No newline at end of file
+}
"os/exec"
"path/filepath"
"runtime"
+ "strconv"
+ "strings"
"testing"
)
resumeCount = flag.Int("resume-count", 0, "")
+ curves = flagStringSlice("curves", "")
+ expectedCurve = flag.String("expect-curve-id", "", "")
+
shimID = flag.Uint64("shim-id", 0, "")
_ = flag.Bool("ipv6", false, "")
// -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() {
if *isHandshakerSupported {
fmt.Println("No")
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++ {
conn, err := net.Dial("tcp", net.JoinHostPort("localhost", *port))
if err != nil {
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)
+ }
+ }
}
}
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()
if err != nil {
t.Fatalf("failed to download boringssl: %s", err)
"-shim-extra-flags=-bogo-mode",
"-allow-unimplemented",
"-loose-errors", // TODO(roland): this should be removed eventually
+ "-pipe",
+ "-v",
}
if *bogoFilter != "" {
args = append(args, fmt.Sprintf("-test=%s", *bogoFilter))
t.Fatal(err)
}
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")
err = cmd.Run()
if err != nil {
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")
+ }
}
t.Run(fmt.Sprintf("curve=%d", curveid), func(t *testing.T) {
clientConfig := testConfig.Clone()
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 {
t.Fatalf("got error: %v, expected success", err)
}
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.
//
-// In TLS 1.3, this type is called NamedGroup, but at this time this library
-// only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7.
+// In TLS 1.2, this registry used to support only elliptic curves. In TLS 1.3,
+// 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
const (
CurveP384 CurveID = 24
CurveP521 CurveID = 25
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.
// testingOnlyDidHRR is true if a HelloRetryRequest was sent/received.
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
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
// RFC 8446, Section 4.2.3.
// an ECDHE handshake, in preference order. If empty, the default will
// 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.
+ //
+ // 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
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
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() {
return fipsCurvePreferences(c)
}
if c == nil || len(c.CurvePreferences) == 0 {
+ if version < VersionTLS13 || tlskyber.Value() == "0" {
+ return defaultCurvePreferencesWithoutKyber
+ }
return defaultCurvePreferences
}
return c.CurvePreferences
}
-func (c *Config) supportsCurve(curve CurveID) bool {
- for _, cc := range c.curvePreferences() {
+func (c *Config) supportsCurve(version uint16, curve CurveID) bool {
+ for _, cc := range c.curvePreferences(version) {
if cc == curve {
return true
}
}
// 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"))
}
}
var curveOk bool
for _, c := range chi.SupportedCurves {
- if c == curve && config.supportsCurve(c) {
+ if c == curve && config.supportsCurve(vers, c) {
curveOk = true
break
}
-// 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
_ = x[CurveP384-24]
_ = x[CurveP521-25]
_ = x[X25519-29]
+ _ = x[x25519Kyber768Draft00-25497]
}
const (
_CurveID_name_0 = "CurveP256CurveP384CurveP521"
_CurveID_name_1 = "X25519"
+ _CurveID_name_2 = "X25519Kyber768Draft00"
)
var (
return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]]
case i == 29:
return _CurveID_name_1
+ case i == 25497:
+ return _CurveID_name_2
default:
return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")"
}
didResume bool // whether this connection was a session resumption
didHRR bool // whether a HelloRetryRequest was sent/received
cipherSuite uint16
+ curveID CurveID
ocspResponse []byte // stapled OCSP response
scts [][]byte // signed certificate timestamps from server
peerCertificates []*x509.Certificate
state.NegotiatedProtocol = c.clientProtocol
state.DidResume = c.didResume
state.testingOnlyDidHRR = c.didHRR
+ // c.curveID is not set on TLS 1.0–1.2 resumptions. Fix that before exposing it.
+ state.testingOnlyCurveID = c.curveID
state.NegotiatedProtocolIsMutual = true
state.ServerName = c.serverName
state.CipherSuite = c.cipherSuite
"bytes"
"context"
"crypto"
- "crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
+ "crypto/internal/mlkem768"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
"errors"
"fmt"
"hash"
+ "internal/byteorder"
"internal/godebug"
"io"
"net"
var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme
-func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
+func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, error) {
config := c.config
if len(config.ServerName) == 0 && !config.InsecureSkipVerify {
return nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")
if len(supportedVersions) == 0 {
return nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion")
}
-
- 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
- }
+ maxVersion := config.maxSupportedVersion(roleClient)
hello := &clientHelloMsg{
- vers: clientHelloVersion,
+ vers: maxVersion,
compressionMethods: []uint8{compressionNone},
random: make([]byte, 32),
extendedMasterSecret: true,
ocspStapling: true,
scts: true,
serverName: hostnameInSNI(config.ServerName),
- supportedCurves: config.curvePreferences(),
+ supportedCurves: config.curvePreferences(maxVersion),
supportedPoints: []uint8{pointFormatUncompressed},
secureRenegotiationSupported: true,
alpnProtocols: config.NextProtos,
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 {
hello.secureRenegotiation = c.clientFinished[:]
}
}
// Don't advertise TLS 1.2-only cipher suites unless
// we're attempting TLS 1.2.
- if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
+ if maxVersion < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
continue
}
hello.cipherSuites = append(hello.cipherSuites, suiteId)
}
}
- if hello.vers >= VersionTLS12 {
+ if maxVersion >= VersionTLS12 {
hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
}
if testingOnlyForceClientHelloSignatureAlgorithms != nil {
hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms
}
- var key *ecdh.PrivateKey
+ var keyShareKeys *keySharePrivateKeys
if hello.supportedVersions[0] == VersionTLS13 {
// Reset the list of ciphers when the client only supports TLS 1.3.
if len(hello.supportedVersions) == 1 {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)
}
- curveID := config.curvePreferences()[0]
- if _, ok := curveForCurveID(curveID); !ok {
- return nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
- }
- key, err = generateECDHEKey(config.rand(), curveID)
- if err != nil {
- return nil, nil, err
+ curveID := config.curvePreferences(maxVersion)[0]
+ keyShareKeys = &keySharePrivateKeys{curveID: curveID}
+ 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()}}
}
- hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
}
if c.quic != nil {
hello.quicTransportParameters = p
}
- return hello, key, nil
+ return hello, keyShareKeys, nil
}
func (c *Conn) clientHandshake(ctx context.Context) (err error) {
// need to be reset.
c.didResume = false
- hello, ecdheKey, err := c.makeClientHello()
+ hello, keyShareKeys, err := c.makeClientHello()
if err != nil {
return err
}
if c.vers == VersionTLS13 {
hs := &clientHandshakeStateTLS13{
- c: c,
- ctx: ctx,
- serverHello: serverHello,
- hello: hello,
- ecdheKey: ecdheKey,
- session: session,
- earlySecret: earlySecret,
- binderKey: binderKey,
+ c: c,
+ ctx: ctx,
+ serverHello: serverHello,
+ hello: hello,
+ keyShareKeys: keyShareKeys,
+ session: session,
+ earlySecret: earlySecret,
+ binderKey: binderKey,
}
-
- // In TLS 1.3, session tickets are delivered after the handshake.
return hs.handshake()
}
hello: hello,
session: session,
}
-
- if err := hs.handshake(); err != nil {
- return err
- }
-
- return nil
+ return hs.handshake()
}
func (c *Conn) loadSession(hello *clientHelloMsg) (
c.sendAlert(alertUnexpectedMessage)
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)
if err != nil {
"bytes"
"context"
"crypto"
- "crypto/ecdh"
"crypto/hmac"
+ "crypto/internal/mlkem768"
"crypto/rsa"
"errors"
"hash"
+ "slices"
"time"
)
type clientHandshakeStateTLS13 struct {
- c *Conn
- ctx context.Context
- serverHello *serverHelloMsg
- hello *clientHelloMsg
- ecdheKey *ecdh.PrivateKey
+ c *Conn
+ ctx context.Context
+ serverHello *serverHelloMsg
+ hello *clientHelloMsg
+ keyShareKeys *keySharePrivateKeys
session *SessionState
earlySecret []byte
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.
func (hs *clientHandshakeStateTLS13) handshake() error {
c := hs.c
}
// 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)
}
// a group we advertised but did not send a key share for, and send a key
// share for it this time.
if curveID := hs.serverHello.selectedGroup; curveID != 0 {
- curveOK := false
- for _, id := range hs.hello.supportedCurves {
- if id == curveID {
- curveOK = true
- break
- }
- }
- if !curveOK {
+ if !slices.Contains(hs.hello.supportedCurves, curveID) {
c.sendAlert(alertIllegalParameter)
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)
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 {
c.sendAlert(alertInternalError)
return errors.New("tls: CurvePreferences includes unsupported curve")
c.sendAlert(alertInternalError)
return err
}
- hs.ecdheKey = key
+ hs.keyShareKeys = &keySharePrivateKeys{curveID: curveID, ecdhe: key}
hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
}
c.sendAlert(alertIllegalParameter)
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)
return errors.New("tls: server selected unsupported group")
}
func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
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 {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid server key share")
}
- sharedKey, err := hs.ecdheKey.ECDH(peerKey)
+ sharedKey, err := hs.keyShareKeys.ecdhe.ECDH(peerKey)
if err != nil {
c.sendAlert(alertIllegalParameter)
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
if !hs.usingPSK {
"errors"
"fmt"
"hash"
+ "internal/byteorder"
"io"
"time"
)
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 {
// Although omitting the ec_point_formats extension is permitted, some
// supportsECDHE returns whether ECDHE key exchanges can be used with this
// 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
for _, curve := range supportedCurves {
- if c.supportsCurve(curve) {
+ if c.supportsCurve(version, curve) {
supportsCurve = true
break
}
return err
}
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 {
return err
}
}
}
s.Close()
+ t.Helper()
if len(expectedSubStr) == 0 {
if err != nil && err != io.EOF {
t.Errorf("Got error: %s; expected to succeed", err)
"context"
"crypto"
"crypto/hmac"
+ "crypto/internal/mlkem768"
"crypto/rsa"
"errors"
"hash"
hs.hello.cipherSuite = hs.suite.id
hs.transcript = hs.suite.hash.New()
- // Pick the ECDHE group in server preference order, but give priority to
- // groups with a key share, to avoid a HelloRetryRequest round-trip.
+ // Pick the key exchange method in server preference order, but give
+ // priority to key shares, to avoid a HelloRetryRequest round-trip.
var selectedGroup CurveID
var clientKeyShare *keyShare
- preferredGroups := c.config.curvePreferences()
+ preferredGroups := c.config.curvePreferences(c.vers)
for _, preferredGroup := range preferredGroups {
ki := slices.IndexFunc(hs.clientHello.keyShares, func(ks keyShare) bool {
return ks.group == preferredGroup
return errors.New("tls: no ECDHE curve supported by both client and server")
}
if clientKeyShare == nil {
- if err := hs.doHelloRetryRequest(selectedGroup); err != nil {
+ ks, err := hs.doHelloRetryRequest(selectedGroup)
+ if err != nil {
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)
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 {
c.sendAlert(alertInternalError)
return err
}
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 {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid client key share")
c.sendAlert(alertIllegalParameter)
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)
if err != nil {
return hs.c.writeChangeCipherRecord()
}
-func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) error {
+func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) (*keyShare, error) {
c := hs.c
// The first ClientHello gets double-hashed into the transcript upon a
// HelloRetryRequest. See RFC 8446, Section 4.4.1.
if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
- return err
+ return nil, err
}
chHash := hs.transcript.Sum(nil)
hs.transcript.Reset()
}
if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil {
- return err
+ return nil, err
}
if err := hs.sendDummyChangeCipherSpec(); err != nil {
- return err
+ return nil, err
}
// clientHelloMsg is not included in the transcript.
msg, err := c.readHandshake(nil)
if err != nil {
- return err
+ return nil, err
}
clientHello, ok := msg.(*clientHelloMsg)
if !ok {
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)
- 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 {
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) {
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
hs.clientHello = clientHello
- return nil
+ return ks, nil
}
// illegalClientHelloChange reports whether the two ClientHello messages are
Certificates: make([]Certificate, 2),
InsecureSkipVerify: true,
CipherSuites: allCipherSuites(),
+ CurvePreferences: []CurveID{X25519, CurveP256, CurveP384, CurveP521},
MinVersion: VersionTLS10,
MaxVersion: VersionTLS13,
}
"io"
)
-// a keyAgreement implements the client and server side of a TLS key agreement
-// protocol by generating and processing key exchange messages.
+// A keyAgreement implements the client and server side of a TLS 1.0–1.2 key
+// agreement protocol by generating and processing key exchange messages.
type keyAgreement interface {
// On the server side, the first two methods are called in order.
}
// 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
// do pre-hashing, it returns the concatenation of the slices.
func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint16, slices ...[]byte) []byte {
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
var curveID CurveID
for _, c := range clientHello.supportedCurves {
- if config.supportsCurve(c) {
+ if config.supportsCurve(ka.version, c) {
curveID = c
break
}
import (
"crypto/ecdh"
"crypto/hmac"
+ "crypto/internal/mlkem768"
"errors"
"fmt"
"hash"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/hkdf"
+ "golang.org/x/crypto/sha3"
)
// This file contains the functions necessary to compute the TLS 1.3 key
}
}
+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
// according to RFC 8446, Section 4.2.8.2.
func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) {
import (
"bytes"
+ "crypto/internal/mlkem768"
"encoding/hex"
"hash"
"strings"
})
}
}
+
+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)
+ }
+}
"net"
"os"
"reflect"
+ "slices"
"sort"
"strings"
"testing"
})
}
}
+
+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")
+ }
+ }
+ })
+ }
+}
{Name: "randautoseed", Package: "math/rand"},
{Name: "tarinsecurepath", Package: "archive/tar"},
{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: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"},
{Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"},