// 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

func addCompliancePolicyTests() {
	for _, protocol := range []protocol{tls, quic} {
		for _, suite := range testCipherSuites {
			var isFIPSCipherSuite bool
			switch suite.id {
			case TLS_AES_128_GCM_SHA256,
				TLS_AES_256_GCM_SHA384,
				TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
				isFIPSCipherSuite = true
			}

			var isWPACipherSuite bool
			var isCNSA1CipherSuite bool
			switch suite.id {
			case TLS_AES_256_GCM_SHA384,
				TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
				isWPACipherSuite = true
				isCNSA1CipherSuite = true
			}

			var isCNSA2CipherSuite bool
			switch suite.id {
			case TLS_AES_256_GCM_SHA384:
				isCNSA2CipherSuite = true
			}

			var cert Credential
			if hasComponent(suite.name, "ECDSA") {
				cert = ecdsaP384Certificate
			} else {
				cert = rsaCertificate
			}

			maxVersion := uint16(VersionTLS13)
			if !isTLS13Suite(suite.name) {
				if protocol == quic {
					continue
				}
				maxVersion = VersionTLS12
			}

			policies := []struct {
				flag          string
				cipherSuiteOk bool
			}{
				{"-fips-202205", isFIPSCipherSuite},
				{"-wpa-202304", isWPACipherSuite},
				{"-cnsa1-202603", isCNSA1CipherSuite},
				{"-cnsa2-202603", isCNSA2CipherSuite},
			}

			for _, policy := range policies {
				shouldFail := !policy.cipherSuiteOk
				// The CNSA2 policy requires TLS 1.3.
				if policy.flag == "-cnsa2-202603" && maxVersion == VersionTLS12 {
					shouldFail = true
				}

				testCases = append(testCases, testCase{
					testType: serverTest,
					protocol: protocol,
					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + suite.name,
					config: Config{
						MinVersion:   VersionTLS12,
						MaxVersion:   maxVersion,
						CipherSuites: []uint16{suite.id},
					},
					shimCertificate: &cert,
					flags: []string{
						policy.flag,
					},
					shouldFail: shouldFail,
				})

				testCases = append(testCases, testCase{
					testType: clientTest,
					protocol: protocol,
					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + suite.name,
					config: Config{
						MinVersion:   VersionTLS12,
						MaxVersion:   maxVersion,
						CipherSuites: []uint16{suite.id},
						Credential:   &cert,
					},
					flags: []string{
						policy.flag,
					},
					shouldFail: shouldFail,
				})
			}
		}

		// Check that a TLS 1.3 client won't accept ChaCha20 even if the server
		// picks it without it being in the client's cipher list.
		testCases = append(testCases, testCase{
			testType: clientTest,
			protocol: protocol,
			name:     "Compliance-fips202205-" + protocol.String() + "-Client-ReallyWontAcceptChaCha",
			config: Config{
				MinVersion: VersionTLS12,
				MaxVersion: maxVersion,
				Bugs: ProtocolBugs{
					SendCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
				},
			},
			flags: []string{
				"-fips-202205",
			},
			shouldFail:    true,
			expectedError: ":WRONG_CIPHER_RETURNED:",
		})

		for _, curve := range testCurves {
			var isFIPSCurve bool
			switch curve.id {
			case CurveP256, CurveP384:
				isFIPSCurve = true
			}

			var isWPACurve bool
			switch curve.id {
			case CurveP384:
				isWPACurve = true
			}

			var isCNSA1Curve bool
			switch curve.id {
			case CurveP384, CurveMLKEM1024:
				isCNSA1Curve = true
			}

			var isCNSA2Curve bool
			switch curve.id {
			case CurveMLKEM1024:
				isCNSA2Curve = true
			}

			policies := []struct {
				flag    string
				curveOk bool
			}{
				{"-fips-202205", isFIPSCurve},
				{"-wpa-202304", isWPACurve},
				{"-cnsa1-202603", isCNSA1Curve},
				{"-cnsa2-202603", isCNSA2Curve},
			}

			for _, policy := range policies {
				testCases = append(testCases, testCase{
					testType: serverTest,
					protocol: protocol,
					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + curve.name,
					config: Config{
						MinVersion:       VersionTLS12,
						MaxVersion:       VersionTLS13,
						CurvePreferences: []CurveID{curve.id},
					},
					flags: []string{
						policy.flag,
					},
					shouldFail: !policy.curveOk,
				})

				testCases = append(testCases, testCase{
					testType: clientTest,
					protocol: protocol,
					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + curve.name,
					config: Config{
						MinVersion:       VersionTLS12,
						MaxVersion:       VersionTLS13,
						CurvePreferences: []CurveID{curve.id},
					},
					flags: []string{
						policy.flag,
					},
					shouldFail: !policy.curveOk,
				})
			}
		}

		// For CNSA1 as a server, if the client supports ML-KEM-1024 (even if not
		// the first choice, or the first shared choice), the server will select
		// ML-KEM-1024, even if the client provides key shares for other groups.
		testCases = append(testCases, testCase{
			testType: serverTest,
			protocol: protocol,
			name:     "Compliance-cnsa1-202603-" + protocol.String() + "-PrefersMLKEM1024",
			config: Config{
				MinVersion:       VersionTLS13,
				MaxVersion:       VersionTLS13,
				CurvePreferences: []CurveID{CurveP256, CurveP384, CurveMLKEM1024},
				DefaultCurves:    []CurveID{CurveP256, CurveP384},
			},
			flags: []string{
				"-cnsa1-202603",
			},
			expectations: connectionExpectations{
				curveID: CurveMLKEM1024,
			},
		})

		for _, sigalg := range testSignatureAlgorithms {
			// The TLS 1.0 and TLS 1.1 default signature algorithm does not
			// apply to these tests.
			if sigalg.id == 0 {
				continue
			}

			var isFIPSSigAlg bool
			switch sigalg.id {
			case signatureRSAPKCS1WithSHA256,
				signatureRSAPKCS1WithSHA384,
				signatureRSAPKCS1WithSHA512,
				signatureECDSAWithP256AndSHA256,
				signatureECDSAWithP384AndSHA384,
				signatureRSAPSSWithSHA256,
				signatureRSAPSSWithSHA384,
				signatureRSAPSSWithSHA512:
				isFIPSSigAlg = true
			}

			var isWPASigAlg bool
			switch sigalg.id {
			case signatureRSAPKCS1WithSHA384,
				signatureRSAPKCS1WithSHA512,
				signatureECDSAWithP384AndSHA384,
				signatureRSAPSSWithSHA384,
				signatureRSAPSSWithSHA512:
				isWPASigAlg = true
			}

			var isCNSASigAlg bool
			switch sigalg.id {
			case signatureRSAPKCS1WithSHA384,
				signatureECDSAWithP384AndSHA384,
				signatureRSAPSSWithSHA384:
				isCNSASigAlg = true
			}

			maxVersion := uint16(VersionTLS13)
			if hasComponent(sigalg.name, "PKCS1") {
				if protocol == quic {
					continue
				}
				maxVersion = VersionTLS12
			}

			policies := []struct {
				flag     string
				sigAlgOk bool
			}{
				{"-fips-202205", isFIPSSigAlg},
				{"-wpa-202304", isWPASigAlg},
				{"-cnsa1-202603", isCNSASigAlg},
				{"-cnsa2-202603", isCNSASigAlg},
			}

			cert := sigalg.baseCert.WithSignatureAlgorithms(sigalg.id)
			for _, policy := range policies {
				shouldFail := !policy.sigAlgOk
				// The CNSA2 policy requires TLS 1.3.
				if policy.flag == "-cnsa2-202603" && maxVersion == VersionTLS12 {
					shouldFail = true
				}
				testCases = append(testCases, testCase{
					testType: serverTest,
					protocol: protocol,
					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + sigalg.name,
					config: Config{
						MinVersion:                VersionTLS12,
						MaxVersion:                maxVersion,
						VerifySignatureAlgorithms: []signatureAlgorithm{sigalg.id},
					},
					// Use the base certificate. We wish to pick up the signature algorithm
					// preferences from the FIPS policy.
					shimCertificate: sigalg.baseCert,
					flags:           []string{policy.flag},
					shouldFail:      shouldFail,
				})

				testCases = append(testCases, testCase{
					testType: clientTest,
					protocol: protocol,
					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + sigalg.name,
					config: Config{
						MinVersion: VersionTLS12,
						MaxVersion: maxVersion,
						Credential: cert,
					},
					flags: []string{
						policy.flag,
					},
					shouldFail: shouldFail,
				})
			}
		}

		// AES-256-GCM is the most preferred.
		testCases = append(testCases, testCase{
			testType: serverTest,
			protocol: protocol,
			name:     "Compliance-cnsa202407-" + protocol.String() + "-AES-256-preferred",
			config: Config{
				MinVersion:   VersionTLS13,
				MaxVersion:   VersionTLS13,
				CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384},
			},
			flags: []string{
				"-cnsa-202407",
			},
			expectations: connectionExpectations{cipher: TLS_AES_256_GCM_SHA384},
		})

		// AES-128-GCM is preferred over ChaCha20-Poly1305.
		testCases = append(testCases, testCase{
			testType: serverTest,
			protocol: protocol,
			name:     "Compliance-cnsa202407-" + protocol.String() + "-AES-128-preferred",
			config: Config{
				MinVersion:   VersionTLS13,
				MaxVersion:   VersionTLS13,
				CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256},
			},
			flags: []string{
				"-cnsa-202407",
			},
			expectations: connectionExpectations{cipher: TLS_AES_128_GCM_SHA256},
		})
	}
}
