package dns
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"encoding/hex"
"hash"
"strconv"
"strings"
"time"
)
const (
HmacSHA1 = "hmac-sha1."
HmacSHA224 = "hmac-sha224."
HmacSHA256 = "hmac-sha256."
HmacSHA384 = "hmac-sha384."
HmacSHA512 = "hmac-sha512."
HmacMD5 = "hmac-md5.sig-alg.reg.int."
)
type TsigProvider interface {
Generate (msg []byte , t *TSIG ) ([]byte , error )
Verify (msg []byte , t *TSIG ) error
}
type tsigHMACProvider string
func (key tsigHMACProvider ) Generate (msg []byte , t *TSIG ) ([]byte , error ) {
rawsecret , err := fromBase64 ([]byte (key ))
if err != nil {
return nil , err
}
var h hash .Hash
switch CanonicalName (t .Algorithm ) {
case HmacSHA1 :
h = hmac .New (sha1 .New , rawsecret )
case HmacSHA224 :
h = hmac .New (sha256 .New224 , rawsecret )
case HmacSHA256 :
h = hmac .New (sha256 .New , rawsecret )
case HmacSHA384 :
h = hmac .New (sha512 .New384 , rawsecret )
case HmacSHA512 :
h = hmac .New (sha512 .New , rawsecret )
default :
return nil , ErrKeyAlg
}
h .Write (msg )
return h .Sum (nil ), nil
}
func (key tsigHMACProvider ) Verify (msg []byte , t *TSIG ) error {
b , err := key .Generate (msg , t )
if err != nil {
return err
}
mac , err := hex .DecodeString (t .MAC )
if err != nil {
return err
}
if !hmac .Equal (b , mac ) {
return ErrSig
}
return nil
}
type tsigSecretProvider map [string ]string
func (ts tsigSecretProvider ) Generate (msg []byte , t *TSIG ) ([]byte , error ) {
key , ok := ts [t .Hdr .Name ]
if !ok {
return nil , ErrSecret
}
return tsigHMACProvider (key ).Generate (msg , t )
}
func (ts tsigSecretProvider ) Verify (msg []byte , t *TSIG ) error {
key , ok := ts [t .Hdr .Name ]
if !ok {
return ErrSecret
}
return tsigHMACProvider (key ).Verify (msg , t )
}
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"`
}
func (rr *TSIG ) String () string {
s := "\n;; TSIG PSEUDOSECTION:\n; "
s += rr .Hdr .String () +
" " + rr .Algorithm +
" " + tsigTimeToString (rr .TimeSigned ) +
" " + strconv .Itoa (int (rr .Fudge )) +
" " + strconv .Itoa (int (rr .MACSize )) +
" " + strings .ToUpper (rr .MAC ) +
" " + strconv .Itoa (int (rr .OrigId )) +
" " + strconv .Itoa (int (rr .Error )) +
" " + strconv .Itoa (int (rr .OtherLen )) +
" " + rr .OtherData
return s
}
func (*TSIG ) parse (c *zlexer , origin string ) *ParseError {
return &ParseError {err : "TSIG records do not have a presentation format" }
}
type tsigWireFmt struct {
Name string `dns:"domain-name"`
Class uint16
Ttl uint32
Algorithm string `dns:"domain-name"`
TimeSigned uint64 `dns:"uint48"`
Fudge uint16
Error uint16
OtherLen uint16
OtherData string `dns:"size-hex:OtherLen"`
}
type macWireFmt struct {
MACSize uint16
MAC string `dns:"size-hex:MACSize"`
}
type timerWireFmt struct {
TimeSigned uint64 `dns:"uint48"`
Fudge uint16
}
func TsigGenerate (m *Msg , secret , requestMAC string , timersOnly bool ) ([]byte , string , error ) {
return TsigGenerateWithProvider (m , tsigHMACProvider (secret ), requestMAC , timersOnly )
}
func TsigGenerateWithProvider (m *Msg , provider TsigProvider , requestMAC string , timersOnly bool ) ([]byte , string , error ) {
if m .IsTsig () == nil {
panic ("dns: TSIG not last RR in additional" )
}
rr := m .Extra [len (m .Extra )-1 ].(*TSIG )
m .Extra = m .Extra [0 : len (m .Extra )-1 ]
mbuf , err := m .Pack ()
if err != nil {
return nil , "" , err
}
buf , err := tsigBuffer (mbuf , rr , requestMAC , timersOnly )
if err != nil {
return nil , "" , err
}
t := new (TSIG )
*t = *rr
t .TimeSigned = 0
t .MAC = ""
t .MACSize = 0
if rr .Error != RcodeBadKey && rr .Error != RcodeBadSig {
mac , err := provider .Generate (buf , rr )
if err != nil {
return nil , "" , err
}
t .TimeSigned = rr .TimeSigned
t .MAC = hex .EncodeToString (mac )
t .MACSize = uint16 (len (t .MAC ) / 2 )
}
tbuf := make ([]byte , Len (t ))
off , err := PackRR (t , tbuf , 0 , nil , false )
if err != nil {
return nil , "" , err
}
mbuf = append (mbuf , tbuf [:off ]...)
binary .BigEndian .PutUint16 (mbuf [10 :], uint16 (len (m .Extra )+1 ))
return mbuf , t .MAC , nil
}
func TsigVerify (msg []byte , secret , requestMAC string , timersOnly bool ) error {
return tsigVerify (msg , tsigHMACProvider (secret ), requestMAC , timersOnly , uint64 (time .Now ().Unix ()))
}
func TsigVerifyWithProvider (msg []byte , provider TsigProvider , requestMAC string , timersOnly bool ) error {
return tsigVerify (msg , provider , requestMAC , timersOnly , uint64 (time .Now ().Unix ()))
}
func tsigVerify(msg []byte , provider TsigProvider , requestMAC string , timersOnly bool , now uint64 ) error {
stripped , tsig , err := stripTsig (msg )
if err != nil {
return err
}
buf , err := tsigBuffer (stripped , tsig , requestMAC , timersOnly )
if err != nil {
return err
}
if err := provider .Verify (buf , tsig ); err != nil {
return err
}
ti := now - tsig .TimeSigned
if now < tsig .TimeSigned {
ti = tsig .TimeSigned - now
}
if uint64 (tsig .Fudge ) < ti {
return ErrTime
}
return nil
}
func tsigBuffer(msgbuf []byte , rr *TSIG , requestMAC string , timersOnly bool ) ([]byte , error ) {
var buf []byte
if rr .TimeSigned == 0 {
rr .TimeSigned = uint64 (time .Now ().Unix ())
}
if rr .Fudge == 0 {
rr .Fudge = 300
}
binary .BigEndian .PutUint16 (msgbuf [0 :2 ], rr .OrigId )
if requestMAC != "" {
m := new (macWireFmt )
m .MACSize = uint16 (len (requestMAC ) / 2 )
m .MAC = requestMAC
buf = make ([]byte , len (requestMAC ))
n , err := packMacWire (m , buf )
if err != nil {
return nil , err
}
buf = buf [:n ]
}
tsigvar := make ([]byte , DefaultMsgSize )
if timersOnly {
tsig := new (timerWireFmt )
tsig .TimeSigned = rr .TimeSigned
tsig .Fudge = rr .Fudge
n , err := packTimerWire (tsig , tsigvar )
if err != nil {
return nil , err
}
tsigvar = tsigvar [:n ]
} else {
tsig := new (tsigWireFmt )
tsig .Name = CanonicalName (rr .Hdr .Name )
tsig .Class = ClassANY
tsig .Ttl = rr .Hdr .Ttl
tsig .Algorithm = CanonicalName (rr .Algorithm )
tsig .TimeSigned = rr .TimeSigned
tsig .Fudge = rr .Fudge
tsig .Error = rr .Error
tsig .OtherLen = rr .OtherLen
tsig .OtherData = rr .OtherData
n , err := packTsigWire (tsig , tsigvar )
if err != nil {
return nil , err
}
tsigvar = tsigvar [:n ]
}
if requestMAC != "" {
x := append (buf , msgbuf ...)
buf = append (x , tsigvar ...)
} else {
buf = append (msgbuf , tsigvar ...)
}
return buf , nil
}
func stripTsig(msg []byte ) ([]byte , *TSIG , error ) {
var (
dh Header
err error
)
off , tsigoff := 0 , 0
if dh , off , err = unpackMsgHdr (msg , off ); err != nil {
return nil , nil , err
}
if dh .Arcount == 0 {
return nil , nil , ErrNoSig
}
if int (dh .Bits &0xF ) == RcodeNotAuth {
return nil , nil , ErrAuth
}
for i := 0 ; i < int (dh .Qdcount ); i ++ {
_, off , err = unpackQuestion (msg , off )
if err != nil {
return nil , nil , err
}
}
_, off , err = unpackRRslice (int (dh .Ancount ), msg , off )
if err != nil {
return nil , nil , err
}
_, off , err = unpackRRslice (int (dh .Nscount ), msg , off )
if err != nil {
return nil , nil , err
}
rr := new (TSIG )
var extra RR
for i := 0 ; i < int (dh .Arcount ); i ++ {
tsigoff = off
extra , off , err = UnpackRR (msg , off )
if err != nil {
return nil , nil , err
}
if extra .Header ().Rrtype == TypeTSIG {
rr = extra .(*TSIG )
arcount := binary .BigEndian .Uint16 (msg [10 :])
binary .BigEndian .PutUint16 (msg [10 :], arcount -1 )
break
}
}
if rr == nil {
return nil , nil , ErrNoSig
}
return msg [:tsigoff ], rr , nil
}
func tsigTimeToString(t uint64 ) string {
ti := time .Unix (int64 (t ), 0 ).UTC ()
return ti .Format ("20060102150405" )
}
func packTsigWire(tw *tsigWireFmt , msg []byte ) (int , error ) {
off , err := PackDomainName (tw .Name , msg , 0 , nil , false )
if err != nil {
return off , err
}
off , err = packUint16 (tw .Class , msg , off )
if err != nil {
return off , err
}
off , err = packUint32 (tw .Ttl , msg , off )
if err != nil {
return off , err
}
off , err = PackDomainName (tw .Algorithm , msg , off , nil , false )
if err != nil {
return off , err
}
off , err = packUint48 (tw .TimeSigned , msg , off )
if err != nil {
return off , err
}
off , err = packUint16 (tw .Fudge , msg , off )
if err != nil {
return off , err
}
off , err = packUint16 (tw .Error , msg , off )
if err != nil {
return off , err
}
off , err = packUint16 (tw .OtherLen , msg , off )
if err != nil {
return off , err
}
off , err = packStringHex (tw .OtherData , msg , off )
if err != nil {
return off , err
}
return off , nil
}
func packMacWire(mw *macWireFmt , msg []byte ) (int , error ) {
off , err := packUint16 (mw .MACSize , msg , 0 )
if err != nil {
return off , err
}
off , err = packStringHex (mw .MAC , msg , off )
if err != nil {
return off , err
}
return off , nil
}
func packTimerWire(tw *timerWireFmt , msg []byte ) (int , error ) {
off , err := packUint48 (tw .TimeSigned , msg , 0 )
if err != nil {
return off , err
}
off , err = packUint16 (tw .Fudge , msg , off )
if err != nil {
return off , err
}
return off , nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .