dns/tsig.go

457 lines
12 KiB
Go
Raw Permalink Normal View History

package dns
2011-01-08 21:39:15 +00:00
import (
2011-01-17 20:10:48 +00:00
"crypto/hmac"
2012-01-27 23:35:37 +00:00
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
2011-01-09 14:54:23 +00:00
"encoding/hex"
2012-01-27 23:35:37 +00:00
"hash"
2012-03-02 22:07:25 +00:00
"strconv"
"strings"
"time"
2011-01-08 21:39:15 +00:00
)
2011-01-27 08:29:11 +00:00
// HMAC hashing codes. These are transmitted as domain names.
2011-01-08 21:39:15 +00:00
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.
2011-01-08 21:39:15 +00:00
)
// 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
}
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)
}
// TSIG is the RR the holds the transaction signature of a message.
// See RFC 2845 and RFC 4635.
type TSIG struct {
2012-03-02 22:07:25 +00:00
Hdr RR_Header
Algorithm string `dns:"domain-name"`
2012-11-19 11:26:13 +00:00
TimeSigned uint64 `dns:"uint48"`
2012-03-02 22:07:25 +00:00
Fudge uint16
MACSize uint16
MAC string `dns:"size-hex:MACSize"`
2012-03-02 22:07:25 +00:00
OrigId uint16
Error uint16
OtherLen uint16
OtherData string `dns:"size-hex:OtherLen"`
2012-03-02 22:07:25 +00:00
}
// TSIG has no official presentation format, but this will suffice.
2012-05-08 12:17:17 +00:00
func (rr *TSIG) String() string {
s := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format
2012-03-02 22:07:25 +00:00
s += rr.Hdr.String() +
" " + rr.Algorithm +
2012-09-11 19:45:21 +00:00
" " + tsigTimeToString(rr.TimeSigned) +
2012-03-02 22:07:25 +00:00
" " + 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
}
Support parsing known RR types in RFC 3597 format (#1211) * Support parsing known RR types in RFC 3597 format This is the format used for "Unknown DNS Resource Records", but it's also useful to support parsing known RR types in this way. RFC 3597 says: An implementation MAY also choose to represent some RRs of known type using the above generic representations for the type, class and/or RDATA, which carries the benefit of making the resulting master file portable to servers where these types are unknown. Using the generic representation for the RDATA of an RR of known type can also be useful in the case of an RR type where the text format varies depending on a version, protocol, or similar field (or several) embedded in the RDATA when such a field has a value for which no text format is known, e.g., a LOC RR [RFC1876] with a VERSION other than 0. Even though an RR of known type represented in the \# format is effectively treated as an unknown type for the purpose of parsing the RDATA text representation, all further processing by the server MUST treat it as a known type and take into account any applicable type- specific rules regarding compression, canonicalization, etc. * Correct mistakes in TestZoneParserAddressAAAA This was spotted when writing TestParseKnownRRAsRFC3597. * Eliminate canParseAsRR This has the advantage that concrete types will now be returned for parsed ANY, NULL, OPT and TSIG records. * Expand TestDynamicUpdateParsing for RFC 3597 This ensures we're properly handling empty RDATA for RFC 3597 parsed records.
2021-01-30 13:05:25 +00:00
func (*TSIG) parse(c *zlexer, origin string) *ParseError {
return &ParseError{err: "TSIG records do not have a presentation format"}
}
2011-01-17 20:10:48 +00:00
// 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 {
2012-02-29 21:00:39 +00:00
// 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 {
2011-03-14 20:16:45 +00:00
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 should be set to the empty string and timersOnly to
// false.
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
return TsigGenerateWithProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
}
// TsigGenerateWithProvider is similar to TsigGenerate, but allows for a custom TsigProvider.
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")
2011-04-22 14:37:26 +00:00
}
2011-03-20 19:55:27 +00:00
rr := m.Extra[len(m.Extra)-1].(*TSIG)
2011-04-22 14:37:26 +00:00
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
}
2011-04-18 20:08:12 +00:00
t := new(TSIG)
// Copy all TSIG fields except MAC, its size, and time signed which are filled when signing.
*t = *rr
t.TimeSigned = 0
t.MAC = ""
t.MACSize = 0
// Sign unless there is a key or MAC validation error (RFC 8945 5.3.2)
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) // Size is half!
}
2011-03-20 20:40:10 +00:00
Properly calculate compressed message lengths (#833) * Remove fullSize return from compressionLenSearch This wasn't used anywhere but TestCompressionLenSearch, and was very wrong. * Add generated compressedLen functions and use them This replaces the confusing and complicated compressionLenSlice function. * Use compressedLenWithCompressionMap even for uncompressed This leaves the len() functions unused and they'll soon be removed. This also fixes the off-by-one error of compressedLen when a (Q)NAME is ".". * Use Len helper instead of RR.len private method * Merge len and compressedLen functions * Merge compressedLen helper into Msg.Len * Remove compress bool from compressedLenWithCompressionMap * Merge map insertion into compressionLenSearch This eliminates the need to loop over the domain name twice when we're compressing the name. * Use compressedNameLen for NSEC.NextDomain This was a mistake. * Remove compress from RR.len * Add test case for multiple questions length * Add test case for MINFO and SOA compression These are the only RRs with multiple compressible names within the same RR, and they were previously broken. * Rename compressedNameLen to domainNameLen It also handles the length of uncompressed domain names. * Use off directly instead of len(s[:off]) * Move initial maxCompressionOffset check out of compressionLenMapInsert This should allow us to avoid the call overhead of compressionLenMapInsert in certain limited cases and may result in a slight performance increase. compressionLenMapInsert still has a maxCompressionOffset check inside the for loop. * Rename compressedLenWithCompressionMap to msgLenWithCompressionMap This better reflects that it also calculates the uncompressed length. * Merge TestMsgCompressMINFO with TestMsgCompressSOA They're both testing the same thing. * Remove compressionLenMapInsert compressionLenSearch does everything compressionLenMapInsert did anyway. * Only call compressionLenSearch in one place in domainNameLen * Split if statement in domainNameLen The last two commits worsened the performance of domainNameLen noticably, this change restores it's original performance. name old time/op new time/op delta MsgLength-12 550ns ±13% 510ns ±21% ~ (p=0.050 n=10+10) MsgLengthNoCompression-12 26.9ns ± 2% 27.0ns ± 1% ~ (p=0.198 n=9+10) MsgLengthPack-12 2.30µs ±12% 2.26µs ±16% ~ (p=0.739 n=10+10) MsgLengthMassive-12 32.9µs ± 7% 32.0µs ±10% ~ (p=0.243 n=9+10) MsgLengthOnlyQuestion-12 9.60ns ± 1% 9.20ns ± 1% -4.16% (p=0.000 n=9+9) * Remove stray newline from TestMsgCompressionMultipleQuestions * Remove stray newline in length_test.go This was introduced when resolving merge conflicts.
2018-11-29 23:33:41 +00:00
tbuf := make([]byte, Len(t))
off, err := PackRR(t, tbuf, 0, nil, false)
if err != nil {
return nil, "", err
}
mbuf = append(mbuf, tbuf[:off]...)
// Update the ArCount directly in the buffer.
binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1))
return mbuf, t.MAC, nil
2011-03-20 19:55:27 +00:00
}
// TsigVerify verifies the TSIG on a message. If the signature does not
// validate the returned error contains the cause. If the signature is OK, the
// error is nil.
2011-11-02 22:06:54 +00:00
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
}
// TsigVerifyWithProvider is similar to TsigVerify, but allows for a custom TsigProvider.
func TsigVerifyWithProvider(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
2011-04-19 09:31:47 +00:00
stripped, tsig, err := stripTsig(msg)
2011-03-25 13:46:30 +00:00
if err != nil {
return err
2011-03-20 19:55:27 +00:00
}
buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
if err != nil {
return err
}
if err := provider.Verify(buf, tsig); err != nil {
return err
2011-11-02 22:06:54 +00:00
}
// 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
}
2011-11-02 22:06:54 +00:00
return nil
2011-01-25 21:29:48 +00:00
}
2011-03-23 18:07:06 +00:00
// Create a wiredata buffer for the MAC calculation.
func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) ([]byte, error) {
2012-03-05 21:03:18 +00:00
var buf []byte
2011-04-22 14:37:26 +00:00
if rr.TimeSigned == 0 {
rr.TimeSigned = uint64(time.Now().Unix())
2011-04-22 14:37:26 +00:00
}
if rr.Fudge == 0 {
2011-11-02 22:06:54 +00:00
rr.Fudge = 300 // Standard (RFC) default.
2011-04-22 14:37:26 +00:00
}
2011-03-20 19:55:27 +00:00
// Replace message ID in header with original ID from TSIG
binary.BigEndian.PutUint16(msgbuf[0:2], rr.OrigId)
2011-04-22 14:37:26 +00:00
if requestMAC != "" {
2011-03-20 19:55:27 +00:00
m := new(macWireFmt)
2011-04-22 14:37:26 +00:00
m.MACSize = uint16(len(requestMAC) / 2)
m.MAC = requestMAC
2012-05-05 15:37:48 +00:00
buf = make([]byte, len(requestMAC)) // long enough
n, err := packMacWire(m, buf)
if err != nil {
return nil, err
}
2012-03-05 21:03:18 +00:00
buf = buf[:n]
2011-03-20 19:55:27 +00:00
}
tsigvar := make([]byte, DefaultMsgSize)
2011-04-18 20:08:12 +00:00
if timersOnly {
tsig := new(timerWireFmt)
2011-04-18 20:08:12 +00:00
tsig.TimeSigned = rr.TimeSigned
tsig.Fudge = rr.Fudge
n, err := packTimerWire(tsig, tsigvar)
if err != nil {
return nil, err
}
2011-03-20 19:55:27 +00:00
tsigvar = tsigvar[:n]
} else {
tsig := new(tsigWireFmt)
tsig.Name = CanonicalName(rr.Hdr.Name)
tsig.Class = ClassANY
2011-04-22 14:37:26 +00:00
tsig.Ttl = rr.Hdr.Ttl
tsig.Algorithm = CanonicalName(rr.Algorithm)
2011-04-18 20:08:12 +00:00
tsig.TimeSigned = rr.TimeSigned
tsig.Fudge = rr.Fudge
2011-04-22 14:37:26 +00:00
tsig.Error = rr.Error
tsig.OtherLen = rr.OtherLen
tsig.OtherData = rr.OtherData
n, err := packTsigWire(tsig, tsigvar)
if err != nil {
return nil, err
}
2011-03-20 19:55:27 +00:00
tsigvar = tsigvar[:n]
}
2012-03-05 21:03:18 +00:00
if requestMAC != "" {
x := append(buf, msgbuf...)
2011-03-20 19:55:27 +00:00
buf = append(x, tsigvar...)
} else {
2011-04-18 20:08:12 +00:00
buf = append(msgbuf, tsigvar...)
2011-03-20 19:55:27 +00:00
}
return buf, nil
2011-03-20 19:55:27 +00:00
}
2011-04-19 09:31:47 +00:00
2014-07-30 06:35:06 +00:00
// 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
2011-03-15 17:43:05 +00:00
}
if dh.Arcount == 0 {
2011-04-19 09:31:47 +00:00
return nil, nil, ErrNoSig
2011-03-15 17:43:05 +00:00
}
2011-04-22 14:37:26 +00:00
// Rcode, see msg.go Unpack()
if int(dh.Bits&0xF) == RcodeNotAuth {
return nil, nil, ErrAuth
}
2011-03-15 17:43:05 +00:00
for i := 0; i < int(dh.Qdcount); i++ {
_, off, err = unpackQuestion(msg, off)
if err != nil {
return nil, nil, err
}
2011-03-15 17:43:05 +00:00
}
_, off, err = unpackRRslice(int(dh.Ancount), msg, off)
if err != nil {
return nil, nil, err
2011-03-15 17:43:05 +00:00
}
_, off, err = unpackRRslice(int(dh.Nscount), msg, off)
if err != nil {
return nil, nil, err
2011-03-15 17:43:05 +00:00
}
rr := new(TSIG)
var extra RR
for i := 0; i < int(dh.Arcount); i++ {
2011-03-15 17:43:05 +00:00
tsigoff = off
extra, off, err = UnpackRR(msg, off)
if err != nil {
return nil, nil, err
}
if extra.Header().Rrtype == TypeTSIG {
rr = extra.(*TSIG)
2011-03-15 17:43:05 +00:00
// Adjust Arcount.
arcount := binary.BigEndian.Uint16(msg[10:])
binary.BigEndian.PutUint16(msg[10:], arcount-1)
2011-03-15 17:43:05 +00:00
break
}
}
2011-04-22 14:37:26 +00:00
if rr == nil {
2011-04-19 09:31:47 +00:00
return nil, nil, ErrNoSig
2011-04-22 14:37:26 +00:00
}
2011-04-19 09:31:47 +00:00
return msg[:tsigoff], rr, nil
}
2012-03-02 22:12:23 +00:00
// Translate the TSIG time signed into a date. There is no
// need for RFC1982 calculations as this date is 48 bits.
2012-09-11 19:45:21 +00:00
func tsigTimeToString(t uint64) string {
2012-03-05 21:03:18 +00:00
ti := time.Unix(int64(t), 0).UTC()
return ti.Format("20060102150405")
2012-03-02 22:12:23 +00:00
}
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
}