tsig.c 30.6 KB
Newer Older
1
/*
Bob Halley's avatar
Bob Halley committed
2
 * Copyright (C) 1999, 2000  Internet Software Consortium.
3
 *
4
5
6
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
7
 *
8
9
10
11
12
13
14
15
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17
18
 */

/*
19
 * $Id: tsig.c,v 1.99 2001/01/09 14:32:44 marka Exp $
20
21
22
23
 * Principal Author: Brian Wellington
 */

#include <config.h>
24
#include <stdlib.h>		/* Required for abs(). */
25

26
#include <isc/buffer.h>
27
#include <isc/mem.h>
Brian Wellington's avatar
Brian Wellington committed
28
#include <isc/print.h>
29
#include <isc/string.h>		/* Required for HP/UX (and others?) */
Bob Halley's avatar
Bob Halley committed
30
#include <isc/util.h>
31
32

#include <dns/keyvalues.h>
33
#include <dns/log.h>
34
#include <dns/message.h>
35
#include <dns/rbt.h>
36
37
38
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
39
#include <dns/rdatastruct.h>
40
#include <dns/result.h>
41
42
43
44
45
#include <dns/tsig.h>

#include <dst/result.h>

#define TSIG_MAGIC		0x54534947	/* TSIG */
46
#define VALID_TSIG_KEY(x)	ISC_MAGIC_VALID(x, TSIG_MAGIC)
47

Brian Wellington's avatar
Brian Wellington committed
48
#define is_response(msg) (msg->flags & DNS_MESSAGEFLAG_QR)
Brian Wellington's avatar
Brian Wellington committed
49
50
#define algname_is_allocated(algname) \
	((algname) != dns_tsig_hmacmd5_name && \
51
52
	 (algname) != dns_tsig_gssapi_name && \
	 (algname) != dns_tsig_gssapims_name)
Brian Wellington's avatar
Brian Wellington committed
53

Brian Wellington's avatar
Brian Wellington committed
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#define BADTIMELEN 6

/* Copied from name.c */
struct dns_constname {
	dns_name_t name;
	unsigned char const_ndata[30];
	unsigned char const_offsets[5];
};

static struct dns_constname hmacmd5 = {
	{
		DNS_NAME_MAGIC,
		hmacmd5.const_ndata, 26, 5,
		DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE,
		hmacmd5.const_offsets, NULL,
		{(void *)-1, (void *)-1},
		{NULL, NULL}
	},
	{ "\010hmac-md5\007sig-alg\003reg\003int" },	/* const_ndata */
	{ 0, 9, 17, 21, 25 }				/* const_offsets */
};

dns_name_t *dns_tsig_hmacmd5_name = &hmacmd5.name;
77

Brian Wellington's avatar
Brian Wellington committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
static struct dns_constname gsstsig = {
	{
		DNS_NAME_MAGIC,
		gsstsig.const_ndata, 10, 2,
		DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE,
		gsstsig.const_offsets, NULL,
		{(void *)-1, (void *)-1},
		{NULL, NULL}
	},
	{ "\010gss-tsig" },				/* const_ndata */
	{ 0, 9 }					/* const_offsets */
};

dns_name_t *dns_tsig_gssapi_name = &gsstsig.name;

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/* It's nice of Microsoft to conform to their own standard. */
static struct dns_constname gsstsigms = {
	{
		DNS_NAME_MAGIC,
		gsstsigms.const_ndata, 19, 4,
		DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE,
		gsstsigms.const_offsets, NULL,
		{(void *)-1, (void *)-1},
		{NULL, NULL}
	},
	{ "\003gss\011microsoft\003com" },		/* const_ndata */
	{ 0, 4, 14, 18 }				/* const_offsets */
};

dns_name_t *dns_tsig_gssapims_name = &gsstsigms.name;

Brian Wellington's avatar
Brian Wellington committed
109
static isc_result_t
110
tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg);
111

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
static void
tsig_log(dns_tsigkey_t *key, int level, const char *fmt, ...) {
	va_list ap;
	char message[4096];
	char namestr[DNS_NAME_FORMATSIZE];

	if (isc_log_wouldlog(dns_lctx, level) == ISC_FALSE)
		return;
	if (key != NULL)
		dns_name_format(&key->name, namestr, sizeof(namestr));
	else
		strcpy(namestr, "<null>");
	va_start(ap, fmt);
	vsnprintf(message, sizeof(message), fmt, ap);
	va_end(ap);
	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_TSIG,
		      level, "tsig key '%s': %s", namestr, message);
}

131
isc_result_t
Brian Wellington's avatar
Brian Wellington committed
132
133
134
135
136
dns_tsigkey_createfromkey(dns_name_t *name, dns_name_t *algorithm,
			  dst_key_t *dstkey, isc_boolean_t generated,
			  dns_name_t *creator, isc_stdtime_t inception,
			  isc_stdtime_t expire, isc_mem_t *mctx,
			  dns_tsig_keyring_t *ring, dns_tsigkey_t **key)
137
{
138
	dns_tsigkey_t *tkey;
139
140
	isc_result_t ret;

141
	REQUIRE(key == NULL || *key == NULL);
142
143
144
145
	REQUIRE(name != NULL);
	REQUIRE(algorithm != NULL);
	REQUIRE(mctx != NULL);

146
147
	tkey = (dns_tsigkey_t *) isc_mem_get(mctx, sizeof(dns_tsigkey_t));
	if (tkey == NULL)
148
149
150
151
152
153
		return (ISC_R_NOMEMORY);

	dns_name_init(&tkey->name, NULL);
	ret = dns_name_dup(name, mctx, &tkey->name);
	if (ret != ISC_R_SUCCESS)
		goto cleanup_key;
Bob Halley's avatar
Bob Halley committed
154
	dns_name_downcase(&tkey->name, &tkey->name, NULL);
155

Brian Wellington's avatar
Brian Wellington committed
156
157
	if (dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME))
		tkey->algorithm = DNS_TSIG_HMACMD5_NAME;
Brian Wellington's avatar
Brian Wellington committed
158
159
	else if (dns_name_equal(algorithm, DNS_TSIG_GSSAPI_NAME))
		tkey->algorithm = DNS_TSIG_GSSAPI_NAME;
160
161
	else if (dns_name_equal(algorithm, DNS_TSIG_GSSAPIMS_NAME))
		tkey->algorithm = DNS_TSIG_GSSAPIMS_NAME;
Brian Wellington's avatar
Brian Wellington committed
162
	else {
Brian Wellington's avatar
Brian Wellington committed
163
		if (key != NULL) {
164
			ret = DNS_R_BADALG;
Brian Wellington's avatar
Brian Wellington committed
165
166
167
168
169
170
171
172
173
174
175
176
177
			goto cleanup_name;
		}
		tkey->algorithm = isc_mem_get(mctx, sizeof(dns_name_t));
		if (tkey->algorithm == NULL) {
			ret = ISC_R_NOMEMORY;
			goto cleanup_name;
		}
		dns_name_init(tkey->algorithm, NULL);
		ret = dns_name_dup(algorithm, mctx, tkey->algorithm);
		if (ret != ISC_R_SUCCESS)
			goto cleanup_algorithm;
		dns_name_downcase(tkey->algorithm, tkey->algorithm, NULL);
	}
178

179
180
181
182
183
184
185
	if (creator != NULL) {
		tkey->creator = isc_mem_get(mctx, sizeof(dns_name_t));
		if (tkey->creator == NULL) {
			ret = ISC_R_NOMEMORY;
			goto cleanup_algorithm;
		}
		dns_name_init(tkey->creator, NULL);
186
		ret = dns_name_dup(creator, mctx, tkey->creator);
187
188
189
190
		if (ret != ISC_R_SUCCESS) {
			isc_mem_put(mctx, tkey->creator, sizeof(dns_name_t));
			goto cleanup_algorithm;
		}
Brian Wellington's avatar
Brian Wellington committed
191
	} else
Brian Wellington's avatar
Brian Wellington committed
192
		tkey->creator = NULL;
193

Brian Wellington's avatar
Brian Wellington committed
194
	tkey->key = dstkey;
195
	tkey->ring = ring;
196
	tkey->refs = 0;
197

Brian Wellington's avatar
Brian Wellington committed
198
199
200
201
	if (ring != NULL) {
		RWLOCK(&ring->lock, isc_rwlocktype_write);
		ret = dns_rbt_addname(ring->keys, name, tkey);
		if (ret != ISC_R_SUCCESS) {
202
			RWUNLOCK(&ring->lock, isc_rwlocktype_write);
Brian Wellington's avatar
Brian Wellington committed
203
			goto cleanup_algorithm;
Brian Wellington's avatar
Brian Wellington committed
204
		}
Brian Wellington's avatar
Brian Wellington committed
205
206
		tkey->refs++;
		RWUNLOCK(&ring->lock, isc_rwlocktype_write);
207
208
	}

209
210
	if (key != NULL)
		tkey->refs++;
211
	tkey->generated = generated;
212
213
	tkey->inception = inception;
	tkey->expire = expire;
214
	tkey->mctx = mctx;
Brian Wellington's avatar
Brian Wellington committed
215
216
217
218
219
	ret = isc_mutex_init(&tkey->lock);
	if (ret != ISC_R_SUCCESS) {
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed: %s",
				 isc_result_totext(ret));
220
		return (ISC_R_UNEXPECTED);
Brian Wellington's avatar
Brian Wellington committed
221
	}
222

223
	tkey->magic = TSIG_MAGIC;
224

225
	if (dstkey != NULL && dst_key_size(dstkey) < 64) {
226
227
228
229
230
231
232
		char namestr[DNS_NAME_FORMATSIZE];
		dns_name_format(name, namestr, sizeof(namestr));
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
			      DNS_LOGMODULE_TSIG, ISC_LOG_INFO,
			      "the TSIG key for '%s' is too short to "
			      "be secure", namestr);
	}
233
234
235
	if (key != NULL)
		*key = tkey;

236
237
	return (ISC_R_SUCCESS);

Brian Wellington's avatar
Brian Wellington committed
238
239
240
241
242
243
244
 cleanup_algorithm:
	if (algname_is_allocated(tkey->algorithm)) {
		if (dns_name_dynamic(tkey->algorithm))
			dns_name_free(tkey->algorithm, mctx);
		isc_mem_put(mctx, tkey->algorithm, sizeof(dns_name_t));
	}
 cleanup_name:
245
	dns_name_free(&tkey->name, mctx);
Brian Wellington's avatar
Brian Wellington committed
246
 cleanup_key:
247
	isc_mem_put(mctx, tkey, sizeof(dns_tsigkey_t));
248
249
250
251

	return (ret);
}

Brian Wellington's avatar
Brian Wellington committed
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
isc_result_t
dns_tsigkey_create(dns_name_t *name, dns_name_t *algorithm,
		   unsigned char *secret, int length, isc_boolean_t generated,
		   dns_name_t *creator, isc_stdtime_t inception,
		   isc_stdtime_t expire, isc_mem_t *mctx,
		   dns_tsig_keyring_t *ring, dns_tsigkey_t **key)
{
	dst_key_t *dstkey = NULL;
	isc_result_t result;

	REQUIRE(length >= 0);
	if (length > 0)
		REQUIRE(secret != NULL);

	if (!dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME) && length > 0)
267
		return (DNS_R_BADALG);
Brian Wellington's avatar
Brian Wellington committed
268
269
270
271
272
273
274
275
276

	if (length > 0) {
		isc_buffer_t b;

		isc_buffer_init(&b, secret, length);
		isc_buffer_add(&b, length);
		result = dst_key_frombuffer(name, DST_ALG_HMACMD5,
					    DNS_KEYOWNER_ENTITY,
					    DNS_KEYPROTO_DNSSEC,
Brian Wellington's avatar
Brian Wellington committed
277
					    dns_rdataclass_in,
Brian Wellington's avatar
Brian Wellington committed
278
279
280
281
282
283
284
285
286
287
288
289
					    &b, mctx, &dstkey);
		if (result != ISC_R_SUCCESS)
			return (result);
	}
	result = dns_tsigkey_createfromkey(name, algorithm, dstkey,
					   generated, creator,
					   inception, expire, mctx, ring, key);
	if (result != ISC_R_SUCCESS && dstkey != NULL)
		dst_key_free(&dstkey);
	return (result);
}

290
291
292
293
294
void
dns_tsigkey_attach(dns_tsigkey_t *source, dns_tsigkey_t **targetp) {
	REQUIRE(VALID_TSIG_KEY(source));
	REQUIRE(targetp != NULL && *targetp == NULL);

295
	LOCK(&source->lock);
296
	source->refs++;
297
	UNLOCK(&source->lock);
298
299
300
	*targetp = source;
}

301
static void
302
303
tsigkey_free(dns_tsigkey_t *key) {
	REQUIRE(VALID_TSIG_KEY(key));
304

305
306
	key->magic = 0;
	dns_name_free(&key->name, key->mctx);
Brian Wellington's avatar
Brian Wellington committed
307
308
309
310
	if (algname_is_allocated(key->algorithm)) {
		dns_name_free(key->algorithm, key->mctx);
		isc_mem_put(key->mctx, key->algorithm, sizeof(dns_name_t));
	}
311
312
313
314
315
	if (key->key != NULL)
		dst_key_free(&key->key);
	if (key->creator != NULL) {
		dns_name_free(key->creator, key->mctx);
		isc_mem_put(key->mctx, key->creator, sizeof(dns_name_t));
316
	}
317
	DESTROYLOCK(&key->lock);
318
	isc_mem_put(key->mctx, key, sizeof(dns_tsigkey_t));
319
320
}

321
void
322
323
dns_tsigkey_detach(dns_tsigkey_t **keyp) {
	dns_tsigkey_t *key;
324
	isc_boolean_t should_free = ISC_FALSE;
325

326
327
328
329
	REQUIRE(keyp != NULL);
	REQUIRE(VALID_TSIG_KEY(*keyp));
	key = *keyp;
	*keyp = NULL;
330

331
332
333
	LOCK(&key->lock);
	key->refs--;
	if (key->refs == 0)
334
		should_free = ISC_TRUE;
335
	UNLOCK(&key->lock);
336
	if (should_free)
337
		tsigkey_free(key);
338
339
}

340
341
void
dns_tsigkey_setdeleted(dns_tsigkey_t *key) {
342
343
344
345
346
347
	REQUIRE(VALID_TSIG_KEY(key));
	REQUIRE(key->ring != NULL);

	RWLOCK(&key->ring->lock, isc_rwlocktype_write);
	(void)dns_rbt_deletename(key->ring->keys, &key->name, ISC_FALSE);
	RWUNLOCK(&key->ring->lock, isc_rwlocktype_write);
348
349
}

Brian Wellington's avatar
Brian Wellington committed
350
351
352
353
354
355
356
357
358
359
360
static void
buffer_putuint48(isc_buffer_t *b, isc_uint64_t val) {
	isc_uint16_t valhi;
	isc_uint32_t vallo;

	valhi = (isc_uint16_t)(val >> 32);
	vallo = (isc_uint32_t)(val & 0xFFFFFFFF);
	isc_buffer_putuint16(b, valhi);
	isc_buffer_putuint32(b, vallo);
}

361
362
isc_result_t
dns_tsig_sign(dns_message_t *msg) {
363
	dns_tsigkey_t *key;
364
	dns_rdata_any_tsig_t tsig, querytsig;
365
	unsigned char data[128];
Michael Graff's avatar
Michael Graff committed
366
	isc_buffer_t databuf, sigbuf;
367
	isc_buffer_t *dynbuf;
368
369
370
371
	dns_name_t *owner;
	dns_rdata_t *rdata;
	dns_rdatalist_t *datalist;
	dns_rdataset_t *dataset;
372
	isc_region_t r;
373
	isc_stdtime_t now;
374
	isc_mem_t *mctx;
Brian Wellington's avatar
Brian Wellington committed
375
	dst_context_t *ctx = NULL;
376
	isc_result_t ret;
Brian Wellington's avatar
Brian Wellington committed
377
	unsigned char badtimedata[BADTIMELEN];
Brian Wellington's avatar
Brian Wellington committed
378
	unsigned int sigsize = 0;
379
380

	REQUIRE(msg != NULL);
381
	REQUIRE(VALID_TSIG_KEY(dns_message_gettsigkey(msg)));
Brian Wellington's avatar
Brian Wellington committed
382

383
384
385
	/*
	 * If this is a response, there should be a query tsig.
	 */
386
	if (is_response(msg) && msg->querytsig == NULL)
Brian Wellington's avatar
Brian Wellington committed
387
		return (DNS_R_EXPECTEDTSIG);
388

389
390
	dynbuf = NULL;

391
	mctx = msg->mctx;
392
	key = dns_message_gettsigkey(msg);
393

394
395
396
397
398
	tsig.mctx = mctx;
	tsig.common.rdclass = dns_rdataclass_any;
	tsig.common.rdtype = dns_rdatatype_tsig;
	ISC_LINK_INIT(&tsig.common, link);
	dns_name_init(&tsig.algorithm, NULL);
Brian Wellington's avatar
Brian Wellington committed
399
	dns_name_clone(key->algorithm, &tsig.algorithm);
400

401
	isc_stdtime_get(&now);
402
403
	tsig.timesigned = now;
	tsig.fudge = DNS_TSIG_FUDGE;
404

405
	tsig.originalid = msg->id;
406

407
	isc_buffer_init(&databuf, data, sizeof(data));
408

409
	if (is_response(msg))
410
		tsig.error = msg->querytsigstatus;
411
	else
412
		tsig.error = dns_rcode_noerror;
413

414
415
416
	if (tsig.error != dns_tsigerror_badtime) {
		tsig.otherlen = 0;
		tsig.other = NULL;
Brian Wellington's avatar
Brian Wellington committed
417
	} else {
418
		isc_buffer_t otherbuf;
419

Brian Wellington's avatar
Brian Wellington committed
420
421
422
423
		tsig.otherlen = BADTIMELEN;
		tsig.other = badtimedata;
		isc_buffer_init(&otherbuf, tsig.other, tsig.otherlen);
		buffer_putuint48(&otherbuf, tsig.timesigned);
424
	}
Brian Wellington's avatar
Brian Wellington committed
425
426

	if (key->key != NULL && tsig.error != dns_tsigerror_badsig) {
427
428
429
		unsigned char header[DNS_MESSAGE_HEADERLEN];
		isc_buffer_t headerbuf;

Brian Wellington's avatar
Brian Wellington committed
430
		ret = dst_context_create(key->key, mctx, &ctx);
431
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
432
			return (ret);
433

434
435
436
		/*
		 * If this is a response, digest the query signature.
		 */
437
		if (is_response(msg)) {
438
			dns_rdata_t querytsigrdata = DNS_RDATA_INIT;
439

440
			ret = dns_rdataset_first(msg->querytsig);
441
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
442
				goto cleanup_context;
443
			dns_rdataset_current(msg->querytsig, &querytsigrdata);
444
445
446
			ret = dns_rdata_tostruct(&querytsigrdata, &querytsig,
						 NULL);
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
447
				goto cleanup_context;
448
449
450
451
452
			isc_buffer_putuint16(&databuf, querytsig.siglen);
			if (isc_buffer_availablelength(&databuf) <
			    querytsig.siglen)
			{
				ret = ISC_R_NOSPACE;
Brian Wellington's avatar
Brian Wellington committed
453
				goto cleanup_context;
454
455
456
			}
			isc_buffer_putmem(&databuf, querytsig.signature,
					  querytsig.siglen);
457
			isc_buffer_usedregion(&databuf, &r);
Brian Wellington's avatar
Brian Wellington committed
458
			ret = dst_context_adddata(ctx, &r);
459
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
460
				goto cleanup_context;
461
462
		}

463
464
465
		/*
		 * Digest the header.
		 */
466
		isc_buffer_init(&headerbuf, header, sizeof(header));
467
		dns_message_renderheader(msg, &headerbuf);
468
		isc_buffer_usedregion(&headerbuf, &r);
Brian Wellington's avatar
Brian Wellington committed
469
		ret = dst_context_adddata(ctx, &r);
470
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
471
			goto cleanup_context;
Brian Wellington's avatar
Brian Wellington committed
472

473
474
475
		/*
		 * Digest the remainder of the message.
		 */
476
		isc_buffer_usedregion(msg->buffer, &r);
477
		isc_region_consume(&r, DNS_MESSAGE_HEADERLEN);
Brian Wellington's avatar
Brian Wellington committed
478
		ret = dst_context_adddata(ctx, &r);
479
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
480
			goto cleanup_context;
481

Brian Wellington's avatar
Brian Wellington committed
482
		if (msg->tcp_continuation == 0) {
483
484
485
			/*
			 * Digest the name, class, ttl, alg.
			 */
Brian Wellington's avatar
Brian Wellington committed
486
			dns_name_toregion(&key->name, &r);
Brian Wellington's avatar
Brian Wellington committed
487
			ret = dst_context_adddata(ctx, &r);
Brian Wellington's avatar
Brian Wellington committed
488
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
489
				goto cleanup_context;
490

Brian Wellington's avatar
Brian Wellington committed
491
492
493
			isc_buffer_clear(&databuf);
			isc_buffer_putuint16(&databuf, dns_rdataclass_any);
			isc_buffer_putuint32(&databuf, 0); /* ttl */
494
			isc_buffer_usedregion(&databuf, &r);
Brian Wellington's avatar
Brian Wellington committed
495
			ret = dst_context_adddata(ctx, &r);
Brian Wellington's avatar
Brian Wellington committed
496
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
497
				goto cleanup_context;
Brian Wellington's avatar
Brian Wellington committed
498

499
			dns_name_toregion(&tsig.algorithm, &r);
Brian Wellington's avatar
Brian Wellington committed
500
			ret = dst_context_adddata(ctx, &r);
Brian Wellington's avatar
Brian Wellington committed
501
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
502
				goto cleanup_context;
503

Brian Wellington's avatar
Brian Wellington committed
504
505
		}
		/* Digest the timesigned and fudge */
506
		isc_buffer_clear(&databuf);
507
508
509
		if (tsig.error == dns_tsigerror_badtime)
			tsig.timesigned = querytsig.timesigned;
		buffer_putuint48(&databuf, tsig.timesigned);
510
		isc_buffer_putuint16(&databuf, tsig.fudge);
511
		isc_buffer_usedregion(&databuf, &r);
Brian Wellington's avatar
Brian Wellington committed
512
		ret = dst_context_adddata(ctx, &r);
513
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
514
			goto cleanup_context;
515

Brian Wellington's avatar
Brian Wellington committed
516
		if (msg->tcp_continuation == 0) {
517
518
519
			/*
			 * Digest the error and other data length.
			 */
Brian Wellington's avatar
Brian Wellington committed
520
			isc_buffer_clear(&databuf);
521
522
			isc_buffer_putuint16(&databuf, tsig.error);
			isc_buffer_putuint16(&databuf, tsig.otherlen);
Brian Wellington's avatar
Brian Wellington committed
523

524
			isc_buffer_usedregion(&databuf, &r);
Brian Wellington's avatar
Brian Wellington committed
525
			ret = dst_context_adddata(ctx, &r);
526
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
527
				goto cleanup_context;
Brian Wellington's avatar
Brian Wellington committed
528

529
530
531
			/*
			 * Digest the error and other data.
			 */
532
533
534
			if (tsig.otherlen > 0) {
				r.length = tsig.otherlen;
				r.base = tsig.other;
Brian Wellington's avatar
Brian Wellington committed
535
				ret = dst_context_adddata(ctx, &r);
Brian Wellington's avatar
Brian Wellington committed
536
				if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
537
					goto cleanup_context;
Brian Wellington's avatar
Brian Wellington committed
538
			}
539
540
		}

Brian Wellington's avatar
Brian Wellington committed
541
		ret = dst_key_sigsize(key->key, &sigsize);
Brian Wellington's avatar
Brian Wellington committed
542
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
543
			goto cleanup_context;
Brian Wellington's avatar
Brian Wellington committed
544
		tsig.signature = (unsigned char *) isc_mem_get(mctx, sigsize);
545
		if (tsig.signature == NULL) {
546
			ret = ISC_R_NOMEMORY;
Brian Wellington's avatar
Brian Wellington committed
547
			goto cleanup_context;
548
549
		}

Brian Wellington's avatar
Brian Wellington committed
550
		isc_buffer_init(&sigbuf, tsig.signature, sigsize);
Brian Wellington's avatar
Brian Wellington committed
551
		ret = dst_context_sign(ctx, &sigbuf);
552
553
		if (ret != ISC_R_SUCCESS)
			goto cleanup_signature;
Brian Wellington's avatar
Brian Wellington committed
554
		dst_context_destroy(&ctx);
Brian Wellington's avatar
Brian Wellington committed
555
		tsig.siglen = isc_buffer_usedlength(&sigbuf);
Brian Wellington's avatar
Brian Wellington committed
556
	} else {
557
558
		tsig.siglen = 0;
		tsig.signature = NULL;
559
560
561
562
563
564
	}

	rdata = NULL;
	ret = dns_message_gettemprdata(msg, &rdata);
	if (ret != ISC_R_SUCCESS)
		goto cleanup_signature;
565
	ret = isc_buffer_allocate(msg->mctx, &dynbuf, 512);
566
	if (ret != ISC_R_SUCCESS)
567
		goto cleanup_signature;
568
	ret = dns_rdata_fromstruct(rdata, dns_rdataclass_any,
569
				   dns_rdatatype_tsig, &tsig, dynbuf);
570
	if (ret != ISC_R_SUCCESS)
571
		goto cleanup_dynbuf;
572

573
574
	dns_message_takebuffer(msg, &dynbuf);

575
	if (tsig.signature != NULL) {
Brian Wellington's avatar
Brian Wellington committed
576
		isc_mem_put(mctx, tsig.signature, sigsize);
577
578
		tsig.signature = NULL;
	}
579
580
581
582

	owner = NULL;
	ret = dns_message_gettempname(msg, &owner);
	if (ret != ISC_R_SUCCESS)
583
		goto cleanup_dynbuf;
584
	dns_name_init(owner, NULL);
585
586
587
	ret = dns_name_dup(&key->name, msg->mctx, owner);
	if (ret != ISC_R_SUCCESS)
		goto cleanup_owner;
588
589
590
591

	datalist = NULL;
	ret = dns_message_gettemprdatalist(msg, &datalist);
	if (ret != ISC_R_SUCCESS)
592
		goto cleanup_dynbuf;
593
594
	datalist->rdclass = dns_rdataclass_any;
	datalist->type = dns_rdatatype_tsig;
595
	datalist->covers = 0;
596
597
598
599
600
601
	datalist->ttl = 0;
	ISC_LIST_INIT(datalist->rdata);
	ISC_LIST_APPEND(datalist->rdata, rdata, link);
	dataset = NULL;
	ret = dns_message_gettemprdataset(msg, &dataset);
	if (ret != ISC_R_SUCCESS)
602
		goto cleanup_owner;
603
604
	dns_rdataset_init(dataset);
	dns_rdatalist_tordataset(datalist, dataset);
605
	msg->tsig = dataset;
606
	msg->tsigname = owner;
607
608
609

	return (ISC_R_SUCCESS);

610
611
612
cleanup_owner:
	if (owner != NULL)
		dns_message_puttempname(msg, &owner);
613
614
615
cleanup_dynbuf:
	if (dynbuf != NULL)
		isc_buffer_free(&dynbuf);
616
cleanup_signature:
617
	if (tsig.signature != NULL)
618
		isc_mem_put(mctx, tsig.signature, sigsize);
Brian Wellington's avatar
Brian Wellington committed
619
620
621
cleanup_context:
	if (ctx != NULL)
		dst_context_destroy(&ctx);
622
623
624
625
	return (ret);
}

isc_result_t
Brian Wellington's avatar
Brian Wellington committed
626
dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg,
627
		dns_tsig_keyring_t *ring1, dns_tsig_keyring_t *ring2)
Brian Wellington's avatar
Brian Wellington committed
628
{
629
	dns_rdata_any_tsig_t tsig, querytsig;
630
631
632
633
	isc_region_t r, source_r, header_r, sig_r;
	isc_buffer_t databuf;
	unsigned char data[32];
	dns_name_t *keyname;
634
	dns_rdata_t rdata = DNS_RDATA_INIT;
635
	isc_stdtime_t now;
636
	isc_result_t ret;
637
	dns_tsigkey_t *tsigkey;
638
639
	dst_key_t *key = NULL;
	unsigned char header[DNS_MESSAGE_HEADERLEN];
Brian Wellington's avatar
Brian Wellington committed
640
	dst_context_t *ctx = NULL;
641
642
643
644
	isc_mem_t *mctx;
	isc_uint16_t addcount, id;

	REQUIRE(source != NULL);
645
	REQUIRE(DNS_MESSAGE_VALID(msg));
646
647
	tsigkey = dns_message_gettsigkey(msg);
	REQUIRE(tsigkey == NULL || VALID_TSIG_KEY(tsigkey));
Brian Wellington's avatar
Brian Wellington committed
648

649
650
	msg->verify_attempted = 1;

Brian Wellington's avatar
Brian Wellington committed
651
	if (msg->tcp_continuation)
652
		return (tsig_verify_tcp(source, msg));
Brian Wellington's avatar
Brian Wellington committed
653

654
655
656
	/*
	 * There should be a TSIG record...
	 */
657
	if (msg->tsig == NULL)
Brian Wellington's avatar
Brian Wellington committed
658
659
660
661
662
663
664
		return (DNS_R_EXPECTEDTSIG);

	/*
	 * If this is a response and there's no key or query TSIG, there
	 * shouldn't be one on the response.
	 */
	if (is_response(msg) &&
665
	    (tsigkey == NULL || msg->querytsig == NULL))
Brian Wellington's avatar
Brian Wellington committed
666
		return (DNS_R_UNEXPECTEDTSIG);
667
668
669
670
671
672
673
674

	mctx = msg->mctx;

	/*
	 * If we're here, we know the message is well formed and contains a
	 * TSIG record.
	 */

675
	keyname = msg->tsigname;
676
	ret = dns_rdataset_first(msg->tsig);
677
678
	if (ret != ISC_R_SUCCESS)
		return (ret);
679
	dns_rdataset_current(msg->tsig, &rdata);
680
	ret = dns_rdata_tostruct(&rdata, &tsig, NULL);
681
	if (ret != ISC_R_SUCCESS)
682
		return (ret);
683
	dns_rdata_reset(&rdata);
684
	if (is_response(msg)) {
685
		ret = dns_rdataset_first(msg->querytsig);
686
687
		if (ret != ISC_R_SUCCESS)
			return (ret);
688
		dns_rdataset_current(msg->querytsig, &rdata);
689
690
691
692
		ret = dns_rdata_tostruct(&rdata, &querytsig, NULL);
		if (ret != ISC_R_SUCCESS)
			return (ret);
	}
693

694
695
696
	/*
	 * Do the key name and algorithm match that of the query?
	 */
697
	if (is_response(msg) &&
698
	    (!dns_name_equal(keyname, &tsigkey->name) ||
699
	     !dns_name_equal(&tsig.algorithm, &querytsig.algorithm)))
700
701
	{
		msg->tsigstatus = dns_tsigerror_badkey;
702
703
		tsig_log(msg->tsigkey, 2,
			 "key name and algorithm do not match");
704
705
706
		return (DNS_R_TSIGVERIFYFAILURE);
	}

707
708
709
	/*
	 * Get the current time.
	 */
710
711
	isc_stdtime_get(&now);

712
713
714
	/*
	 * Find dns_tsigkey_t based on keyname.
	 */
715
	if (tsigkey == NULL) {
Brian Wellington's avatar
Brian Wellington committed
716
		ret = ISC_R_NOTFOUND;
717
		if (ring1 != NULL)
Brian Wellington's avatar
Brian Wellington committed
718
			ret = dns_tsigkey_find(&tsigkey, keyname,
719
					       &tsig.algorithm, ring1);
720
		if (ret == ISC_R_NOTFOUND && ring2 != NULL)
Brian Wellington's avatar
Brian Wellington committed
721
			ret = dns_tsigkey_find(&tsigkey, keyname,
722
					       &tsig.algorithm, ring2);
Brian Wellington's avatar
Brian Wellington committed
723
724
		if (ret != ISC_R_SUCCESS) {
			msg->tsigstatus = dns_tsigerror_badkey;
725
			ret = dns_tsigkey_create(keyname, &tsig.algorithm,
Brian Wellington's avatar
Brian Wellington committed
726
						 NULL, 0, ISC_FALSE, NULL,
727
						 now, now,
728
						 mctx, NULL, &msg->tsigkey);
Brian Wellington's avatar
Brian Wellington committed
729
			if (ret != ISC_R_SUCCESS)
730
				return (ret);
731
			tsig_log(msg->tsigkey, 2, "unknown key");
Brian Wellington's avatar
Brian Wellington committed
732
733
			return (DNS_R_TSIGVERIFYFAILURE);
		}
Brian Wellington's avatar
Brian Wellington committed
734
		msg->tsigkey = tsigkey;
735
736
737
738
	}

	key = tsigkey->key;

739
740
741
	/*
	 * Is the time ok?
	 */
742
	if (abs(now - tsig.timesigned) > tsig.fudge) {
743
		msg->tsigstatus = dns_tsigerror_badtime;
744
745
746
747
748
749
		if (now > tsig.timesigned + tsig.fudge)
			tsig_log(msg->tsigkey, 2,
				 "signature has expired");
		else
			tsig_log(msg->tsigkey, 2,
				 "signature is in the future");
750
751
752
		return (DNS_R_TSIGVERIFYFAILURE);
	}

753
754
755
	if (tsig.siglen > 0) {
		sig_r.base = tsig.signature;
		sig_r.length = tsig.siglen;
756

Brian Wellington's avatar
Brian Wellington committed
757
		ret = dst_context_create(key, mctx, &ctx);
758
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
759
			return (ret);
760
761

		if (is_response(msg)) {
762
			isc_buffer_init(&databuf, data, sizeof(data));
763
			isc_buffer_putuint16(&databuf, querytsig.siglen);
764
			isc_buffer_usedregion(&databuf, &r);
Brian Wellington's avatar
Brian Wellington committed
765
			ret = dst_context_adddata(ctx, &r);
766
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
767
				goto cleanup_context;
768
769
770
			if (querytsig.siglen > 0) {
				r.length = querytsig.siglen;
				r.base = querytsig.signature;
Brian Wellington's avatar
Brian Wellington committed
771
				ret = dst_context_adddata(ctx, &r);
772
				if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
773
					goto cleanup_context;
774
775
776
			}
		}

777
778
779
		/*
		 * Extract the header.
		 */
780
		isc_buffer_usedregion(source, &r);
Brian Wellington's avatar
Brian Wellington committed
781
782
783
		memcpy(header, r.base, DNS_MESSAGE_HEADERLEN);
		isc_region_consume(&r, DNS_MESSAGE_HEADERLEN);

784
785
786
		/*
		 * Decrement the additional field counter.
		 */
787
788
789
790
		memcpy(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2);
		addcount = htons(ntohs(addcount) - 1);
		memcpy(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2);

791
792
793
		/*
		 * Put in the original id.
		 */
794
		id = htons(tsig.originalid);
795
796
		memcpy(&header[0], &id, 2);

797
798
799
		/*
		 * Digest the modified header.
		 */
800
801
		header_r.base = (unsigned char *) header;
		header_r.length = DNS_MESSAGE_HEADERLEN;
Brian Wellington's avatar
Brian Wellington committed
802
		ret = dst_context_adddata(ctx, &header_r);
803
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
804
			goto cleanup_context;
805

806
807
808
		/*
		 * Digest all non-TSIG records.
		 */
809
		isc_buffer_usedregion(source, &source_r);
810
		r.base = source_r.base + DNS_MESSAGE_HEADERLEN;
811
		r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN;
Brian Wellington's avatar
Brian Wellington committed
812
		ret = dst_context_adddata(ctx, &r);
813
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
814
			goto cleanup_context;
815

816
817
818
		/*
		 * Digest the key name.
		 */
819
		dns_name_toregion(&tsigkey->name, &r);
Brian Wellington's avatar
Brian Wellington committed
820
		ret = dst_context_adddata(ctx, &r);
821
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
822
			goto cleanup_context;
823

824
		isc_buffer_init(&databuf, data, sizeof(data));
825
		isc_buffer_putuint16(&databuf, tsig.common.rdclass);
826
		isc_buffer_putuint32(&databuf, msg->tsig->ttl);
827
		isc_buffer_usedregion(&databuf, &r);
Brian Wellington's avatar
Brian Wellington committed
828
		ret = dst_context_adddata(ctx, &r);
829
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
830
			goto cleanup_context;
831

832
833
834
		/*
		 * Digest the key algorithm.
		 */
Brian Wellington's avatar
Brian Wellington committed
835
		dns_name_toregion(tsigkey->algorithm, &r);
Brian Wellington's avatar
Brian Wellington committed
836
		ret = dst_context_adddata(ctx, &r);
837
		if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
838
			goto cleanup_context;
839
840

		isc_buffer_clear(&databuf);
Brian Wellington's avatar
Brian Wellington committed
841
		buffer_putuint48(&databuf, tsig.timesigned);
842
843
844
		isc_buffer_putuint16(&databuf, tsig.fudge);
		isc_buffer_putuint16(&databuf, tsig.error);
		isc_buffer_putuint16(&databuf, tsig.otherlen);
845
		isc_buffer_usedregion(&databuf, &r);
Brian Wellington's avatar
Brian Wellington committed
846
847
848
		ret = dst_context_adddata(ctx, &r);
		if (ret != ISC_R_SUCCESS)
			goto cleanup_context;
849

850
851
852
		if (tsig.otherlen > 0) {
			r.base = tsig.other;
			r.length = tsig.otherlen;
Brian Wellington's avatar
Brian Wellington committed
853
			ret = dst_context_adddata(ctx, &r);
854
			if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
855
				goto cleanup_context;
856
857
		}

Brian Wellington's avatar
Brian Wellington committed
858
859
		ret = dst_context_verify(ctx, &sig_r);
		if (ret == DST_R_VERIFYFAILURE) {
860
			msg->tsigstatus = dns_tsigerror_badsig;
861
			ret = DNS_R_TSIGVERIFYFAILURE;
862
863
			tsig_log(msg->tsigkey, 2,
				 "signature failed to verify");
864
			goto cleanup_context;
Brian Wellington's avatar
Brian Wellington committed
865
		} else if (ret != ISC_R_SUCCESS)
Brian Wellington's avatar
Brian Wellington committed
866
			goto cleanup_context;
867
868

		dst_context_destroy(&ctx);
Brian Wellington's avatar
Brian Wellington committed
869
870
	} else if (tsig.error != dns_tsigerror_badsig &&
		   tsig.error != dns_tsigerror_badkey)
871
872
	{
		msg->tsigstatus = dns_tsigerror_badsig;
873
		tsig_log(msg->tsigkey, 2, "signature was empty");
874
875
876
877
878
		return (DNS_R_TSIGVERIFYFAILURE);
	}

	msg->tsigstatus = dns_rcode_noerror;

879
880
	if (tsig.error != dns_rcode_noerror)
		return (DNS_R_TSIGERRORSET);
881

882
883
	msg->verified_sig = 1;

884
885
	return (ISC_R_SUCCESS);

Brian Wellington's avatar
Brian Wellington committed
886
887
888
cleanup_context:
	if (ctx != NULL)
		dst_context_destroy(&ctx);
889

890
891
892
	return (ret);
}

Brian Wellington's avatar
Brian Wellington committed
893
static isc_result_t
894
tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg) {
895
	dns_rdata_any_tsig_t tsig, querytsig;