1package dns
2
3import (
4	"crypto/hmac"
5	"crypto/md5"
6	"crypto/sha1"
7	"crypto/sha256"
8	"crypto/sha512"
9	"encoding/binary"
10	"encoding/hex"
11	"hash"
12	"strconv"
13	"strings"
14	"time"
15)
16
17// HMAC hashing codes. These are transmitted as domain names.
18const (
19	HmacMD5    = "hmac-md5.sig-alg.reg.int."
20	HmacSHA1   = "hmac-sha1."
21	HmacSHA256 = "hmac-sha256."
22	HmacSHA512 = "hmac-sha512."
23)
24
25// TSIG is the RR the holds the transaction signature of a message.
26// See RFC 2845 and RFC 4635.
27type TSIG struct {
28	Hdr        RR_Header
29	Algorithm  string `dns:"domain-name"`
30	TimeSigned uint64 `dns:"uint48"`
31	Fudge      uint16
32	MACSize    uint16
33	MAC        string `dns:"size-hex:MACSize"`
34	OrigId     uint16
35	Error      uint16
36	OtherLen   uint16
37	OtherData  string `dns:"size-hex:OtherLen"`
38}
39
40// TSIG has no official presentation format, but this will suffice.
41
42func (rr *TSIG) String() string {
43	s := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format
44	s += rr.Hdr.String() +
45		" " + rr.Algorithm +
46		" " + tsigTimeToString(rr.TimeSigned) +
47		" " + strconv.Itoa(int(rr.Fudge)) +
48		" " + strconv.Itoa(int(rr.MACSize)) +
49		" " + strings.ToUpper(rr.MAC) +
50		" " + strconv.Itoa(int(rr.OrigId)) +
51		" " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR
52		" " + strconv.Itoa(int(rr.OtherLen)) +
53		" " + rr.OtherData
54	return s
55}
56
57func (rr *TSIG) parse(c *zlexer, origin string) *ParseError {
58	panic("dns: internal error: parse should never be called on TSIG")
59}
60
61// The following values must be put in wireformat, so that the MAC can be calculated.
62// RFC 2845, section 3.4.2. TSIG Variables.
63type tsigWireFmt struct {
64	// From RR_Header
65	Name  string `dns:"domain-name"`
66	Class uint16
67	Ttl   uint32
68	// Rdata of the TSIG
69	Algorithm  string `dns:"domain-name"`
70	TimeSigned uint64 `dns:"uint48"`
71	Fudge      uint16
72	// MACSize, MAC and OrigId excluded
73	Error     uint16
74	OtherLen  uint16
75	OtherData string `dns:"size-hex:OtherLen"`
76}
77
78// If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC
79type macWireFmt struct {
80	MACSize uint16
81	MAC     string `dns:"size-hex:MACSize"`
82}
83
84// 3.3. Time values used in TSIG calculations
85type timerWireFmt struct {
86	TimeSigned uint64 `dns:"uint48"`
87	Fudge      uint16
88}
89
90// TsigGenerate fills out the TSIG record attached to the message.
91// The message should contain
92// a "stub" TSIG RR with the algorithm, key name (owner name of the RR),
93// time fudge (defaults to 300 seconds) and the current time
94// The TSIG MAC is saved in that Tsig RR.
95// When TsigGenerate is called for the first time requestMAC is set to the empty string and
96// timersOnly is false.
97// If something goes wrong an error is returned, otherwise it is nil.
98func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
99	if m.IsTsig() == nil {
100		panic("dns: TSIG not last RR in additional")
101	}
102	// If we barf here, the caller is to blame
103	rawsecret, err := fromBase64([]byte(secret))
104	if err != nil {
105		return nil, "", err
106	}
107
108	rr := m.Extra[len(m.Extra)-1].(*TSIG)
109	m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
110	mbuf, err := m.Pack()
111	if err != nil {
112		return nil, "", err
113	}
114	buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
115
116	t := new(TSIG)
117	var h hash.Hash
118	switch strings.ToLower(rr.Algorithm) {
119	case HmacMD5:
120		h = hmac.New(md5.New, rawsecret)
121	case HmacSHA1:
122		h = hmac.New(sha1.New, rawsecret)
123	case HmacSHA256:
124		h = hmac.New(sha256.New, rawsecret)
125	case HmacSHA512:
126		h = hmac.New(sha512.New, rawsecret)
127	default:
128		return nil, "", ErrKeyAlg
129	}
130	h.Write(buf)
131	t.MAC = hex.EncodeToString(h.Sum(nil))
132	t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
133
134	t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}
135	t.Fudge = rr.Fudge
136	t.TimeSigned = rr.TimeSigned
137	t.Algorithm = rr.Algorithm
138	t.OrigId = m.Id
139
140	tbuf := make([]byte, Len(t))
141	off, err := PackRR(t, tbuf, 0, nil, false)
142	if err != nil {
143		return nil, "", err
144	}
145	mbuf = append(mbuf, tbuf[:off]...)
146	// Update the ArCount directly in the buffer.
147	binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1))
148
149	return mbuf, t.MAC, nil
150}
151
152// TsigVerify verifies the TSIG on a message.
153// If the signature does not validate err contains the
154// error, otherwise it is nil.
155func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
156	rawsecret, err := fromBase64([]byte(secret))
157	if err != nil {
158		return err
159	}
160	// Strip the TSIG from the incoming msg
161	stripped, tsig, err := stripTsig(msg)
162	if err != nil {
163		return err
164	}
165
166	msgMAC, err := hex.DecodeString(tsig.MAC)
167	if err != nil {
168		return err
169	}
170
171	buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly)
172
173	// Fudge factor works both ways. A message can arrive before it was signed because
174	// of clock skew.
175	now := uint64(time.Now().Unix())
176	ti := now - tsig.TimeSigned
177	if now < tsig.TimeSigned {
178		ti = tsig.TimeSigned - now
179	}
180	if uint64(tsig.Fudge) < ti {
181		return ErrTime
182	}
183
184	var h hash.Hash
185	switch strings.ToLower(tsig.Algorithm) {
186	case HmacMD5:
187		h = hmac.New(md5.New, rawsecret)
188	case HmacSHA1:
189		h = hmac.New(sha1.New, rawsecret)
190	case HmacSHA256:
191		h = hmac.New(sha256.New, rawsecret)
192	case HmacSHA512:
193		h = hmac.New(sha512.New, rawsecret)
194	default:
195		return ErrKeyAlg
196	}
197	h.Write(buf)
198	if !hmac.Equal(h.Sum(nil), msgMAC) {
199		return ErrSig
200	}
201	return nil
202}
203
204// Create a wiredata buffer for the MAC calculation.
205func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte {
206	var buf []byte
207	if rr.TimeSigned == 0 {
208		rr.TimeSigned = uint64(time.Now().Unix())
209	}
210	if rr.Fudge == 0 {
211		rr.Fudge = 300 // Standard (RFC) default.
212	}
213
214	// Replace message ID in header with original ID from TSIG
215	binary.BigEndian.PutUint16(msgbuf[0:2], rr.OrigId)
216
217	if requestMAC != "" {
218		m := new(macWireFmt)
219		m.MACSize = uint16(len(requestMAC) / 2)
220		m.MAC = requestMAC
221		buf = make([]byte, len(requestMAC)) // long enough
222		n, _ := packMacWire(m, buf)
223		buf = buf[:n]
224	}
225
226	tsigvar := make([]byte, DefaultMsgSize)
227	if timersOnly {
228		tsig := new(timerWireFmt)
229		tsig.TimeSigned = rr.TimeSigned
230		tsig.Fudge = rr.Fudge
231		n, _ := packTimerWire(tsig, tsigvar)
232		tsigvar = tsigvar[:n]
233	} else {
234		tsig := new(tsigWireFmt)
235		tsig.Name = strings.ToLower(rr.Hdr.Name)
236		tsig.Class = ClassANY
237		tsig.Ttl = rr.Hdr.Ttl
238		tsig.Algorithm = strings.ToLower(rr.Algorithm)
239		tsig.TimeSigned = rr.TimeSigned
240		tsig.Fudge = rr.Fudge
241		tsig.Error = rr.Error
242		tsig.OtherLen = rr.OtherLen
243		tsig.OtherData = rr.OtherData
244		n, _ := packTsigWire(tsig, tsigvar)
245		tsigvar = tsigvar[:n]
246	}
247
248	if requestMAC != "" {
249		x := append(buf, msgbuf...)
250		buf = append(x, tsigvar...)
251	} else {
252		buf = append(msgbuf, tsigvar...)
253	}
254	return buf
255}
256
257// Strip the TSIG from the raw message.
258func stripTsig(msg []byte) ([]byte, *TSIG, error) {
259	// Copied from msg.go's Unpack() Header, but modified.
260	var (
261		dh  Header
262		err error
263	)
264	off, tsigoff := 0, 0
265
266	if dh, off, err = unpackMsgHdr(msg, off); err != nil {
267		return nil, nil, err
268	}
269	if dh.Arcount == 0 {
270		return nil, nil, ErrNoSig
271	}
272
273	// Rcode, see msg.go Unpack()
274	if int(dh.Bits&0xF) == RcodeNotAuth {
275		return nil, nil, ErrAuth
276	}
277
278	for i := 0; i < int(dh.Qdcount); i++ {
279		_, off, err = unpackQuestion(msg, off)
280		if err != nil {
281			return nil, nil, err
282		}
283	}
284
285	_, off, err = unpackRRslice(int(dh.Ancount), msg, off)
286	if err != nil {
287		return nil, nil, err
288	}
289	_, off, err = unpackRRslice(int(dh.Nscount), msg, off)
290	if err != nil {
291		return nil, nil, err
292	}
293
294	rr := new(TSIG)
295	var extra RR
296	for i := 0; i < int(dh.Arcount); i++ {
297		tsigoff = off
298		extra, off, err = UnpackRR(msg, off)
299		if err != nil {
300			return nil, nil, err
301		}
302		if extra.Header().Rrtype == TypeTSIG {
303			rr = extra.(*TSIG)
304			// Adjust Arcount.
305			arcount := binary.BigEndian.Uint16(msg[10:])
306			binary.BigEndian.PutUint16(msg[10:], arcount-1)
307			break
308		}
309	}
310	if rr == nil {
311		return nil, nil, ErrNoSig
312	}
313	return msg[:tsigoff], rr, nil
314}
315
316// Translate the TSIG time signed into a date. There is no
317// need for RFC1982 calculations as this date is 48 bits.
318func tsigTimeToString(t uint64) string {
319	ti := time.Unix(int64(t), 0).UTC()
320	return ti.Format("20060102150405")
321}
322
323func packTsigWire(tw *tsigWireFmt, msg []byte) (int, error) {
324	// copied from zmsg.go TSIG packing
325	// RR_Header
326	off, err := PackDomainName(tw.Name, msg, 0, nil, false)
327	if err != nil {
328		return off, err
329	}
330	off, err = packUint16(tw.Class, msg, off)
331	if err != nil {
332		return off, err
333	}
334	off, err = packUint32(tw.Ttl, msg, off)
335	if err != nil {
336		return off, err
337	}
338
339	off, err = PackDomainName(tw.Algorithm, msg, off, nil, false)
340	if err != nil {
341		return off, err
342	}
343	off, err = packUint48(tw.TimeSigned, msg, off)
344	if err != nil {
345		return off, err
346	}
347	off, err = packUint16(tw.Fudge, msg, off)
348	if err != nil {
349		return off, err
350	}
351
352	off, err = packUint16(tw.Error, msg, off)
353	if err != nil {
354		return off, err
355	}
356	off, err = packUint16(tw.OtherLen, msg, off)
357	if err != nil {
358		return off, err
359	}
360	off, err = packStringHex(tw.OtherData, msg, off)
361	if err != nil {
362		return off, err
363	}
364	return off, nil
365}
366
367func packMacWire(mw *macWireFmt, msg []byte) (int, error) {
368	off, err := packUint16(mw.MACSize, msg, 0)
369	if err != nil {
370		return off, err
371	}
372	off, err = packStringHex(mw.MAC, msg, off)
373	if err != nil {
374		return off, err
375	}
376	return off, nil
377}
378
379func packTimerWire(tw *timerWireFmt, msg []byte) (int, error) {
380	off, err := packUint48(tw.TimeSigned, msg, 0)
381	if err != nil {
382		return off, err
383	}
384	off, err = packUint16(tw.Fudge, msg, off)
385	if err != nil {
386		return off, err
387	}
388	return off, nil
389}
390