// 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 (
	"errors"
	"strconv"
)

func addPAKETests() {
	spakeCredential := Credential{
		Type:         CredentialTypeSPAKE2PlusV1,
		PAKEContext:  []byte("context"),
		PAKEClientID: []byte("client"),
		PAKEServerID: []byte("server"),
		PAKEPassword: []byte("password"),
	}

	spakeWrongClientID := spakeCredential
	spakeWrongClientID.PAKEClientID = []byte("wrong")

	spakeWrongServerID := spakeCredential
	spakeWrongServerID.PAKEServerID = []byte("wrong")

	spakeWrongPassword := spakeCredential
	spakeWrongPassword.PAKEPassword = []byte("wrong")

	spakeWrongRole := spakeCredential
	spakeWrongRole.WrongPAKERole = true

	spakeWrongCodepoint := spakeCredential
	spakeWrongCodepoint.OverridePAKECodepoint = 1234

	testCases = append(testCases, testCase{
		name:     "PAKE-No-Server-Support",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
		},
		shouldFail:    true,
		expectedError: ":MISSING_KEY_SHARE:",
	})
	testCases = append(testCases, testCase{
		name:     "PAKE-Server",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				// We do not currently support resumption with PAKE, so PAKE
				// servers should not issue session tickets.
				ExpectNoNewSessionTicket: true,
			},
		},
		shimCredentials: []*Credential{&spakeCredential},
	})
	testCases = append(testCases, testCase{
		// Send a ClientHello with the wrong PAKE client ID.
		name:     "PAKE-Server-WrongClientID",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeWrongClientID,
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":PEER_PAKE_MISMATCH:",
		expectedLocalError: "remote error: handshake failure",
	})
	testCases = append(testCases, testCase{
		// Send a ClientHello with the wrong PAKE server ID.
		name:     "PAKE-Server-WrongServerID",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeWrongServerID,
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":PEER_PAKE_MISMATCH:",
		expectedLocalError: "remote error: handshake failure",
	})
	testCases = append(testCases, testCase{
		// Send a ClientHello with the wrong PAKE codepoint.
		name:     "PAKE-Server-WrongCodepoint",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeWrongCodepoint,
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":PEER_PAKE_MISMATCH:",
		expectedLocalError: "remote error: handshake failure",
	})
	testCases = append(testCases, testCase{
		// A server configured with a mix of PAKE and non-PAKE
		// credentials will select the first that matches what the
		// client offered. In doing so, it should skip unsupported
		// PAKE algorithms.
		name:     "PAKE-Server-MultiplePAKEs",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				OfferExtraPAKEs: []uint16{1, 2, 3, 4, 5},
			},
		},
		shimCredentials: []*Credential{&spakeWrongClientID, &spakeWrongServerID, &spakeWrongRole, &spakeCredential, &rsaCertificate},
		flags:           []string{"-expect-selected-credential", "3"},
	})
	testCases = append(testCases, testCase{
		// A server configured with a certificate credential before a
		// PAKE credential will consider the certificate credential first.
		name:     "PAKE-Server-CertificateBeforePAKE",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Bugs: ProtocolBugs{
				// Pretend to offer a matching PAKE share, but expect the
				// shim to select the credential first and negotiate a
				// normal handshake.
				OfferExtraPAKEClientID: spakeCredential.PAKEClientID,
				OfferExtraPAKEServerID: spakeCredential.PAKEServerID,
				OfferExtraPAKEs:        []uint16{spakeID},
			},
		},
		shimCredentials: []*Credential{&rsaCertificate, &spakeCredential},
		flags:           []string{"-expect-selected-credential", "0"},
	})
	testCases = append(testCases, testCase{
		// A server configured with just a PAKE credential should reject normal
		// clients.
		name:     "PAKE-Server-NormalClient",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":PEER_PAKE_MISMATCH:",
		expectedLocalError: "remote error: handshake failure",
	})
	testCases = append(testCases, testCase{
		// ... and TLS 1.2 clients.
		name:     "PAKE-Server-NormalTLS12Client",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS12,
			MaxVersion: VersionTLS12,
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":NO_SHARED_CIPHER:",
		expectedLocalError: "remote error: handshake failure",
	})
	testCases = append(testCases, testCase{
		// ... but you can configure a server with both PAKE and certificate-based
		// SSL_CREDENTIALs and that works.
		name:     "PAKE-ServerWithCertsToo-NormalClient",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
		},
		shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
		flags:           []string{"-expect-selected-credential", "1"},
	})
	testCases = append(testCases, testCase{
		// ... and for older clients.
		name:     "PAKE-ServerWithCertsToo-NormalTLS12Client",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS12,
			MaxVersion: VersionTLS12,
		},
		shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
		flags:           []string{"-expect-selected-credential", "1"},
	})
	testCases = append(testCases, testCase{
		name:     "PAKE-Client",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				CheckClientHello: func(c *clientHelloMsg) error {
					// PAKE connections don't use the key_share / supported_groups mechanism.
					if c.hasKeyShares {
						return errors.New("unexpected key_share extension")
					}
					if len(c.supportedCurves) != 0 {
						return errors.New("unexpected supported_groups extension")
					}
					// PAKE connections don't use signature algorithms.
					if len(c.signatureAlgorithms) != 0 {
						return errors.New("unexpected signature_algorithms extension")
					}
					// We don't support resumption with PAKEs.
					if len(c.pskKEModes) != 0 {
						return errors.New("unexpected psk_key_exchange_modes extension")
					}
					return nil
				},
			},
		},
		shimCredentials: []*Credential{&spakeCredential},
	})
	testCases = append(testCases, testCase{
		// Although there is no reason to request new key shares, the PAKE
		// client should handle cookie requests.
		name:     "PAKE-Client-HRRCookie",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				SendHelloRetryRequestCookie: []byte("cookie"),
			},
		},
		shimCredentials: []*Credential{&spakeCredential},
	})
	testCases = append(testCases, testCase{
		// A PAKE client will not offer key shares, even if explicitly configured,
		// and the client should reject a HelloRetryRequest requesting a different
		// key share.
		name:     "PAKE-Client-HRRKeyShare",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				ExpectedKeyShares:          []CurveID{},
				SendHelloRetryRequestCurve: CurveX25519,
			},
		},
		flags: []string{
			"-key-shares", strconv.Itoa(int(CurveP256)),
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":UNEXPECTED_EXTENSION:",
		expectedLocalError: "remote error: unsupported extension",
	})
	testCases = append(testCases, testCase{
		// A server cannot reply with an HRR asking for a PAKE if the client didn't
		// offer a PAKE in the ClientHello.
		name:     "PAKE-NormalClient-PAKEInHRR",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				AlwaysSendHelloRetryRequest: true,
				SendPAKEInHelloRetryRequest: true,
			},
		},
		shouldFail:    true,
		expectedError: ":UNEXPECTED_EXTENSION:",
	})
	testCases = append(testCases, testCase{
		// A PAKE client should not accept an empty ServerHello.
		name:     "PAKE-Client-EmptyServerHello",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Bugs: ProtocolBugs{
				// Trigger an empty ServerHello by making a normal server skip
				// the key_share extension.
				MissingKeyShare: true,
			},
		},
		shimCredentials: []*Credential{&spakeCredential},
		shouldFail:      true,
		expectedError:   ":MISSING_EXTENSION:",
	})
	testCases = append(testCases, testCase{
		// A PAKE client should not accept a key_share ServerHello.
		name:     "PAKE-Client-KeyShareServerHello",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Bugs: ProtocolBugs{
				// Trigger a key_share ServerHello by making a normal server
				// skip the HelloRetryRequest it would otherwise send in
				// response to the shim's key_share-less ClientHello.
				SkipHelloRetryRequest: true,
				// Ignore the client's lack of supported_groups.
				IgnorePeerCurvePreferences: true,
			},
		},
		shimCredentials: []*Credential{&spakeCredential},
		shouldFail:      true,
		expectedError:   ":UNEXPECTED_EXTENSION:",
	})
	testCases = append(testCases, testCase{
		// A PAKE client should not accept a TLS 1.2 ServerHello.
		name:     "PAKE-Client-TLS12ServerHello",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS12,
			MaxVersion: VersionTLS12,
		},
		shimCredentials: []*Credential{&spakeCredential},
		shouldFail:      true,
		expectedError:   ":UNSUPPORTED_PROTOCOL:",
	})
	testCases = append(testCases, testCase{
		// A server cannot send the PAKE extension to a non-PAKE client.
		name:     "PAKE-NormalClient-UnsolicitedPAKEInServerHello",
		testType: clientTest,
		config: Config{
			Bugs: ProtocolBugs{
				UnsolicitedPAKE: spakeID,
			},
		},
		shouldFail:    true,
		expectedError: ":UNEXPECTED_EXTENSION:",
	})
	testCases = append(testCases, testCase{
		// A server cannot reply with a PAKE that the client did not offer.
		name:     "PAKE-Client-WrongPAKEInServerHello",
		testType: clientTest,
		config: Config{
			Bugs: ProtocolBugs{
				UnsolicitedPAKE: 1234,
			},
		},
		shimCredentials: []*Credential{&spakeCredential},
		shouldFail:      true,
		expectedError:   ":DECODE_ERROR:",
	})
	testCases = append(testCases, testCase{
		name:     "PAKE-Extension-Duplicate",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Bugs: ProtocolBugs{
				OfferExtraPAKEClientID: []byte("client"),
				OfferExtraPAKEServerID: []byte("server"),
				OfferExtraPAKEs:        []uint16{1234, 1234},
			},
		},
		shouldFail:    true,
		expectedError: ":ERROR_PARSING_EXTENSION:",
	})
	testCases = append(testCases, testCase{
		// If the client sees a server with a wrong password, it should
		// reject the confirmV value in the ServerHello.
		name:     "PAKE-Client-WrongPassword",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeWrongPassword,
		},
		shimCredentials: []*Credential{&spakeCredential},
		shouldFail:      true,
		expectedError:   ":DECODE_ERROR:",
	})
	testCases = append(testCases, testCase{
		name:     "PAKE-Client-Truncate",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				TruncatePAKEMessage: true,
			},
		},
		shimCredentials: []*Credential{&spakeCredential},
		shouldFail:      true,
		expectedError:   ":DECODE_ERROR:",
	})
	testCases = append(testCases, testCase{
		name:     "PAKE-Server-Truncate",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				TruncatePAKEMessage: true,
			},
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":DECODE_ERROR:",
		expectedLocalError: "remote error: illegal parameter",
	})
	testCases = append(testCases, testCase{
		// Servers may not send CertificateRequest in a PAKE handshake.
		name:     "PAKE-Client-UnexpectedCertificateRequest",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			ClientAuth: RequireAnyClientCert,
			Bugs: ProtocolBugs{
				AlwaysSendCertificateRequest: true,
			},
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":UNEXPECTED_MESSAGE:",
		expectedLocalError: "remote error: unexpected message",
	})
	testCases = append(testCases, testCase{
		// Servers may not send Certificate in a PAKE handshake.
		name:     "PAKE-Client-UnexpectedCertificate",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				AlwaysSendCertificate:    true,
				UseCertificateCredential: &rsaCertificate,
				// Ignore the client's lack of signature_algorithms.
				IgnorePeerSignatureAlgorithmPreferences: true,
			},
		},
		shimCredentials:    []*Credential{&spakeCredential},
		shouldFail:         true,
		expectedError:      ":UNEXPECTED_MESSAGE:",
		expectedLocalError: "remote error: unexpected message",
	})
	testCases = append(testCases, testCase{
		// If a server is configured to request client certificates, it should
		// still not do so when negotiating a PAKE.
		name:     "PAKE-Server-DoNotRequestClientCertificate",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
		},
		shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
		flags:           []string{"-require-any-client-certificate"},
	})
	testCases = append(testCases, testCase{
		// Clients should ignore server PAKE credentials.
		name:     "PAKE-Client-WrongRole",
		testType: clientTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
		},
		shimCredentials: []*Credential{&spakeWrongRole},
		shouldFail:      true,
		// The shim will send a non-PAKE ClientHello.
		expectedLocalError: "tls: client not configured with PAKE",
	})
	testCases = append(testCases, testCase{
		// Servers should ignore client PAKE credentials.
		name:     "PAKE-Server-WrongRole",
		testType: serverTest,
		config: Config{
			MinVersion: VersionTLS13,
			Credential: &spakeCredential,
		},
		shimCredentials: []*Credential{&spakeWrongRole},
		shouldFail:      true,
		// The shim will fail the handshake because it has no usable credentials
		// available.
		expectedError:      ":UNKNOWN_CERTIFICATE_TYPE:",
		expectedLocalError: "remote error: handshake failure",
	})
	testCases = append(testCases, testCase{
		// On the client, we only support a single PAKE credential.
		name:            "PAKE-Client-MultiplePAKEs",
		testType:        clientTest,
		shimCredentials: []*Credential{&spakeCredential, &spakeWrongPassword},
		shouldFail:      true,
		expectedError:   ":UNSUPPORTED_CREDENTIAL_LIST:",
	})
	testCases = append(testCases, testCase{
		// On the client, we only support a single PAKE credential.
		name:            "PAKE-Client-PAKEAndCertificate",
		testType:        clientTest,
		shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
		shouldFail:      true,
		expectedError:   ":UNSUPPORTED_CREDENTIAL_LIST:",
	})
	testCases = append(testCases, testCase{
		// We currently do not support resumption with PAKE. Even if configured
		// with a session, the client should not offer the session with PAKEs.
		name:     "PAKE-Client-NoResume",
		testType: clientTest,
		// Make two connections. For the first connection, just establish a
		// session without PAKE, to pick up a session.
		config: Config{
			Credential: &rsaCertificate,
		},
		// For the second connection, use SPAKE.
		resumeSession: true,
		resumeConfig: &Config{
			Credential: &spakeCredential,
			Bugs: ProtocolBugs{
				// Check that the ClientHello does not offer a session, even
				// though one was configured.
				ExpectNoTLS13PSK: true,
				// Respond with an unsolicited PSK extension in ServerHello, to
				// check that the client rejects it.
				AlwaysSelectPSKIdentity: ptrTo(uint16(0)),
			},
		},
		resumeShimCredentials: []*Credential{&spakeCredential},
		shouldFail:            true,
		expectedError:         ":UNEXPECTED_EXTENSION:",
	})
}
