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