|  | // Copyright 2025 The BoringSSL Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | package runner | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "strconv" | 
|  | ) | 
|  |  | 
|  | var testCurves = []struct { | 
|  | name string | 
|  | id   CurveID | 
|  | }{ | 
|  | {"P-256", CurveP256}, | 
|  | {"P-384", CurveP384}, | 
|  | {"P-521", CurveP521}, | 
|  | {"X25519", CurveX25519}, | 
|  | {"Kyber", CurveX25519Kyber768}, | 
|  | {"MLKEM", CurveX25519MLKEM768}, | 
|  | } | 
|  |  | 
|  | const bogusCurve = 0x1234 | 
|  |  | 
|  | func isPqGroup(r CurveID) bool { | 
|  | return r == CurveX25519Kyber768 || r == CurveX25519MLKEM768 | 
|  | } | 
|  |  | 
|  | func isECDHGroup(r CurveID) bool { | 
|  | return r == CurveP256 || r == CurveP384 || r == CurveP521 | 
|  | } | 
|  |  | 
|  | func isX25519Group(r CurveID) bool { | 
|  | return r == CurveX25519 || r == CurveX25519Kyber768 || r == CurveX25519MLKEM768 | 
|  | } | 
|  |  | 
|  | func addCurveTests() { | 
|  | // A set of cipher suites that ensures some curve-using mode is used. | 
|  | // Without this, servers may fall back to RSA key exchange. | 
|  | ecdheCiphers := []uint16{ | 
|  | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | 
|  | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | 
|  | TLS_AES_256_GCM_SHA384, | 
|  | } | 
|  |  | 
|  | for _, curve := range testCurves { | 
|  | for _, ver := range tlsVersions { | 
|  | if isPqGroup(curve.id) && ver.version < VersionTLS13 { | 
|  | continue | 
|  | } | 
|  | for _, testType := range []testType{clientTest, serverTest} { | 
|  | suffix := fmt.Sprintf("%s-%s-%s", testType, curve.name, ver.name) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | }, | 
|  | flags: append( | 
|  | []string{"-expect-curve-id", strconv.Itoa(int(curve.id))}, | 
|  | flagInts("-curves", shimConfig.AllCurves)..., | 
|  | ), | 
|  | expectations: connectionExpectations{ | 
|  | curveID: curve.id, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | badKeyShareLocalError := "remote error: illegal parameter" | 
|  | if testType == clientTest && ver.version >= VersionTLS13 { | 
|  | // If the shim is a TLS 1.3 client and the runner sends a bad | 
|  | // key share, the runner never reads the client's cleartext | 
|  | // alert because the runner has already started encrypting by | 
|  | // the time the client sees it. | 
|  | badKeyShareLocalError = "local error: bad record MAC" | 
|  | } | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-Invalid-TruncateKeyShare-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | Bugs: ProtocolBugs{ | 
|  | TruncateKeyShare: true, | 
|  | }, | 
|  | }, | 
|  | flags:              flagInts("-curves", shimConfig.AllCurves), | 
|  | shouldFail:         true, | 
|  | expectedError:      ":BAD_ECPOINT:", | 
|  | expectedLocalError: badKeyShareLocalError, | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-Invalid-PadKeyShare-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | Bugs: ProtocolBugs{ | 
|  | PadKeyShare: true, | 
|  | }, | 
|  | }, | 
|  | flags:              flagInts("-curves", shimConfig.AllCurves), | 
|  | shouldFail:         true, | 
|  | expectedError:      ":BAD_ECPOINT:", | 
|  | expectedLocalError: badKeyShareLocalError, | 
|  | }) | 
|  |  | 
|  | if isECDHGroup(curve.id) { | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-Invalid-Compressed-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCompressedCoordinates: true, | 
|  | }, | 
|  | }, | 
|  | flags:              flagInts("-curves", shimConfig.AllCurves), | 
|  | shouldFail:         true, | 
|  | expectedError:      ":BAD_ECPOINT:", | 
|  | expectedLocalError: badKeyShareLocalError, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-Invalid-NotOnCurve-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | Bugs: ProtocolBugs{ | 
|  | ECDHPointNotOnCurve: true, | 
|  | }, | 
|  | }, | 
|  | flags:              flagInts("-curves", shimConfig.AllCurves), | 
|  | shouldFail:         true, | 
|  | expectedError:      ":BAD_ECPOINT:", | 
|  | expectedLocalError: badKeyShareLocalError, | 
|  | }) | 
|  | } | 
|  |  | 
|  | if isX25519Group(curve.id) { | 
|  | // Implementations should mask off the high order bit in X25519. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-SetX25519HighBit-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SetX25519HighBit: true, | 
|  | }, | 
|  | }, | 
|  | flags: flagInts("-curves", shimConfig.AllCurves), | 
|  | expectations: connectionExpectations{ | 
|  | curveID: curve.id, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Implementations should reject low order points. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-Invalid-LowOrderX25519Point-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | Bugs: ProtocolBugs{ | 
|  | LowOrderX25519Point: true, | 
|  | }, | 
|  | }, | 
|  | flags:              flagInts("-curves", shimConfig.AllCurves), | 
|  | shouldFail:         true, | 
|  | expectedError:      ":BAD_ECPOINT:", | 
|  | expectedLocalError: badKeyShareLocalError, | 
|  | }) | 
|  | } | 
|  |  | 
|  | if curve.id == CurveX25519MLKEM768 && testType == serverTest { | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: testType, | 
|  | name:     "CurveTest-Invalid-MLKEMEncapKeyNotReduced-" + suffix, | 
|  | config: Config{ | 
|  | MaxVersion:       ver.version, | 
|  | CipherSuites:     ecdheCiphers, | 
|  | CurvePreferences: []CurveID{curve.id}, | 
|  | Bugs: ProtocolBugs{ | 
|  | MLKEMEncapKeyNotReduced: true, | 
|  | }, | 
|  | }, | 
|  | flags:              flagInts("-curves", shimConfig.AllCurves), | 
|  | shouldFail:         true, | 
|  | expectedError:      ":BAD_ECPOINT:", | 
|  | expectedLocalError: badKeyShareLocalError, | 
|  | }) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // The server must be tolerant to bogus curves. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "UnknownCurve", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS12, | 
|  | CipherSuites:     []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | CurvePreferences: []CurveID{bogusCurve, CurveP256}, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // The server must be tolerant to bogus curves. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "UnknownCurve-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{bogusCurve, CurveP256}, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // The server must not consider ECDHE ciphers when there are no | 
|  | // supported curves. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "NoSupportedCurves", | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | NoSupportedCurves: true, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":NO_SHARED_CIPHER:", | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "NoSupportedCurves-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | NoSupportedCurves: true, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":NO_SHARED_GROUP:", | 
|  | }) | 
|  |  | 
|  | // The server must fall back to another cipher when there are no | 
|  | // supported curves. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "NoCommonCurves", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | CipherSuites: []uint16{ | 
|  | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | 
|  | TLS_RSA_WITH_AES_128_GCM_SHA256, | 
|  | }, | 
|  | CurvePreferences: []CurveID{21 /* P-224 */}, | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | cipher: TLS_RSA_WITH_AES_128_GCM_SHA256, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // The client must reject bogus curves and disabled curves. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "BadECDHECurve", | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCurve: bogusCurve, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":WRONG_CURVE:", | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "BadECDHECurve-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCurve: bogusCurve, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":WRONG_CURVE:", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "UnsupportedCurve", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS12, | 
|  | CipherSuites:     []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | CurvePreferences: []CurveID{CurveP256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | IgnorePeerCurvePreferences: true, | 
|  | }, | 
|  | }, | 
|  | flags:         []string{"-curves", strconv.Itoa(int(CurveP384))}, | 
|  | shouldFail:    true, | 
|  | expectedError: ":WRONG_CURVE:", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | // TODO(davidben): Add a TLS 1.3 version where | 
|  | // HelloRetryRequest requests an unsupported curve. | 
|  | name: "UnsupportedCurve-ServerHello-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{CurveP384}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCurve: CurveP256, | 
|  | }, | 
|  | }, | 
|  | flags:         []string{"-curves", strconv.Itoa(int(CurveP384))}, | 
|  | shouldFail:    true, | 
|  | expectedError: ":WRONG_CURVE:", | 
|  | }) | 
|  |  | 
|  | // The previous curve ID should be reported on TLS 1.2 resumption. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "CurveID-Resume-Client", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS12, | 
|  | CipherSuites:     []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | CurvePreferences: []CurveID{CurveX25519}, | 
|  | }, | 
|  | flags:         []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))}, | 
|  | resumeSession: true, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "CurveID-Resume-Server", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS12, | 
|  | CipherSuites:     []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | CurvePreferences: []CurveID{CurveX25519}, | 
|  | }, | 
|  | flags:         []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))}, | 
|  | resumeSession: true, | 
|  | }) | 
|  |  | 
|  | // TLS 1.3 allows resuming at a differet curve. If this happens, the new | 
|  | // one should be reported. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "CurveID-Resume-Client-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{CurveX25519}, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{CurveP256}, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)), | 
|  | "-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)), | 
|  | }, | 
|  | resumeSession: true, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "CurveID-Resume-Server-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{CurveX25519}, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{CurveP256}, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)), | 
|  | "-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)), | 
|  | }, | 
|  | resumeSession: true, | 
|  | }) | 
|  |  | 
|  | // Server-sent point formats are legal in TLS 1.2, but not in TLS 1.3. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "PointFormat-ServerHello-TLS12", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{pointFormatUncompressed}, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "PointFormat-EncryptedExtensions-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{pointFormatUncompressed}, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":ERROR_PARSING_EXTENSION:", | 
|  | }) | 
|  |  | 
|  | // Server-sent supported groups/curves are legal in TLS 1.3. They are | 
|  | // illegal in TLS 1.2, but some servers send them anyway, so we must | 
|  | // tolerate them. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "SupportedCurves-ServerHello-TLS12", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendServerSupportedCurves: true, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "SupportedCurves-EncryptedExtensions-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendServerSupportedCurves: true, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that we tolerate unknown point formats, as long as | 
|  | // pointFormatUncompressed is present. Limit ciphers to ECDHE ciphers to | 
|  | // check they are still functional. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "PointFormat-Client-Tolerance", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime}, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "PointFormat-Server-Tolerance", | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime}, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test TLS 1.2 does not require the point format extension to be | 
|  | // present. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "PointFormat-Client-Missing", | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{}, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "PointFormat-Server-Missing", | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{}, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // If the point format extension is present, uncompressed points must be | 
|  | // offered. BoringSSL requires this whether or not ECDHE is used. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "PointFormat-Client-MissingUncompressed", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{pointFormatCompressedPrime}, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":ERROR_PARSING_EXTENSION:", | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "PointFormat-Server-MissingUncompressed", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendSupportedPointFormats: []byte{pointFormatCompressedPrime}, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":ERROR_PARSING_EXTENSION:", | 
|  | }) | 
|  |  | 
|  | // Post-quantum groups require TLS 1.3. | 
|  | for _, curve := range testCurves { | 
|  | if !isPqGroup(curve.id) { | 
|  | continue | 
|  | } | 
|  |  | 
|  | // Post-quantum groups should not be offered by a TLS 1.2 client. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "TLS12ClientShouldNotOffer-" + curve.name, | 
|  | config: Config{ | 
|  | Bugs: ProtocolBugs{ | 
|  | FailIfPostQuantumOffered: true, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-max-version", strconv.Itoa(VersionTLS12), | 
|  | "-curves", strconv.Itoa(int(curve.id)), | 
|  | "-curves", strconv.Itoa(int(CurveX25519)), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Post-quantum groups should not be selected by a TLS 1.2 server. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS12ServerShouldNotSelect-" + curve.name, | 
|  | flags: []string{ | 
|  | "-max-version", strconv.Itoa(VersionTLS12), | 
|  | "-curves", strconv.Itoa(int(curve.id)), | 
|  | "-curves", strconv.Itoa(int(CurveX25519)), | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | curveID: CurveX25519, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // If a TLS 1.2 server selects a post-quantum group anyway, the client | 
|  | // should not accept it. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "ClientShouldNotAllowInTLS12-" + curve.name, | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCurve: curve.id, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-curves", strconv.Itoa(int(curve.id)), | 
|  | "-curves", strconv.Itoa(int(CurveX25519)), | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedError:      ":WRONG_CURVE:", | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | }) | 
|  | } | 
|  |  | 
|  | // ML-KEM and Kyber should not be offered by default as a client. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "PostQuantumNotEnabledByDefaultInClients", | 
|  | config: Config{ | 
|  | MinVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | FailIfPostQuantumOffered: true, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "NotJustMLKEMKeyShare", | 
|  | config: Config{ | 
|  | MinVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519}, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | "-curves", strconv.Itoa(int(CurveX25519)), | 
|  | "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // ... and the other way around | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "MLKEMKeyShareIncludedSecond", | 
|  | config: Config{ | 
|  | MinVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768}, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-curves", strconv.Itoa(int(CurveX25519)), | 
|  | "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | "-expect-curve-id", strconv.Itoa(int(CurveX25519)), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // ... and even if there's another curve in the middle because it's the | 
|  | // first classical and first post-quantum "curves" that get key shares | 
|  | // included. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "MLKEMKeyShareIncludedThird", | 
|  | config: Config{ | 
|  | MinVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768}, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-curves", strconv.Itoa(int(CurveX25519)), | 
|  | "-curves", strconv.Itoa(int(CurveP256)), | 
|  | "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | "-expect-curve-id", strconv.Itoa(int(CurveX25519)), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // If ML-KEM is the only configured curve, the key share is sent. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "JustConfiguringMLKEMWorks", | 
|  | config: Config{ | 
|  | MinVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectedKeyShares: []CurveID{CurveX25519MLKEM768}, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // If both ML-KEM and Kyber are configured, only the preferred one's | 
|  | // key share should be sent. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: "BothMLKEMAndKyber", | 
|  | config: Config{ | 
|  | MinVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectedKeyShares: []CurveID{CurveX25519MLKEM768}, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | "-curves", strconv.Itoa(int(CurveX25519Kyber768)), | 
|  | "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // As a server, ML-KEM is not yet supported by default. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "PostQuantumNotEnabledByDefaultForAServer", | 
|  | config: Config{ | 
|  | MinVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768, CurveX25519}, | 
|  | DefaultCurves:    []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768}, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-server-preference", | 
|  | "-expect-curve-id", strconv.Itoa(int(CurveX25519)), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // In TLS 1.2, the curve list is also used to signal ECDSA curves. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "CheckECDSACurve-TLS12", | 
|  | config: Config{ | 
|  | MinVersion:       VersionTLS12, | 
|  | MaxVersion:       VersionTLS12, | 
|  | CurvePreferences: []CurveID{CurveP384}, | 
|  | }, | 
|  | shimCertificate: &ecdsaP256Certificate, | 
|  | shouldFail:      true, | 
|  | expectedError:   ":WRONG_CURVE:", | 
|  | }) | 
|  |  | 
|  | // If the ECDSA certificate is ineligible due to a curve mismatch, the | 
|  | // server may still consider a PSK cipher suite. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "CheckECDSACurve-PSK-TLS12", | 
|  | config: Config{ | 
|  | MinVersion: VersionTLS12, | 
|  | MaxVersion: VersionTLS12, | 
|  | CipherSuites: []uint16{ | 
|  | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | 
|  | TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, | 
|  | }, | 
|  | CurvePreferences:     []CurveID{CurveP384}, | 
|  | PreSharedKey:         []byte("12345"), | 
|  | PreSharedKeyIdentity: "luggage combo", | 
|  | }, | 
|  | shimCertificate: &ecdsaP256Certificate, | 
|  | expectations: connectionExpectations{ | 
|  | cipher: TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-psk", "12345", | 
|  | "-psk-identity", "luggage combo", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // In TLS 1.3, the curve list only controls ECDH. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "CheckECDSACurve-NotApplicable-TLS13", | 
|  | config: Config{ | 
|  | MinVersion:       VersionTLS13, | 
|  | MaxVersion:       VersionTLS13, | 
|  | CurvePreferences: []CurveID{CurveP384}, | 
|  | }, | 
|  | shimCertificate: &ecdsaP256Certificate, | 
|  | }) | 
|  | } |