tessl install tessl/golang-github-com-miekg--dns@1.1.1Complete DNS library for Go with full protocol control, DNSSEC support, and both client and server programming capabilities
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
})