DNS transaction authentication and integrity using shared secret keys (RFC 2845, RFC 4635, RFC 8945).
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
}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
}const (
HmacSHA1 = "hmac-sha1."
HmacSHA224 = "hmac-sha224."
HmacSHA256 = "hmac-sha256."
HmacSHA384 = "hmac-sha384."
HmacSHA512 = "hmac-sha512."
HmacMD5 = "hmac-md5.sig-alg.reg.int." // Deprecated
)// 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)// 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// 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// 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 := &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()// 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// 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)// 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
}// 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,
}// 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])
}// 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")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
})// 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())// 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())// 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)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
)// 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 periodicallyHmacSHA256 or HmacSHA512HmacMD5 (deprecated, weak)HmacSHA1 (only for compatibility)// Ensure server clocks are synchronized (NTP)
// Default fudge of 300 seconds allows for reasonable clock skew
// Tighter fudge values require better time sync// 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
})