ab9dd18bec
See https://github.com/hashicorp/consul/issues/3977 While trying to improve furthermore #3948 (This pull request is still valid since we are not using Compression to compute the result anyway). I saw a strange behaviour of dns library. Basically, msg.Len() and len(msg.Pack()) disagree on Message len. Thus, calculation of DNS response is false consul relies on msg.Len() instead of the result of Pack() This is linked to miekg/dns#453 and a fix has been provided with miekg/dns#454 Would it be possible to upgrade miekg/dns to a more recent function ? Consul might for instance upgrade to a post 1.0 release such as https://github.com/miekg/dns/releases/tag/v1.0.4
387 lines
9.3 KiB
Go
387 lines
9.3 KiB
Go
package dns
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/md5"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"hash"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// HMAC hashing codes. These are transmitted as domain names.
|
|
const (
|
|
HmacMD5 = "hmac-md5.sig-alg.reg.int."
|
|
HmacSHA1 = "hmac-sha1."
|
|
HmacSHA256 = "hmac-sha256."
|
|
HmacSHA512 = "hmac-sha512."
|
|
)
|
|
|
|
// 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
|
|
MAC string `dns:"size-hex:MACSize"`
|
|
OrigId uint16
|
|
Error uint16
|
|
OtherLen uint16
|
|
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"
|
|
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
|
|
}
|
|
|
|
// 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
|
|
OtherData string `dns:"size-hex:OtherLen"`
|
|
}
|
|
|
|
// If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC
|
|
type macWireFmt struct {
|
|
MACSize uint16
|
|
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) {
|
|
if m.IsTsig() == nil {
|
|
panic("dns: TSIG not last RR in additional")
|
|
}
|
|
// If we barf here, the caller is to blame
|
|
rawsecret, err := fromBase64([]byte(secret))
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
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 := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
|
|
|
|
t := new(TSIG)
|
|
var h hash.Hash
|
|
switch strings.ToLower(rr.Algorithm) {
|
|
case HmacMD5:
|
|
h = hmac.New(md5.New, []byte(rawsecret))
|
|
case HmacSHA1:
|
|
h = hmac.New(sha1.New, []byte(rawsecret))
|
|
case HmacSHA256:
|
|
h = hmac.New(sha256.New, []byte(rawsecret))
|
|
case HmacSHA512:
|
|
h = hmac.New(sha512.New, []byte(rawsecret))
|
|
default:
|
|
return nil, "", ErrKeyAlg
|
|
}
|
|
h.Write(buf)
|
|
t.MAC = hex.EncodeToString(h.Sum(nil))
|
|
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
|
|
|
|
t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}
|
|
t.Fudge = rr.Fudge
|
|
t.TimeSigned = rr.TimeSigned
|
|
t.Algorithm = rr.Algorithm
|
|
t.OrigId = m.Id
|
|
|
|
tbuf := make([]byte, t.len())
|
|
if off, err := PackRR(t, tbuf, 0, nil, false); err == nil {
|
|
tbuf = tbuf[:off] // reset to actual size used
|
|
} else {
|
|
return nil, "", err
|
|
}
|
|
mbuf = append(mbuf, tbuf...)
|
|
// 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 {
|
|
rawsecret, err := fromBase64([]byte(secret))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Strip the TSIG from the incoming msg
|
|
stripped, tsig, err := stripTsig(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msgMAC, err := hex.DecodeString(tsig.MAC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
|
|
|
|
// Fudge factor works both ways. A message can arrive before it was signed because
|
|
// of clock skew.
|
|
now := uint64(time.Now().Unix())
|
|
ti := now - tsig.TimeSigned
|
|
if now < tsig.TimeSigned {
|
|
ti = tsig.TimeSigned - now
|
|
}
|
|
if uint64(tsig.Fudge) < ti {
|
|
return ErrTime
|
|
}
|
|
|
|
var h hash.Hash
|
|
switch strings.ToLower(tsig.Algorithm) {
|
|
case HmacMD5:
|
|
h = hmac.New(md5.New, rawsecret)
|
|
case HmacSHA1:
|
|
h = hmac.New(sha1.New, rawsecret)
|
|
case HmacSHA256:
|
|
h = hmac.New(sha256.New, rawsecret)
|
|
case HmacSHA512:
|
|
h = hmac.New(sha512.New, rawsecret)
|
|
default:
|
|
return ErrKeyAlg
|
|
}
|
|
h.Write(buf)
|
|
if !hmac.Equal(h.Sum(nil), msgMAC) {
|
|
return ErrSig
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Create a wiredata buffer for the MAC calculation.
|
|
func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte {
|
|
var buf []byte
|
|
if rr.TimeSigned == 0 {
|
|
rr.TimeSigned = uint64(time.Now().Unix())
|
|
}
|
|
if rr.Fudge == 0 {
|
|
rr.Fudge = 300 // Standard (RFC) default.
|
|
}
|
|
|
|
// 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, _ := packMacWire(m, buf)
|
|
buf = buf[:n]
|
|
}
|
|
|
|
tsigvar := make([]byte, DefaultMsgSize)
|
|
if timersOnly {
|
|
tsig := new(timerWireFmt)
|
|
tsig.TimeSigned = rr.TimeSigned
|
|
tsig.Fudge = rr.Fudge
|
|
n, _ := packTimerWire(tsig, tsigvar)
|
|
tsigvar = tsigvar[:n]
|
|
} else {
|
|
tsig := new(tsigWireFmt)
|
|
tsig.Name = strings.ToLower(rr.Hdr.Name)
|
|
tsig.Class = ClassANY
|
|
tsig.Ttl = rr.Hdr.Ttl
|
|
tsig.Algorithm = strings.ToLower(rr.Algorithm)
|
|
tsig.TimeSigned = rr.TimeSigned
|
|
tsig.Fudge = rr.Fudge
|
|
tsig.Error = rr.Error
|
|
tsig.OtherLen = rr.OtherLen
|
|
tsig.OtherData = rr.OtherData
|
|
n, _ := packTsigWire(tsig, tsigvar)
|
|
tsigvar = tsigvar[:n]
|
|
}
|
|
|
|
if requestMAC != "" {
|
|
x := append(buf, msgbuf...)
|
|
buf = append(x, tsigvar...)
|
|
} else {
|
|
buf = append(msgbuf, tsigvar...)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// Strip the TSIG from the raw message.
|
|
func stripTsig(msg []byte) ([]byte, *TSIG, error) {
|
|
// 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
|
|
}
|
|
|
|
// Rcode, see msg.go Unpack()
|
|
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)
|
|
// Adjust Arcount.
|
|
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")
|
|
}
|
|
|
|
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
|
|
}
|