package dns

import (
	
	
	
	
	
	
	
	
	
	
)

// HMAC hashing codes. These are transmitted as domain names.
const (
	HmacSHA1   = "hmac-sha1."
	HmacSHA224 = "hmac-sha224."
	HmacSHA256 = "hmac-sha256."
	HmacSHA384 = "hmac-sha384."
	HmacSHA512 = "hmac-sha512."

	HmacMD5 = "hmac-md5.sig-alg.reg.int." // Deprecated: HmacMD5 is no longer supported.
)

// TsigProvider provides the API to plug-in a custom TSIG implementation.
type TsigProvider interface {
	// Generate is passed the DNS message to be signed and the partial TSIG RR. It returns the signature and nil, otherwise an error.
	Generate(msg []byte, t *TSIG) ([]byte, error)
	// Verify is passed the DNS message to be verified and the TSIG RR. If the signature is valid it will return nil, otherwise an error.
	Verify(msg []byte, t *TSIG) error
}

type tsigHMACProvider string

func ( tsigHMACProvider) ( []byte,  *TSIG) ([]byte, error) {
	// If we barf here, the caller is to blame
	,  := fromBase64([]byte())
	if  != nil {
		return nil, 
	}
	var  hash.Hash
	switch CanonicalName(.Algorithm) {
	case HmacSHA1:
		 = hmac.New(sha1.New, )
	case HmacSHA224:
		 = hmac.New(sha256.New224, )
	case HmacSHA256:
		 = hmac.New(sha256.New, )
	case HmacSHA384:
		 = hmac.New(sha512.New384, )
	case HmacSHA512:
		 = hmac.New(sha512.New, )
	default:
		return nil, ErrKeyAlg
	}
	.Write()
	return .Sum(nil), nil
}

func ( tsigHMACProvider) ( []byte,  *TSIG) error {
	,  := .Generate(, )
	if  != nil {
		return 
	}
	,  := hex.DecodeString(.MAC)
	if  != nil {
		return 
	}
	if !hmac.Equal(, ) {
		return ErrSig
	}
	return nil
}

type tsigSecretProvider map[string]string

func ( tsigSecretProvider) ( []byte,  *TSIG) ([]byte, error) {
	,  := [.Hdr.Name]
	if ! {
		return nil, ErrSecret
	}
	return tsigHMACProvider().Generate(, )
}

func ( tsigSecretProvider) ( []byte,  *TSIG) error {
	,  := [.Hdr.Name]
	if ! {
		return ErrSecret
	}
	return tsigHMACProvider().Verify(, )
}

// TSIG is the RR the holds the transaction signature of a message.
// See RFC 2845 and RFC 4635.
type TSIG struct {
	Hdr        RR_Header
	Algorithm  string `dns:"domain-name"`
	TimeSigned uint64 `dns:"uint48"`
	Fudge      uint16
	MACSize    uint16
	MAC        string `dns:"size-hex:MACSize"`
	OrigId     uint16
	Error      uint16
	OtherLen   uint16
	OtherData  string `dns:"size-hex:OtherLen"`
}

// TSIG has no official presentation format, but this will suffice.

func ( *TSIG) () string {
	 := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format
	 += .Hdr.String() +
		" " + .Algorithm +
		" " + tsigTimeToString(.TimeSigned) +
		" " + strconv.Itoa(int(.Fudge)) +
		" " + strconv.Itoa(int(.MACSize)) +
		" " + strings.ToUpper(.MAC) +
		" " + strconv.Itoa(int(.OrigId)) +
		" " + strconv.Itoa(int(.Error)) + // BIND prints NOERROR
		" " + strconv.Itoa(int(.OtherLen)) +
		" " + .OtherData
	return 
}

func (*TSIG) ( *zlexer,  string) *ParseError {
	return &ParseError{err: "TSIG records do not have a presentation format"}
}

// The following values must be put in wireformat, so that the MAC can be calculated.
// RFC 2845, section 3.4.2. TSIG Variables.
type tsigWireFmt struct {
	// From RR_Header
	Name  string `dns:"domain-name"`
	Class uint16
	Ttl   uint32
	// Rdata of the TSIG
	Algorithm  string `dns:"domain-name"`
	TimeSigned uint64 `dns:"uint48"`
	Fudge      uint16
	// MACSize, MAC and OrigId excluded
	Error     uint16
	OtherLen  uint16
	OtherData string `dns:"size-hex:OtherLen"`
}

// If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC
type macWireFmt struct {
	MACSize uint16
	MAC     string `dns:"size-hex:MACSize"`
}

// 3.3. Time values used in TSIG calculations
type timerWireFmt struct {
	TimeSigned uint64 `dns:"uint48"`
	Fudge      uint16
}

// TsigGenerate fills out the TSIG record attached to the message.
// The message should contain a "stub" TSIG RR with the algorithm, key name
// (owner name of the RR), time fudge (defaults to 300 seconds) and the current
// time The TSIG MAC is saved in that Tsig RR. When TsigGenerate is called for
// the first time requestMAC should be set to the empty string and timersOnly to
// false.
func ( *Msg, ,  string,  bool) ([]byte, string, error) {
	return TsigGenerateWithProvider(, tsigHMACProvider(), , )
}

// TsigGenerateWithProvider is similar to TsigGenerate, but allows for a custom TsigProvider.
func ( *Msg,  TsigProvider,  string,  bool) ([]byte, string, error) {
	if .IsTsig() == nil {
		panic("dns: TSIG not last RR in additional")
	}

	 := .Extra[len(.Extra)-1].(*TSIG)
	.Extra = .Extra[0 : len(.Extra)-1] // kill the TSIG from the msg
	,  := .Pack()
	if  != nil {
		return nil, "", 
	}

	,  := tsigBuffer(, , , )
	if  != nil {
		return nil, "", 
	}

	 := new(TSIG)
	// Copy all TSIG fields except MAC, its size, and time signed which are filled when signing.
	* = *
	.TimeSigned = 0
	.MAC = ""
	.MACSize = 0

	// Sign unless there is a key or MAC validation error (RFC 8945 5.3.2)
	if .Error != RcodeBadKey && .Error != RcodeBadSig {
		,  := .Generate(, )
		if  != nil {
			return nil, "", 
		}
		.TimeSigned = .TimeSigned
		.MAC = hex.EncodeToString()
		.MACSize = uint16(len(.MAC) / 2) // Size is half!
	}

	 := make([]byte, Len())
	,  := PackRR(, , 0, nil, false)
	if  != nil {
		return nil, "", 
	}
	 = append(, [:]...)
	// Update the ArCount directly in the buffer.
	binary.BigEndian.PutUint16([10:], uint16(len(.Extra)+1))

	return , .MAC, nil
}

// TsigVerify verifies the TSIG on a message. If the signature does not
// validate the returned error contains the cause. If the signature is OK, the
// error is nil.
func ( []byte, ,  string,  bool) error {
	return tsigVerify(, tsigHMACProvider(), , , uint64(time.Now().Unix()))
}

// TsigVerifyWithProvider is similar to TsigVerify, but allows for a custom TsigProvider.
func ( []byte,  TsigProvider,  string,  bool) error {
	return tsigVerify(, , , , uint64(time.Now().Unix()))
}

// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests.
func tsigVerify( []byte,  TsigProvider,  string,  bool,  uint64) error {
	// Strip the TSIG from the incoming msg
	, ,  := stripTsig()
	if  != nil {
		return 
	}

	,  := tsigBuffer(, , , )
	if  != nil {
		return 
	}

	if  := .Verify(, );  != nil {
		return 
	}

	// Fudge factor works both ways. A message can arrive before it was signed because
	// of clock skew.
	// We check this after verifying the signature, following draft-ietf-dnsop-rfc2845bis
	// instead of RFC2845, in order to prevent a security vulnerability as reported in CVE-2017-3142/3143.
	 :=  - .TimeSigned
	if  < .TimeSigned {
		 = .TimeSigned - 
	}
	if uint64(.Fudge) <  {
		return ErrTime
	}

	return nil
}

// Create a wiredata buffer for the MAC calculation.
func tsigBuffer( []byte,  *TSIG,  string,  bool) ([]byte, error) {
	var  []byte
	if .TimeSigned == 0 {
		.TimeSigned = uint64(time.Now().Unix())
	}
	if .Fudge == 0 {
		.Fudge = 300 // Standard (RFC) default.
	}

	// Replace message ID in header with original ID from TSIG
	binary.BigEndian.PutUint16([0:2], .OrigId)

	if  != "" {
		 := new(macWireFmt)
		.MACSize = uint16(len() / 2)
		.MAC = 
		 = make([]byte, len()) // long enough
		,  := packMacWire(, )
		if  != nil {
			return nil, 
		}
		 = [:]
	}

	 := make([]byte, DefaultMsgSize)
	if  {
		 := new(timerWireFmt)
		.TimeSigned = .TimeSigned
		.Fudge = .Fudge
		,  := packTimerWire(, )
		if  != nil {
			return nil, 
		}
		 = [:]
	} else {
		 := new(tsigWireFmt)
		.Name = CanonicalName(.Hdr.Name)
		.Class = ClassANY
		.Ttl = .Hdr.Ttl
		.Algorithm = CanonicalName(.Algorithm)
		.TimeSigned = .TimeSigned
		.Fudge = .Fudge
		.Error = .Error
		.OtherLen = .OtherLen
		.OtherData = .OtherData
		,  := packTsigWire(, )
		if  != nil {
			return nil, 
		}
		 = [:]
	}

	if  != "" {
		 := append(, ...)
		 = append(, ...)
	} else {
		 = append(, ...)
	}
	return , nil
}

// Strip the TSIG from the raw message.
func stripTsig( []byte) ([]byte, *TSIG, error) {
	// Copied from msg.go's Unpack() Header, but modified.
	var (
		  Header
		 error
	)
	,  := 0, 0

	if , ,  = unpackMsgHdr(, );  != nil {
		return nil, nil, 
	}
	if .Arcount == 0 {
		return nil, nil, ErrNoSig
	}

	// Rcode, see msg.go Unpack()
	if int(.Bits&0xF) == RcodeNotAuth {
		return nil, nil, ErrAuth
	}

	for  := 0;  < int(.Qdcount); ++ {
		_, ,  = unpackQuestion(, )
		if  != nil {
			return nil, nil, 
		}
	}

	_, ,  = unpackRRslice(int(.Ancount), , )
	if  != nil {
		return nil, nil, 
	}
	_, ,  = unpackRRslice(int(.Nscount), , )
	if  != nil {
		return nil, nil, 
	}

	 := new(TSIG)
	var  RR
	for  := 0;  < int(.Arcount); ++ {
		 = 
		, ,  = UnpackRR(, )
		if  != nil {
			return nil, nil, 
		}
		if .Header().Rrtype == TypeTSIG {
			 = .(*TSIG)
			// Adjust Arcount.
			 := binary.BigEndian.Uint16([10:])
			binary.BigEndian.PutUint16([10:], -1)
			break
		}
	}
	if  == nil {
		return nil, nil, ErrNoSig
	}
	return [:], , nil
}

// Translate the TSIG time signed into a date. There is no
// need for RFC1982 calculations as this date is 48 bits.
func tsigTimeToString( uint64) string {
	 := time.Unix(int64(), 0).UTC()
	return .Format("20060102150405")
}

func packTsigWire( *tsigWireFmt,  []byte) (int, error) {
	// copied from zmsg.go TSIG packing
	// RR_Header
	,  := PackDomainName(.Name, , 0, nil, false)
	if  != nil {
		return , 
	}
	,  = packUint16(.Class, , )
	if  != nil {
		return , 
	}
	,  = packUint32(.Ttl, , )
	if  != nil {
		return , 
	}

	,  = PackDomainName(.Algorithm, , , nil, false)
	if  != nil {
		return , 
	}
	,  = packUint48(.TimeSigned, , )
	if  != nil {
		return , 
	}
	,  = packUint16(.Fudge, , )
	if  != nil {
		return , 
	}

	,  = packUint16(.Error, , )
	if  != nil {
		return , 
	}
	,  = packUint16(.OtherLen, , )
	if  != nil {
		return , 
	}
	,  = packStringHex(.OtherData, , )
	if  != nil {
		return , 
	}
	return , nil
}

func packMacWire( *macWireFmt,  []byte) (int, error) {
	,  := packUint16(.MACSize, , 0)
	if  != nil {
		return , 
	}
	,  = packStringHex(.MAC, , )
	if  != nil {
		return , 
	}
	return , nil
}

func packTimerWire( *timerWireFmt,  []byte) (int, error) {
	,  := packUint48(.TimeSigned, , 0)
	if  != nil {
		return , 
	}
	,  = packUint16(.Fudge, , )
	if  != nil {
		return , 
	}
	return , nil
}