or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client.mdconstants.mddnssec.mddynamic-updates.mdedns0.mdindex.mdmessaging.mdrr-types.mdserver.mdtsig.mdutilities.mdzone-parsing.mdzone-transfer.md
tile.json

tsig.mddocs/

TSIG - Transaction Signatures

DNS transaction authentication and integrity using shared secret keys (RFC 2845, RFC 4635, RFC 8945).

Core Types

TSIG Record

Transaction signature record for message authentication.

type TSIG struct {
	Hdr        RR_Header
	Algorithm  string // HMAC algorithm domain name
	TimeSigned uint64 // Time message was signed (48-bit Unix time)
	Fudge      uint16 // Time fudge factor (seconds)
	MACSize    uint16 // MAC size in octets
	MAC        string // Hex encoded message authentication code
	OrigId     uint16 // Original message ID
	Error      uint16 // TSIG error code
	OtherLen   uint16 // Other data length
	OtherData  string // Hex encoded other data
}

TsigProvider Interface

Interface for custom TSIG implementations.

type TsigProvider interface {
	// Generate creates TSIG signature
	Generate(msg []byte, t *TSIG) ([]byte, error)

	// Verify validates TSIG signature
	Verify(msg []byte, t *TSIG) error
}

HMAC Algorithm Constants

const (
	HmacSHA1   = "hmac-sha1."
	HmacSHA224 = "hmac-sha224."
	HmacSHA256 = "hmac-sha256."
	HmacSHA384 = "hmac-sha384."
	HmacSHA512 = "hmac-sha512."
	HmacMD5    = "hmac-md5.sig-alg.reg.int." // Deprecated
)

TSIG Functions

Generation

// TsigGenerate generates TSIG for message
// m: message with stub TSIG record
// secret: base64 encoded shared secret
// requestMAC: MAC from previous message (empty for first)
// timersOnly: true for subsequent messages in sequence
// Returns: signed message bytes, MAC string, error
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error)

// TsigGenerateWithProvider uses custom TsigProvider
func TsigGenerateWithProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error)

Verification

// TsigVerify verifies TSIG signature on message
// msg: signed message bytes
// secret: base64 encoded shared secret
// requestMAC: MAC from previous message
// timersOnly: true for subsequent messages in sequence
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error

// TsigVerifyWithProvider uses custom TsigProvider
func TsigVerifyWithProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error

Message Methods

// SetTsig adds TSIG record to message
// owner: key name (must be FQDN)
// algo: HMAC algorithm
// fudge: time fudge in seconds (typically 300)
// timesigned: Unix timestamp
func (dns *Msg) SetTsig(owner, algo string, fudge uint16, timesigned int64) *Msg

// IsTsig checks if message has TSIG, returns TSIG record or nil
func (dns *Msg) IsTsig() *TSIG

Usage Examples

Client with TSIG

// Create client with TSIG secret
c := &dns.Client{
	TsigSecret: map[string]string{
		"example-key.": "base64secret==",
	},
}

// Create query with TSIG
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn("example.com"), dns.TypeA)
m.SetTsig("example-key.", dns.HmacSHA256, 300, time.Now().Unix())

// Send query
r, _, err := c.Exchange(m, "ns1.example.com:53")
if err != nil {
	log.Fatal(err)
}

// Response is automatically verified
fmt.Println(r.Answer)

Server with TSIG

server := &dns.Server{
	Addr: ":53",
	Net:  "udp",
	TsigSecret: map[string]string{
		"example-key.": "base64secret==",
	},
}

dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
	// Check if request has TSIG
	if r.IsTsig() == nil {
		// No TSIG, handle normally or refuse
		m := new(dns.Msg)
		m.SetRcode(r, dns.RcodeRefused)
		w.WriteMsg(m)
		return
	}

	// Check TSIG verification status
	if w.TsigStatus() != nil {
		// TSIG verification failed
		m := new(dns.Msg)
		m.SetRcode(r, dns.RcodeNotAuth)
		w.WriteMsg(m)
		return
	}

	// TSIG is valid, process request
	m := new(dns.Msg)
	m.SetReply(r)
	// Add answer records...
	w.WriteMsg(m)
})

server.ListenAndServe()

Manual TSIG Generation

// Create message
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn("example.com"), dns.TypeA)

// Add stub TSIG record
m.SetTsig("example-key.", dns.HmacSHA256, 300, time.Now().Unix())

// Generate TSIG
secret := "base64secret=="
signedMsg, mac, err := dns.TsigGenerate(m, secret, "", false)
if err != nil {
	log.Fatal(err)
}

// Send signedMsg over the wire
// Save mac for next message in sequence

Manual TSIG Verification

// Receive signed message
signedMsg := receiveFromWire()

// Verify TSIG
secret := "base64secret=="
err := dns.TsigVerify(signedMsg, secret, "", false)
if err != nil {
	log.Printf("TSIG verification failed: %v", err)
	return
}

// Parse verified message
m := new(dns.Msg)
m.Unpack(signedMsg)

Multi-Message TSIG Sequence

// Zone transfer with TSIG
c := &dns.Client{
	TsigSecret: map[string]string{
		"example-key.": "base64secret==",
	},
}

// First message: full TSIG
m := new(dns.Msg)
m.SetAxfr("example.com.")
m.SetTsig("example-key.", dns.HmacSHA256, 300, time.Now().Unix())

t := &dns.Transfer{
	TsigSecret: map[string]string{
		"example-key.": "base64secret==",
	},
}

ch, err := t.In(m, "ns1.example.com:53")
if err != nil {
	log.Fatal(err)
}

// Subsequent messages use timers-only mode automatically
for env := range ch {
	if env.Error != nil {
		log.Fatal(env.Error)
	}
	// Process records
}

Custom TSIG Provider

// Implement custom TSIG provider
type CustomTsigProvider struct {
	secrets map[string]string
}

func (p *CustomTsigProvider) Generate(msg []byte, t *dns.TSIG) ([]byte, error) {
	secret, ok := p.secrets[t.Hdr.Name]
	if !ok {
		return nil, fmt.Errorf("unknown key: %s", t.Hdr.Name)
	}

	// Custom HMAC generation logic
	h := hmac.New(sha256.New, []byte(secret))
	h.Write(msg)
	return h.Sum(nil), nil
}

func (p *CustomTsigProvider) Verify(msg []byte, t *dns.TSIG) error {
	mac, err := p.Generate(msg, t)
	if err != nil {
		return err
	}

	msgMac, _ := hex.DecodeString(t.MAC)
	if !hmac.Equal(mac, msgMac) {
		return dns.ErrSig
	}
	return nil
}

// Use custom provider
provider := &CustomTsigProvider{
	secrets: map[string]string{
		"example-key.": "my-secret",
	},
}

c := &dns.Client{
	TsigProvider: provider,
}

Dynamic Updates with TSIG

// Create dynamic update message
m := new(dns.Msg)
m.SetUpdate("example.com.")

// Add RR to insert
rr := &dns.A{
	Hdr: dns.RR_Header{
		Name:   "new.example.com.",
		Rrtype: dns.TypeA,
		Class:  dns.ClassINET,
		Ttl:    3600,
	},
	A: net.ParseIP("192.0.2.1"),
}
m.Insert([]dns.RR{rr})

// Add TSIG
m.SetTsig("update-key.", dns.HmacSHA256, 300, time.Now().Unix())

// Send update
c := &dns.Client{
	TsigSecret: map[string]string{
		"update-key.": "base64secret==",
	},
}

r, _, err := c.Exchange(m, "ns1.example.com:53")
if err != nil {
	log.Fatal(err)
}

if r.Rcode != dns.RcodeSuccess {
	log.Printf("Update failed: %s", dns.RcodeToString[r.Rcode])
}

TSIG Key Rotation

// Server with multiple keys
server := &dns.Server{
	Addr: ":53",
	Net:  "udp",
	TsigSecret: map[string]string{
		"old-key.": "old-base64secret==",
		"new-key.": "new-base64secret==",
	},
}

// Client can use either key
c := &dns.Client{
	TsigSecret: map[string]string{
		"old-key.": "old-base64secret==",
		"new-key.": "new-base64secret==",
	},
}

// Gradually migrate clients to new key
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn("example.com"), dns.TypeA)
m.SetTsig("new-key.", dns.HmacSHA256, 300, time.Now().Unix())

r, _, err := c.Exchange(m, "ns1.example.com:53")

TSIG Error Handling

dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
	tsig := r.IsTsig()
	if tsig == nil {
		// No TSIG
		return
	}

	// Check verification status
	err := w.TsigStatus()
	if err != nil {
		m := new(dns.Msg)
		m.SetReply(r)

		// Determine appropriate error code
		switch err {
		case dns.ErrSecret:
			m.Rcode = dns.RcodeBadKey
		case dns.ErrSig:
			m.Rcode = dns.RcodeBadSig
		case dns.ErrTime:
			m.Rcode = dns.RcodeBadTime
		default:
			m.Rcode = dns.RcodeNotAuth
		}

		w.WriteMsg(m)
		return
	}

	// TSIG valid, process request
})

TSIG with Different Algorithms

// HMAC-SHA256 (recommended)
m.SetTsig("key.", dns.HmacSHA256, 300, time.Now().Unix())

// HMAC-SHA512 (stronger)
m.SetTsig("key.", dns.HmacSHA512, 300, time.Now().Unix())

// HMAC-SHA1 (legacy compatibility)
m.SetTsig("key.", dns.HmacSHA1, 300, time.Now().Unix())

// HMAC-SHA384
m.SetTsig("key.", dns.HmacSHA384, 300, time.Now().Unix())

TSIG Time Validation

// Default fudge is 300 seconds (5 minutes)
m.SetTsig("key.", dns.HmacSHA256, 300, time.Now().Unix())

// Tighter time window (60 seconds)
m.SetTsig("key.", dns.HmacSHA256, 60, time.Now().Unix())

// Wider window for clock skew
m.SetTsig("key.", dns.HmacSHA256, 600, time.Now().Unix())

Complete TSIG Workflow

// Generate key
key := make([]byte, 32)
rand.Read(key)
secret := base64.StdEncoding.EncodeToString(key)

// Configure server
server := &dns.Server{
	Addr: ":53",
	Net:  "udp",
	TsigSecret: map[string]string{
		"my-key.": secret,
	},
}

go server.ListenAndServe()

// Configure client
client := &dns.Client{
	TsigSecret: map[string]string{
		"my-key.": secret,
	},
}

// Send authenticated query
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn("example.com"), dns.TypeA)
m.SetTsig("my-key.", dns.HmacSHA256, 300, time.Now().Unix())

r, _, err := client.Exchange(m, "127.0.0.1:53")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Authenticated response: %v\n", r.Answer)

TSIG Error Codes

const (
	RcodeBadSig  = 16 // TSIG signature failure
	RcodeBadKey  = 17 // Key not recognized
	RcodeBadTime = 18 // Signature out of time window
)

var (
	ErrSecret error // Unknown TSIG key
	ErrSig    error // Invalid TSIG signature
	ErrTime   error // Time out of fudge window
)

Security Considerations

Key Management

// Generate strong random keys
func GenerateTSIGKey() string {
	key := make([]byte, 32) // 256 bits for HMAC-SHA256
	if _, err := rand.Read(key); err != nil {
		panic(err)
	}
	return base64.StdEncoding.EncodeToString(key)
}

// Store keys securely
// - Use environment variables or secure key management systems
// - Never commit keys to version control
// - Rotate keys periodically

Algorithm Selection

  • Recommended: HmacSHA256 or HmacSHA512
  • Avoid: HmacMD5 (deprecated, weak)
  • Legacy: HmacSHA1 (only for compatibility)

Time Synchronization

// Ensure server clocks are synchronized (NTP)
// Default fudge of 300 seconds allows for reasonable clock skew
// Tighter fudge values require better time sync

Access Control

// Use TSIG for privileged operations
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
	// Check for TSIG on updates and zone transfers
	if r.Opcode == dns.OpcodeUpdate || r.Question[0].Qtype == dns.TypeAXFR {
		if r.IsTsig() == nil || w.TsigStatus() != nil {
			m := new(dns.Msg)
			m.SetRcode(r, dns.RcodeNotAuth)
			w.WriteMsg(m)
			return
		}
	}
	// Process request
})

Performance Tips

  1. Connection Reuse: Reuse connections for multiple signed messages
  2. Algorithm Choice: HMAC-SHA256 offers good security/performance balance
  3. Timers-Only Mode: Use for multi-message sequences (AXFR, IXFR)
  4. Secret Caching: Cache decoded secrets to avoid repeated base64 decoding

Related Topics

  • DNS Messaging - Adding TSIG to messages
  • Client Operations - Client-side TSIG
  • Server Operations - Server-side TSIG
  • Zone Transfers - TSIG with AXFR/IXFR
  • Dynamic Updates - TSIG with updates