Skip to content
Snippets Groups Projects
tsig.go 11 KiB
Newer Older
  • Learn to ignore specific revisions
  • package dns
    
    import (
    	"crypto/hmac"
    	"crypto/sha1"
    	"crypto/sha256"
    	"crypto/sha512"
    
    ale's avatar
    ale committed
    	"encoding/binary"
    
    	"encoding/hex"
    	"hash"
    	"strconv"
    	"strings"
    	"time"
    )
    
    // 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 (key tsigHMACProvider) Generate(msg []byte, t *TSIG) ([]byte, error) {
    	// If we barf here, the caller is to blame
    	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
    }
    
    
    // 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
    
    ale's avatar
    ale committed
    	MAC        string `dns:"size-hex:MACSize"`
    
    	OrigId     uint16
    	Error      uint16
    	OtherLen   uint16
    
    ale's avatar
    ale committed
    	OtherData  string `dns:"size-hex:OtherLen"`
    
    }
    
    // TSIG has no official presentation format, but this will suffice.
    
    func (rr *TSIG) String() string {
    
    	s := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format
    
    	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)) + // BIND prints NOERROR
    		" " + 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"}
    
    // 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
    
    ale's avatar
    ale committed
    	OtherData string `dns:"size-hex:OtherLen"`
    
    ale's avatar
    ale committed
    // If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC
    
    type macWireFmt struct {
    	MACSize uint16
    
    ale's avatar
    ale committed
    	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 is set to the empty string and
    // timersOnly is false.
    // If something goes wrong an error is returned, otherwise it is nil.
    func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
    
    	return tsigGenerateProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
    }
    
    func tsigGenerateProvider(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] // kill the TSIG from the msg
    	mbuf, err := m.Pack()
    	if err != nil {
    		return nil, "", err
    	}
    
    	buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
    	if err != nil {
    		return nil, "", err
    	}
    
    	// Copy all TSIG fields except MAC and its size, which are filled using the computed digest.
    	*t = *rr
    
    	mac, err := provider.Generate(buf, rr)
    	if err != nil {
    		return nil, "", err
    	}
    	t.MAC = hex.EncodeToString(mac)
    
    	t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
    
    
    	tbuf := make([]byte, Len(t))
    	off, err := PackRR(t, tbuf, 0, nil, false)
    	if err != nil {
    
    	mbuf = append(mbuf, tbuf[:off]...)
    
    ale's avatar
    ale committed
    	// Update the ArCount directly in the buffer.
    	binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1))
    
    
    	return mbuf, t.MAC, nil
    }
    
    // TsigVerify verifies the TSIG on a message.
    // If the signature does not validate err contains the
    // error, otherwise it is nil.
    func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
    
    	return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
    }
    
    func tsigVerifyProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
    	return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix()))
    
    }
    
    // actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests.
    
    func tsigVerify(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool, now uint64) error {
    
    	// Strip the TSIG from the incoming msg
    	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
    
    
    	// 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.
    	ti := now - tsig.TimeSigned
    	if now < tsig.TimeSigned {
    		ti = tsig.TimeSigned - now
    	}
    	if uint64(tsig.Fudge) < ti {
    		return ErrTime
    	}
    
    
    	return nil
    }
    
    // Create a wiredata buffer for the MAC calculation.
    
    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 // Standard (RFC) default.
    	}
    
    
    ale's avatar
    ale committed
    	// Replace message ID in header with original ID from TSIG
    	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)) // long enough
    
    		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
    
    }
    
    // Strip the TSIG from the raw message.
    func stripTsig(msg []byte) ([]byte, *TSIG, error) {
    
    ale's avatar
    ale committed
    	// Copied from msg.go's Unpack() Header, but modified.
    	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
    	}
    
    ale's avatar
    ale committed
    
    
    	// Rcode, see msg.go Unpack()
    	if int(dh.Bits&0xF) == RcodeNotAuth {
    		return nil, nil, ErrAuth
    	}
    
    
    ale's avatar
    ale committed
    	for i := 0; i < int(dh.Qdcount); i++ {
    		_, off, err = unpackQuestion(msg, off)
    
    		if err != nil {
    			return nil, nil, err
    		}
    	}
    
    ale's avatar
    ale committed
    
    	_, off, err = unpackRRslice(int(dh.Ancount), msg, off)
    	if err != nil {
    		return nil, nil, err
    
    ale's avatar
    ale committed
    	_, off, err = unpackRRslice(int(dh.Nscount), msg, off)
    	if err != nil {
    		return nil, nil, err
    
    ale's avatar
    ale committed
    
    	rr := new(TSIG)
    	var extra RR
    	for i := 0; i < int(dh.Arcount); i++ {
    
    ale's avatar
    ale committed
    		extra, off, err = UnpackRR(msg, off)
    
    		if err != nil {
    			return nil, nil, err
    		}
    
    ale's avatar
    ale committed
    		if extra.Header().Rrtype == TypeTSIG {
    			rr = extra.(*TSIG)
    
    ale's avatar
    ale committed
    			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
    }
    
    // Translate the TSIG time signed into a date. There is no
    // need for RFC1982 calculations as this date is 48 bits.
    func tsigTimeToString(t uint64) string {
    	ti := time.Unix(int64(t), 0).UTC()
    	return ti.Format("20060102150405")
    }
    
    ale's avatar
    ale committed
    
    func packTsigWire(tw *tsigWireFmt, msg []byte) (int, error) {
    	// copied from zmsg.go TSIG packing
    	// RR_Header
    	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
    }