// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package dtls

import (
	
	
	
	

	
	
	
	
	
	
	
	
	
	
)

//nolint:gocognit,gocyclo,lll,cyclop,maintidx
func flight4Parse(
	 context.Context,
	 flightConn,
	 *State,
	 *handshakeCache,
	 *handshakeConfig,
) (flightVal, *alert.Alert, error) {
	, ,  := .fullPullMap(.handshakeRecvSequence, .cipherSuite,
		handshakeCachePullRule{handshake.TypeCertificate, .initialEpoch, true, true},
		handshakeCachePullRule{handshake.TypeClientKeyExchange, .initialEpoch, true, false},
		handshakeCachePullRule{handshake.TypeCertificateVerify, .initialEpoch, true, true},
	)
	if ! {
		// No valid message received. Keep reading
		return 0, nil, nil
	}

	// Validate type
	var  *handshake.MessageClientKeyExchange
	if ,  = [handshake.TypeClientKeyExchange].(*handshake.MessageClientKeyExchange); ! {
		return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
	}

	if ,  := [handshake.TypeCertificate].(*handshake.MessageCertificate);  {
		.PeerCertificates = .Certificate
		// If the client offer its certificate, just disable session resumption.
		// Otherwise, we have to store the certificate identitfication and expire time.
		// And we have to check whether this certificate expired, revoked or changed.
		//
		// https://curl.se/docs/CVE-2016-5419.html
		.SessionID = nil
	}

	//nolint:nestif
	if ,  := [handshake.TypeCertificateVerify].(*handshake.MessageCertificateVerify);  {
		if .PeerCertificates == nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errCertificateVerifyNoCertificate
		}

		 := .pullAndMerge(
			handshakeCachePullRule{handshake.TypeClientHello, .initialEpoch, true, false},
			handshakeCachePullRule{handshake.TypeServerHello, .initialEpoch, false, false},
			handshakeCachePullRule{handshake.TypeCertificate, .initialEpoch, false, false},
			handshakeCachePullRule{handshake.TypeServerKeyExchange, .initialEpoch, false, false},
			handshakeCachePullRule{handshake.TypeCertificateRequest, .initialEpoch, false, false},
			handshakeCachePullRule{handshake.TypeServerHelloDone, .initialEpoch, false, false},
			handshakeCachePullRule{handshake.TypeCertificate, .initialEpoch, true, false},
			handshakeCachePullRule{handshake.TypeClientKeyExchange, .initialEpoch, true, false},
		)

		// Verify that the pair of hash algorithm and signiture is listed.
		var  bool
		for ,  := range .localSignatureSchemes {
			if .Hash == .HashAlgorithm && .Signature == .SignatureAlgorithm {
				 = true

				break
			}
		}
		if ! {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoAvailableSignatureSchemes
		}

		if  := verifyCertificateVerify(
			,
			.HashAlgorithm,
			.Signature,
			.PeerCertificates,
		);  != nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, 
		}
		var  [][]*x509.Certificate
		var  error
		var  bool
		if .clientAuth >= VerifyClientCertIfGiven {
			if ,  = verifyClientCert(.PeerCertificates, .clientCAs);  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, 
			}
			 = true
		}
		if .verifyPeerCertificate != nil {
			if  := .verifyPeerCertificate(.PeerCertificates, );  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, 
			}
		}
		.peerCertificatesVerified = 
	} else if .PeerCertificates != nil {
		// A certificate was received, but we haven't seen a CertificateVerify
		// keep reading until we receive one
		return 0, nil, nil
	}

	if !.cipherSuite.IsInitialized() { //nolint:nestif
		 := .localRandom.MarshalFixed()
		 := .remoteRandom.MarshalFixed()

		var  error
		var  []byte
		if .cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypePreSharedKey {
			var  []byte
			if ,  = .localPSKCallback(.IdentityHint);  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
			}
			.IdentityHint = .IdentityHint
			switch .cipherSuite.KeyExchangeAlgorithm() {
			case CipherSuiteKeyExchangeAlgorithmPsk:
				 = prf.PSKPreMasterSecret()
			case (CipherSuiteKeyExchangeAlgorithmPsk | CipherSuiteKeyExchangeAlgorithmEcdhe):
				if ,  = prf.EcdhePSKPreMasterSecret(
					,
					.PublicKey,
					.localKeypair.PrivateKey,
					.localKeypair.Curve,
				);  != nil {
					return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
				}
			default:
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidCipherSuite
			}
		} else {
			,  = prf.PreMasterSecret(
				.PublicKey,
				.localKeypair.PrivateKey,
				.localKeypair.Curve,
			)
			if  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, 
			}
		}

		if .extendedMasterSecret {
			var  []byte
			,  = .sessionHash(.cipherSuite.HashFunc(), .initialEpoch)
			if  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
			}

			.masterSecret,  = prf.ExtendedMasterSecret(, , .cipherSuite.HashFunc())
			if  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
			}
		} else {
			.masterSecret,  = prf.MasterSecret(
				,
				[:],
				[:],
				.cipherSuite.HashFunc(),
			)
			if  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
			}
		}

		if  := .cipherSuite.Init(.masterSecret, [:], [:], false);  != nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
		}
		.writeKeyLog(keyLogLabelTLS12, [:], .masterSecret)
	}

	if len(.SessionID) > 0 {
		 := Session{
			ID:     .SessionID,
			Secret: .masterSecret,
		}
		.log.Tracef("[handshake] save new session: %x", .ID)
		if  := .sessionStore.Set(.SessionID, );  != nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
		}
	}

	// Now, encrypted packets can be handled
	if  := .handleQueuedPackets();  != nil {
		return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
	}

	, ,  = .fullPullMap(, .cipherSuite,
		handshakeCachePullRule{handshake.TypeFinished, .initialEpoch + 1, true, false},
	)
	if ! {
		// No valid message received. Keep reading
		return 0, nil, nil
	}
	.handshakeRecvSequence = 

	if _,  = [handshake.TypeFinished].(*handshake.MessageFinished); ! {
		return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil
	}

	if .cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeAnonymous { //nolint:nestif
		if .verifyConnection != nil {
			,  := .clone()
			if  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
			}
			if  := .verifyConnection();  != nil {
				return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, 
			}
		}

		return flight6, nil, nil
	}

	switch .clientAuth {
	case RequireAnyClientCert:
		if .PeerCertificates == nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errClientCertificateRequired
		}
	case VerifyClientCertIfGiven:
		if .PeerCertificates != nil && !.peerCertificatesVerified {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, errClientCertificateNotVerified
		}
	case RequireAndVerifyClientCert:
		if .PeerCertificates == nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errClientCertificateRequired
		}
		if !.peerCertificatesVerified {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, errClientCertificateNotVerified
		}
	case NoClientCert, RequestClientCert:
		// go to flight6
	}
	if .verifyConnection != nil {
		,  := .clone()
		if  != nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
		}
		if  := .verifyConnection();  != nil {
			return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, 
		}
	}

	return flight6, nil, nil
}

//nolint:gocognit,cyclop,maintidx
func flight4Generate(
	 flightConn,
	 *State,
	 *handshakeCache,
	 *handshakeConfig,
) ([]*packet, *alert.Alert, error) {
	 := []extension.Extension{&extension.RenegotiationInfo{
		RenegotiatedConnection: 0,
	}}
	if (.extendedMasterSecret == RequestExtendedMasterSecret ||
		.extendedMasterSecret == RequireExtendedMasterSecret) && .extendedMasterSecret {
		 = append(, &extension.UseExtendedMasterSecret{
			Supported: true,
		})
	}
	if .getSRTPProtectionProfile() != 0 {
		 = append(, &extension.UseSRTP{
			ProtectionProfiles:  []SRTPProtectionProfile{.getSRTPProtectionProfile()},
			MasterKeyIdentifier: .localSRTPMasterKeyIdentifier,
		})
	}
	if .cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate {
		 = append(, &extension.SupportedPointFormats{
			PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed},
		})
	}

	,  := extension.ALPNProtocolSelection(.supportedProtocols, .peerSupportedProtocols)
	if  != nil {
		return nil, &alert.Alert{Level: alert.Fatal, Description: alert.NoApplicationProtocol}, 
	}
	if  != "" {
		 = append(, &extension.ALPN{
			ProtocolNameList: []string{},
		})
		.NegotiatedProtocol = 
	}

	// If we have a connection ID generator, we are willing to use connection
	// IDs. We already know whether the client supports connection IDs from
	// parsing the ClientHello, so avoid setting local connection ID if the
	// client won't send it.
	if .connectionIDGenerator != nil && .remoteConnectionID != nil {
		.setLocalConnectionID(.connectionIDGenerator())
		 = append(, &extension.ConnectionID{CID: .getLocalConnectionID()})
	}

	var  []*packet
	 := uint16(.cipherSuite.ID())

	if .sessionStore != nil {
		.SessionID = make([]byte, sessionLength)
		if ,  := rand.Read(.SessionID);  != nil {
			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
		}
	}

	 := &handshake.MessageServerHello{
		Version:           protocol.Version1_2,
		Random:            .localRandom,
		SessionID:         .SessionID,
		CipherSuiteID:     &,
		CompressionMethod: defaultCompressionMethods()[0],
		Extensions:        ,
	}

	var  handshake.Handshake

	if .serverHelloMessageHook != nil {
		 = handshake.Handshake{Message: .serverHelloMessageHook(*)}
	} else {
		 = handshake.Handshake{Message: }
	}

	 = append(, &packet{
		record: &recordlayer.RecordLayer{
			Header: recordlayer.Header{
				Version: protocol.Version1_2,
			},
			Content: &,
		},
	})

	switch {
	case .cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate:
		,  := .getCertificate(&ClientHelloInfo{
			ServerName:   .serverName,
			CipherSuites: []ciphersuite.ID{.cipherSuite.ID()},
			RandomBytes:  .remoteRandom.RandomBytes,
		})
		if  != nil {
			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, 
		}

		 = append(, &packet{
			record: &recordlayer.RecordLayer{
				Header: recordlayer.Header{
					Version: protocol.Version1_2,
				},
				Content: &handshake.Handshake{
					Message: &handshake.MessageCertificate{
						Certificate: .Certificate,
					},
				},
			},
		})

		 := .localRandom.MarshalFixed()
		 := .remoteRandom.MarshalFixed()

		,  := .PrivateKey.(crypto.Signer)
		if ! {
			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidPrivateKey
		}

		// Find compatible signature scheme
		,  := signaturehash.SelectSignatureScheme(.localSignatureSchemes, )
		if  != nil {
			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, 
		}

		,  := generateKeySignature(
			[:],
			[:],
			.localKeypair.PublicKey,
			.namedCurve,
			,
			.Hash,
		)
		if  != nil {
			return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, 
		}
		.localKeySignature = 

		 = append(, &packet{
			record: &recordlayer.RecordLayer{
				Header: recordlayer.Header{
					Version: protocol.Version1_2,
				},
				Content: &handshake.Handshake{
					Message: &handshake.MessageServerKeyExchange{
						EllipticCurveType:  elliptic.CurveTypeNamedCurve,
						NamedCurve:         .namedCurve,
						PublicKey:          .localKeypair.PublicKey,
						HashAlgorithm:      .Hash,
						SignatureAlgorithm: .Signature,
						Signature:          .localKeySignature,
					},
				},
			},
		})

		if .clientAuth > NoClientCert {
			// An empty list of certificateAuthorities signals to
			// the client that it may send any certificate in response
			// to our request. When we know the CAs we trust, then
			// we can send them down, so that the client can choose
			// an appropriate certificate to give to us.
			var  [][]byte
			if .clientCAs != nil {
				// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR
				// because cert does not come from SystemCertPool and it's ok if certificate
				// authorities is empty.
				 = .clientCAs.Subjects()
			}

			 := &handshake.MessageCertificateRequest{
				CertificateTypes:            []clientcertificate.Type{clientcertificate.RSASign, clientcertificate.ECDSASign},
				SignatureHashAlgorithms:     .localSignatureSchemes,
				CertificateAuthoritiesNames: ,
			}

			var  handshake.Handshake

			if .certificateRequestMessageHook != nil {
				 = handshake.Handshake{Message: .certificateRequestMessageHook(*)}
			} else {
				 = handshake.Handshake{Message: }
			}

			 = append(, &packet{
				record: &recordlayer.RecordLayer{
					Header: recordlayer.Header{
						Version: protocol.Version1_2,
					},
					Content: &,
				},
			})
		}
	case .localPSKIdentityHint != nil ||
		.cipherSuite.KeyExchangeAlgorithm().Has(CipherSuiteKeyExchangeAlgorithmEcdhe):
		// To help the client in selecting which identity to use, the server
		// can provide a "PSK identity hint" in the ServerKeyExchange message.
		// If no hint is provided and cipher suite doesn't use elliptic curve,
		// the ServerKeyExchange message is omitted.
		//
		// https://tools.ietf.org/html/rfc4279#section-2
		 := &handshake.MessageServerKeyExchange{
			IdentityHint: .localPSKIdentityHint,
		}
		if .cipherSuite.KeyExchangeAlgorithm().Has(CipherSuiteKeyExchangeAlgorithmEcdhe) {
			.EllipticCurveType = elliptic.CurveTypeNamedCurve
			.NamedCurve = .namedCurve
			.PublicKey = .localKeypair.PublicKey
		}
		 = append(, &packet{
			record: &recordlayer.RecordLayer{
				Header: recordlayer.Header{
					Version: protocol.Version1_2,
				},
				Content: &handshake.Handshake{
					Message: ,
				},
			},
		})
	}

	 = append(, &packet{
		record: &recordlayer.RecordLayer{
			Header: recordlayer.Header{
				Version: protocol.Version1_2,
			},
			Content: &handshake.Handshake{
				Message: &handshake.MessageServerHelloDone{},
			},
		},
	})

	return , nil, nil
}