diff options
Diffstat (limited to 'contrib/bind9/lib/dns/tkey.c')
-rw-r--r-- | contrib/bind9/lib/dns/tkey.c | 1240 |
1 files changed, 1240 insertions, 0 deletions
diff --git a/contrib/bind9/lib/dns/tkey.c b/contrib/bind9/lib/dns/tkey.c new file mode 100644 index 000000000000..dc49a337ad3d --- /dev/null +++ b/contrib/bind9/lib/dns/tkey.c @@ -0,0 +1,1240 @@ +/* + * Copyright (C) 2004 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2001, 2003 Internet Software Consortium. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC 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. + */ + +/* + * $Id: tkey.c,v 1.71.2.1.10.5 2004/06/11 00:30:54 marka Exp $ + */ + +#include <config.h> + +#include <isc/buffer.h> +#include <isc/entropy.h> +#include <isc/md5.h> +#include <isc/mem.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/dnssec.h> +#include <dns/fixedname.h> +#include <dns/keyvalues.h> +#include <dns/log.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/result.h> +#include <dns/tkey.h> +#include <dns/tsig.h> + +#include <dst/dst.h> +#include <dst/gssapi.h> + +#define TKEY_RANDOM_AMOUNT 16 + +#define RETERR(x) do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +static void +tkey_log(const char *fmt, ...) ISC_FORMAT_PRINTF(1, 2); + +static void +tkey_log(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_REQUEST, ISC_LOG_DEBUG(4), fmt, ap); + va_end(ap); +} + +isc_result_t +dns_tkeyctx_create(isc_mem_t *mctx, isc_entropy_t *ectx, dns_tkeyctx_t **tctxp) +{ + dns_tkeyctx_t *tctx; + + REQUIRE(mctx != NULL); + REQUIRE(ectx != NULL); + REQUIRE(tctxp != NULL && *tctxp == NULL); + + tctx = isc_mem_get(mctx, sizeof(dns_tkeyctx_t)); + if (tctx == NULL) + return (ISC_R_NOMEMORY); + tctx->mctx = NULL; + isc_mem_attach(mctx, &tctx->mctx); + tctx->ectx = NULL; + isc_entropy_attach(ectx, &tctx->ectx); + tctx->dhkey = NULL; + tctx->domain = NULL; + tctx->gsscred = NULL; + + *tctxp = tctx; + return (ISC_R_SUCCESS); +} + +void +dns_tkeyctx_destroy(dns_tkeyctx_t **tctxp) { + isc_mem_t *mctx; + dns_tkeyctx_t *tctx; + + REQUIRE(tctxp != NULL && *tctxp != NULL); + + tctx = *tctxp; + mctx = tctx->mctx; + + if (tctx->dhkey != NULL) + dst_key_free(&tctx->dhkey); + if (tctx->domain != NULL) { + if (dns_name_dynamic(tctx->domain)) + dns_name_free(tctx->domain, mctx); + isc_mem_put(mctx, tctx->domain, sizeof(dns_name_t)); + } + isc_entropy_detach(&tctx->ectx); + isc_mem_put(mctx, tctx, sizeof(dns_tkeyctx_t)); + isc_mem_detach(&mctx); + *tctxp = NULL; +} + +static isc_result_t +add_rdata_to_list(dns_message_t *msg, dns_name_t *name, dns_rdata_t *rdata, + isc_uint32_t ttl, dns_namelist_t *namelist) +{ + isc_result_t result; + isc_region_t r, newr; + dns_rdata_t *newrdata = NULL; + dns_name_t *newname = NULL; + dns_rdatalist_t *newlist = NULL; + dns_rdataset_t *newset = NULL; + isc_buffer_t *tmprdatabuf = NULL; + + RETERR(dns_message_gettemprdata(msg, &newrdata)); + + dns_rdata_toregion(rdata, &r); + RETERR(isc_buffer_allocate(msg->mctx, &tmprdatabuf, r.length)); + isc_buffer_availableregion(tmprdatabuf, &newr); + memcpy(newr.base, r.base, r.length); + dns_rdata_fromregion(newrdata, rdata->rdclass, rdata->type, &newr); + dns_message_takebuffer(msg, &tmprdatabuf); + + RETERR(dns_message_gettempname(msg, &newname)); + dns_name_init(newname, NULL); + RETERR(dns_name_dup(name, msg->mctx, newname)); + + RETERR(dns_message_gettemprdatalist(msg, &newlist)); + newlist->rdclass = newrdata->rdclass; + newlist->type = newrdata->type; + newlist->covers = 0; + newlist->ttl = ttl; + ISC_LIST_INIT(newlist->rdata); + ISC_LIST_APPEND(newlist->rdata, newrdata, link); + + RETERR(dns_message_gettemprdataset(msg, &newset)); + dns_rdataset_init(newset); + RETERR(dns_rdatalist_tordataset(newlist, newset)); + + ISC_LIST_INIT(newname->list); + ISC_LIST_APPEND(newname->list, newset, link); + + ISC_LIST_APPEND(*namelist, newname, link); + + return (ISC_R_SUCCESS); + + failure: + if (newrdata != NULL) { + if (ISC_LINK_LINKED(newrdata, link)) + ISC_LIST_UNLINK(newlist->rdata, newrdata, link); + dns_message_puttemprdata(msg, &newrdata); + } + if (newname != NULL) + dns_message_puttempname(msg, &newname); + if (newset != NULL) { + dns_rdataset_disassociate(newset); + dns_message_puttemprdataset(msg, &newset); + } + if (newlist != NULL) + dns_message_puttemprdatalist(msg, &newlist); + return (result); +} + +static void +free_namelist(dns_message_t *msg, dns_namelist_t *namelist) { + dns_name_t *name; + dns_rdataset_t *set; + + while (!ISC_LIST_EMPTY(*namelist)) { + name = ISC_LIST_HEAD(*namelist); + ISC_LIST_UNLINK(*namelist, name, link); + while (!ISC_LIST_EMPTY(name->list)) { + set = ISC_LIST_HEAD(name->list); + ISC_LIST_UNLINK(name->list, set, link); + dns_message_puttemprdataset(msg, &set); + } + dns_message_puttempname(msg, &name); + } +} + +static isc_result_t +compute_secret(isc_buffer_t *shared, isc_region_t *queryrandomness, + isc_region_t *serverrandomness, isc_buffer_t *secret) +{ + isc_md5_t md5ctx; + isc_region_t r, r2; + unsigned char digests[32]; + unsigned int i; + + isc_buffer_usedregion(shared, &r); + + /* + * MD5 ( query data | DH value ). + */ + isc_md5_init(&md5ctx); + isc_md5_update(&md5ctx, queryrandomness->base, + queryrandomness->length); + isc_md5_update(&md5ctx, r.base, r.length); + isc_md5_final(&md5ctx, digests); + + /* + * MD5 ( server data | DH value ). + */ + isc_md5_init(&md5ctx); + isc_md5_update(&md5ctx, serverrandomness->base, + serverrandomness->length); + isc_md5_update(&md5ctx, r.base, r.length); + isc_md5_final(&md5ctx, &digests[ISC_MD5_DIGESTLENGTH]); + + /* + * XOR ( DH value, MD5-1 | MD5-2). + */ + isc_buffer_availableregion(secret, &r); + isc_buffer_usedregion(shared, &r2); + if (r.length < sizeof(digests) || r.length < r2.length) + return (ISC_R_NOSPACE); + if (r2.length > sizeof(digests)) { + memcpy(r.base, r2.base, r2.length); + for (i = 0; i < sizeof(digests); i++) + r.base[i] ^= digests[i]; + isc_buffer_add(secret, r2.length); + } else { + memcpy(r.base, digests, sizeof(digests)); + for (i = 0; i < r2.length; i++) + r.base[i] ^= r2.base[i]; + isc_buffer_add(secret, sizeof(digests)); + } + return (ISC_R_SUCCESS); + +} + +static isc_result_t +process_dhtkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name, + dns_rdata_tkey_t *tkeyin, dns_tkeyctx_t *tctx, + dns_rdata_tkey_t *tkeyout, + dns_tsig_keyring_t *ring, dns_namelist_t *namelist) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_name_t *keyname, ourname; + dns_rdataset_t *keyset = NULL; + dns_rdata_t keyrdata = DNS_RDATA_INIT, ourkeyrdata = DNS_RDATA_INIT; + isc_boolean_t found_key = ISC_FALSE, found_incompatible = ISC_FALSE; + dst_key_t *pubkey = NULL; + isc_buffer_t ourkeybuf, *shared = NULL; + isc_region_t r, r2, ourkeyr; + unsigned char keydata[DST_KEY_MAXSIZE]; + unsigned int sharedsize; + isc_buffer_t secret; + unsigned char *randomdata = NULL, secretdata[256]; + dns_ttl_t ttl = 0; + + if (tctx->dhkey == NULL) { + tkey_log("process_dhtkey: tkey-dhkey not defined"); + tkeyout->error = dns_tsigerror_badalg; + return (DNS_R_REFUSED); + } + + if (!dns_name_equal(&tkeyin->algorithm, DNS_TSIG_HMACMD5_NAME)) { + tkey_log("process_dhtkey: algorithms other than " + "hmac-md5 are not supported"); + tkeyout->error = dns_tsigerror_badalg; + return (ISC_R_SUCCESS); + } + + /* + * Look for a DH KEY record that will work with ours. + */ + for (result = dns_message_firstname(msg, DNS_SECTION_ADDITIONAL); + result == ISC_R_SUCCESS && !found_key; + result = dns_message_nextname(msg, DNS_SECTION_ADDITIONAL)) + { + keyname = NULL; + dns_message_currentname(msg, DNS_SECTION_ADDITIONAL, &keyname); + keyset = NULL; + result = dns_message_findtype(keyname, dns_rdatatype_key, 0, + &keyset); + if (result != ISC_R_SUCCESS) + continue; + + for (result = dns_rdataset_first(keyset); + result == ISC_R_SUCCESS && !found_key; + result = dns_rdataset_next(keyset)) + { + dns_rdataset_current(keyset, &keyrdata); + pubkey = NULL; + result = dns_dnssec_keyfromrdata(keyname, &keyrdata, + msg->mctx, &pubkey); + if (result != ISC_R_SUCCESS) { + dns_rdata_reset(&keyrdata); + continue; + } + if (dst_key_alg(pubkey) == DNS_KEYALG_DH) { + if (dst_key_paramcompare(pubkey, tctx->dhkey)) + { + found_key = ISC_TRUE; + ttl = keyset->ttl; + break; + } else + found_incompatible = ISC_TRUE; + } + dst_key_free(&pubkey); + dns_rdata_reset(&keyrdata); + } + } + + if (!found_key) { + if (found_incompatible) { + tkey_log("process_dhtkey: found an incompatible key"); + tkeyout->error = dns_tsigerror_badkey; + return (ISC_R_SUCCESS); + } else { + tkey_log("process_dhtkey: failed to find a key"); + return (DNS_R_FORMERR); + } + } + + RETERR(add_rdata_to_list(msg, keyname, &keyrdata, ttl, namelist)); + + isc_buffer_init(&ourkeybuf, keydata, sizeof(keydata)); + RETERR(dst_key_todns(tctx->dhkey, &ourkeybuf)); + isc_buffer_usedregion(&ourkeybuf, &ourkeyr); + dns_rdata_fromregion(&ourkeyrdata, dns_rdataclass_any, + dns_rdatatype_key, &ourkeyr); + + dns_name_init(&ourname, NULL); + dns_name_clone(dst_key_name(tctx->dhkey), &ourname); + + /* + * XXXBEW The TTL should be obtained from the database, if it exists. + */ + RETERR(add_rdata_to_list(msg, &ourname, &ourkeyrdata, 0, namelist)); + + RETERR(dst_key_secretsize(tctx->dhkey, &sharedsize)); + RETERR(isc_buffer_allocate(msg->mctx, &shared, sharedsize)); + + result = dst_key_computesecret(pubkey, tctx->dhkey, shared); + if (result != ISC_R_SUCCESS) { + tkey_log("process_dhtkey: failed to compute shared secret: %s", + isc_result_totext(result)); + goto failure; + } + dst_key_free(&pubkey); + + isc_buffer_init(&secret, secretdata, sizeof(secretdata)); + + randomdata = isc_mem_get(tctx->mctx, TKEY_RANDOM_AMOUNT); + if (randomdata == NULL) + goto failure; + + result = isc_entropy_getdata(tctx->ectx, randomdata, + TKEY_RANDOM_AMOUNT, NULL, 0); + if (result != ISC_R_SUCCESS) { + tkey_log("process_dhtkey: failed to obtain entropy: %s", + isc_result_totext(result)); + goto failure; + } + + r.base = randomdata; + r.length = TKEY_RANDOM_AMOUNT; + r2.base = tkeyin->key; + r2.length = tkeyin->keylen; + RETERR(compute_secret(shared, &r2, &r, &secret)); + isc_buffer_free(&shared); + + RETERR(dns_tsigkey_create(name, &tkeyin->algorithm, + isc_buffer_base(&secret), + isc_buffer_usedlength(&secret), + ISC_TRUE, signer, tkeyin->inception, + tkeyin->expire, msg->mctx, ring, NULL)); + + /* This key is good for a long time */ + tkeyout->inception = tkeyin->inception; + tkeyout->expire = tkeyin->expire; + + tkeyout->key = randomdata; + tkeyout->keylen = TKEY_RANDOM_AMOUNT; + + return (ISC_R_SUCCESS); + + failure: + if (!ISC_LIST_EMPTY(*namelist)) + free_namelist(msg, namelist); + if (shared != NULL) + isc_buffer_free(&shared); + if (pubkey != NULL) + dst_key_free(&pubkey); + if (randomdata == NULL) + isc_mem_put(tctx->mctx, randomdata, TKEY_RANDOM_AMOUNT); + return (result); +} + +static isc_result_t +process_gsstkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name, + dns_rdata_tkey_t *tkeyin, dns_tkeyctx_t *tctx, + dns_rdata_tkey_t *tkeyout, + dns_tsig_keyring_t *ring, dns_namelist_t *namelist) +{ + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *dstkey = NULL; + void *gssctx = NULL; + isc_stdtime_t now; + isc_region_t intoken; + unsigned char array[1024]; + isc_buffer_t outtoken; + + UNUSED(namelist); + + if (tctx->gsscred == NULL) + return (ISC_R_NOPERM); + + if (!dns_name_equal(&tkeyin->algorithm, DNS_TSIG_GSSAPI_NAME) && + !dns_name_equal(&tkeyin->algorithm, DNS_TSIG_GSSAPIMS_NAME)) { + tkeyout->error = dns_tsigerror_badalg; + return (ISC_R_SUCCESS); + } + + intoken.base = tkeyin->key; + intoken.length = tkeyin->keylen; + + isc_buffer_init(&outtoken, array, sizeof(array)); + RETERR(dst_gssapi_acceptctx(name, tctx->gsscred, &intoken, + &outtoken, &gssctx)); + + dstkey = NULL; + RETERR(dst_key_fromgssapi(name, gssctx, msg->mctx, &dstkey)); + + result = dns_tsigkey_createfromkey(name, &tkeyin->algorithm, + dstkey, ISC_TRUE, signer, + tkeyin->inception, tkeyin->expire, + msg->mctx, ring, NULL); + if (result != ISC_R_SUCCESS) + goto failure; + + if (result == ISC_R_NOTFOUND) { + tkeyout->error = dns_tsigerror_badalg; + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) + goto failure; + + /* This key is good for a long time */ + isc_stdtime_get(&now); + tkeyout->inception = tkeyin->inception; + tkeyout->expire = tkeyin->expire; + + tkeyout->key = isc_mem_get(msg->mctx, + isc_buffer_usedlength(&outtoken)); + if (tkeyout->key == NULL) { + result = ISC_R_NOMEMORY; + goto failure; + } + tkeyout->keylen = isc_buffer_usedlength(&outtoken); + memcpy(tkeyout->key, isc_buffer_base(&outtoken), tkeyout->keylen); + + return (ISC_R_SUCCESS); + + failure: + if (dstkey != NULL) + dst_key_free(&dstkey); + + return (result); +} + +static isc_result_t +process_deletetkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name, + dns_rdata_tkey_t *tkeyin, + dns_rdata_tkey_t *tkeyout, + dns_tsig_keyring_t *ring, + dns_namelist_t *namelist) +{ + isc_result_t result; + dns_tsigkey_t *tsigkey = NULL; + dns_name_t *identity; + + UNUSED(msg); + UNUSED(namelist); + + result = dns_tsigkey_find(&tsigkey, name, &tkeyin->algorithm, ring); + if (result != ISC_R_SUCCESS) { + tkeyout->error = dns_tsigerror_badname; + return (ISC_R_SUCCESS); + } + + /* + * Only allow a delete if the identity that created the key is the + * same as the identity that signed the message. + */ + identity = dns_tsigkey_identity(tsigkey); + if (identity == NULL || !dns_name_equal(identity, signer)) { + dns_tsigkey_detach(&tsigkey); + return (DNS_R_REFUSED); + } + + /* + * Set the key to be deleted when no references are left. If the key + * was not generated with TKEY and is in the config file, it may be + * reloaded later. + */ + dns_tsigkey_setdeleted(tsigkey); + + /* Release the reference */ + dns_tsigkey_detach(&tsigkey); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx, + dns_tsig_keyring_t *ring) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_tkey_t tkeyin, tkeyout; + isc_boolean_t freetkeyin = ISC_FALSE; + dns_name_t *qname, *name, *keyname, *signer, tsigner; + dns_fixedname_t fkeyname; + dns_rdataset_t *tkeyset; + dns_rdata_t rdata; + dns_namelist_t namelist; + char tkeyoutdata[512]; + isc_buffer_t tkeyoutbuf; + + REQUIRE(msg != NULL); + REQUIRE(tctx != NULL); + REQUIRE(ring != NULL); + + ISC_LIST_INIT(namelist); + + /* + * Interpret the question section. + */ + result = dns_message_firstname(msg, DNS_SECTION_QUESTION); + if (result != ISC_R_SUCCESS) + return (DNS_R_FORMERR); + + qname = NULL; + dns_message_currentname(msg, DNS_SECTION_QUESTION, &qname); + + /* + * Look for a TKEY record that matches the question. + */ + tkeyset = NULL; + name = NULL; + result = dns_message_findname(msg, DNS_SECTION_ADDITIONAL, qname, + dns_rdatatype_tkey, 0, &name, &tkeyset); + if (result != ISC_R_SUCCESS) { + /* + * Try the answer section, since that's where Win2000 + * puts it. + */ + if (dns_message_findname(msg, DNS_SECTION_ANSWER, qname, + dns_rdatatype_tkey, 0, &name, + &tkeyset) != ISC_R_SUCCESS) + { + result = DNS_R_FORMERR; + tkey_log("dns_tkey_processquery: couldn't find a TKEY " + "matching the question"); + goto failure; + } + } + result = dns_rdataset_first(tkeyset); + if (result != ISC_R_SUCCESS) { + result = DNS_R_FORMERR; + goto failure; + } + dns_rdata_init(&rdata); + dns_rdataset_current(tkeyset, &rdata); + + RETERR(dns_rdata_tostruct(&rdata, &tkeyin, NULL)); + freetkeyin = ISC_TRUE; + + if (tkeyin.error != dns_rcode_noerror) { + result = DNS_R_FORMERR; + goto failure; + } + + /* + * Before we go any farther, verify that the message was signed. + * GSSAPI TKEY doesn't require a signature, the rest do. + */ + dns_name_init(&tsigner, NULL); + result = dns_message_signer(msg, &tsigner); + if (result != ISC_R_SUCCESS) { + if (tkeyin.mode == DNS_TKEYMODE_GSSAPI && + result == ISC_R_NOTFOUND) + signer = NULL; + else { + tkey_log("dns_tkey_processquery: query was not " + "properly signed - rejecting"); + result = DNS_R_FORMERR; + goto failure; + } + } else + signer = &tsigner; + + tkeyout.common.rdclass = tkeyin.common.rdclass; + tkeyout.common.rdtype = tkeyin.common.rdtype; + ISC_LINK_INIT(&tkeyout.common, link); + tkeyout.mctx = msg->mctx; + + dns_name_init(&tkeyout.algorithm, NULL); + dns_name_clone(&tkeyin.algorithm, &tkeyout.algorithm); + + tkeyout.inception = tkeyout.expire = 0; + tkeyout.mode = tkeyin.mode; + tkeyout.error = 0; + tkeyout.keylen = tkeyout.otherlen = 0; + tkeyout.key = tkeyout.other = NULL; + + /* + * A delete operation must have a fully specified key name. If this + * is not a delete, we do the following: + * if (qname != ".") + * keyname = qname + defaultdomain + * else + * keyname = <random hex> + defaultdomain + */ + if (tkeyin.mode != DNS_TKEYMODE_DELETE) { + dns_tsigkey_t *tsigkey = NULL; + + if (tctx->domain == NULL) { + tkey_log("dns_tkey_processquery: tkey-domain not set"); + result = DNS_R_REFUSED; + goto failure; + } + + dns_fixedname_init(&fkeyname); + keyname = dns_fixedname_name(&fkeyname); + + if (!dns_name_equal(qname, dns_rootname)) { + unsigned int n = dns_name_countlabels(qname); + RUNTIME_CHECK(dns_name_copy(qname, keyname, NULL) + == ISC_R_SUCCESS); + dns_name_getlabelsequence(keyname, 0, n - 1, keyname); + } else { + static char hexdigits[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + unsigned char randomdata[16]; + char randomtext[32]; + isc_buffer_t b; + unsigned int i, j; + + result = isc_entropy_getdata(tctx->ectx, + randomdata, + sizeof(randomdata), + NULL, 0); + if (result != ISC_R_SUCCESS) + goto failure; + + for (i = 0, j = 0; i < sizeof(randomdata); i++) { + unsigned char val = randomdata[i]; + randomtext[j++] = hexdigits[val >> 4]; + randomtext[j++] = hexdigits[val & 0xF]; + } + isc_buffer_init(&b, randomtext, sizeof(randomtext)); + isc_buffer_add(&b, sizeof(randomtext)); + result = dns_name_fromtext(keyname, &b, NULL, + ISC_FALSE, NULL); + if (result != ISC_R_SUCCESS) + goto failure; + } + result = dns_name_concatenate(keyname, tctx->domain, + keyname, NULL); + if (result != ISC_R_SUCCESS) + goto failure; + + result = dns_tsigkey_find(&tsigkey, keyname, NULL, ring); + if (result == ISC_R_SUCCESS) { + tkeyout.error = dns_tsigerror_badname; + dns_tsigkey_detach(&tsigkey); + goto failure_with_tkey; + } else if (result != ISC_R_NOTFOUND) + goto failure; + } else + keyname = qname; + + switch (tkeyin.mode) { + case DNS_TKEYMODE_DIFFIEHELLMAN: + tkeyout.error = dns_rcode_noerror; + RETERR(process_dhtkey(msg, signer, keyname, &tkeyin, + tctx, &tkeyout, ring, + &namelist)); + break; + case DNS_TKEYMODE_GSSAPI: + tkeyout.error = dns_rcode_noerror; + RETERR(process_gsstkey(msg, signer, keyname, &tkeyin, + tctx, &tkeyout, ring, + &namelist)); + break; + case DNS_TKEYMODE_DELETE: + tkeyout.error = dns_rcode_noerror; + RETERR(process_deletetkey(msg, signer, keyname, + &tkeyin, &tkeyout, + ring, &namelist)); + break; + case DNS_TKEYMODE_SERVERASSIGNED: + case DNS_TKEYMODE_RESOLVERASSIGNED: + result = DNS_R_NOTIMP; + goto failure; + default: + tkeyout.error = dns_tsigerror_badmode; + } + + failure_with_tkey: + dns_rdata_init(&rdata); + isc_buffer_init(&tkeyoutbuf, tkeyoutdata, sizeof(tkeyoutdata)); + result = dns_rdata_fromstruct(&rdata, tkeyout.common.rdclass, + tkeyout.common.rdtype, &tkeyout, + &tkeyoutbuf); + + if (freetkeyin) { + dns_rdata_freestruct(&tkeyin); + freetkeyin = ISC_FALSE; + } + + if (tkeyout.key != NULL) + isc_mem_put(msg->mctx, tkeyout.key, tkeyout.keylen); + if (tkeyout.other != NULL) + isc_mem_put(msg->mctx, tkeyout.other, tkeyout.otherlen); + if (result != ISC_R_SUCCESS) + goto failure; + + RETERR(add_rdata_to_list(msg, keyname, &rdata, 0, &namelist)); + + RETERR(dns_message_reply(msg, ISC_TRUE)); + + name = ISC_LIST_HEAD(namelist); + while (name != NULL) { + dns_name_t *next = ISC_LIST_NEXT(name, link); + ISC_LIST_UNLINK(namelist, name, link); + dns_message_addname(msg, name, DNS_SECTION_ANSWER); + name = next; + } + + return (ISC_R_SUCCESS); + + failure: + if (freetkeyin) + dns_rdata_freestruct(&tkeyin); + if (!ISC_LIST_EMPTY(namelist)) + free_namelist(msg, &namelist); + return (result); +} + +static isc_result_t +buildquery(dns_message_t *msg, dns_name_t *name, + dns_rdata_tkey_t *tkey) +{ + dns_name_t *qname = NULL, *aname = NULL; + dns_rdataset_t *question = NULL, *tkeyset = NULL; + dns_rdatalist_t *tkeylist = NULL; + dns_rdata_t *rdata = NULL; + isc_buffer_t *dynbuf = NULL; + isc_result_t result; + + REQUIRE(msg != NULL); + REQUIRE(name != NULL); + REQUIRE(tkey != NULL); + + RETERR(dns_message_gettempname(msg, &qname)); + RETERR(dns_message_gettempname(msg, &aname)); + + RETERR(dns_message_gettemprdataset(msg, &question)); + dns_rdataset_init(question); + dns_rdataset_makequestion(question, dns_rdataclass_any, + dns_rdatatype_tkey); + + RETERR(isc_buffer_allocate(msg->mctx, &dynbuf, 512)); + RETERR(dns_message_gettemprdata(msg, &rdata)); + RETERR(dns_rdata_fromstruct(rdata, dns_rdataclass_any, + dns_rdatatype_tkey, tkey, dynbuf)); + dns_message_takebuffer(msg, &dynbuf); + + RETERR(dns_message_gettemprdatalist(msg, &tkeylist)); + tkeylist->rdclass = dns_rdataclass_any; + tkeylist->type = dns_rdatatype_tkey; + tkeylist->covers = 0; + tkeylist->ttl = 0; + ISC_LIST_INIT(tkeylist->rdata); + ISC_LIST_APPEND(tkeylist->rdata, rdata, link); + + RETERR(dns_message_gettemprdataset(msg, &tkeyset)); + dns_rdataset_init(tkeyset); + RETERR(dns_rdatalist_tordataset(tkeylist, tkeyset)); + + dns_name_init(qname, NULL); + dns_name_clone(name, qname); + + dns_name_init(aname, NULL); + dns_name_clone(name, aname); + + ISC_LIST_APPEND(qname->list, question, link); + ISC_LIST_APPEND(aname->list, tkeyset, link); + + dns_message_addname(msg, qname, DNS_SECTION_QUESTION); + dns_message_addname(msg, aname, DNS_SECTION_ADDITIONAL); + + return (ISC_R_SUCCESS); + + failure: + if (qname != NULL) + dns_message_puttempname(msg, &qname); + if (aname != NULL) + dns_message_puttempname(msg, &aname); + if (question != NULL) { + dns_rdataset_disassociate(question); + dns_message_puttemprdataset(msg, &question); + } + if (dynbuf != NULL) + isc_buffer_free(&dynbuf); + return (result); +} + +isc_result_t +dns_tkey_builddhquery(dns_message_t *msg, dst_key_t *key, dns_name_t *name, + dns_name_t *algorithm, isc_buffer_t *nonce, + isc_uint32_t lifetime) +{ + dns_rdata_tkey_t tkey; + dns_rdata_t *rdata = NULL; + isc_buffer_t *dynbuf = NULL; + isc_region_t r; + dns_name_t keyname; + dns_namelist_t namelist; + isc_result_t result; + isc_stdtime_t now; + + REQUIRE(msg != NULL); + REQUIRE(key != NULL); + REQUIRE(dst_key_alg(key) == DNS_KEYALG_DH); + REQUIRE(dst_key_isprivate(key)); + REQUIRE(name != NULL); + REQUIRE(algorithm != NULL); + + tkey.common.rdclass = dns_rdataclass_any; + tkey.common.rdtype = dns_rdatatype_tkey; + ISC_LINK_INIT(&tkey.common, link); + tkey.mctx = msg->mctx; + dns_name_init(&tkey.algorithm, NULL); + dns_name_clone(algorithm, &tkey.algorithm); + isc_stdtime_get(&now); + tkey.inception = now; + tkey.expire = now + lifetime; + tkey.mode = DNS_TKEYMODE_DIFFIEHELLMAN; + if (nonce != NULL) + isc_buffer_usedregion(nonce, &r); + else { + r.base = isc_mem_get(msg->mctx, 0); + r.length = 0; + } + tkey.error = 0; + tkey.key = r.base; + tkey.keylen = r.length; + tkey.other = NULL; + tkey.otherlen = 0; + + RETERR(buildquery(msg, name, &tkey)); + + if (nonce == NULL) + isc_mem_put(msg->mctx, r.base, 0); + + RETERR(dns_message_gettemprdata(msg, &rdata)); + RETERR(isc_buffer_allocate(msg->mctx, &dynbuf, 1024)); + RETERR(dst_key_todns(key, dynbuf)); + isc_buffer_usedregion(dynbuf, &r); + dns_rdata_fromregion(rdata, dns_rdataclass_any, + dns_rdatatype_key, &r); + dns_message_takebuffer(msg, &dynbuf); + + dns_name_init(&keyname, NULL); + dns_name_clone(dst_key_name(key), &keyname); + + ISC_LIST_INIT(namelist); + RETERR(add_rdata_to_list(msg, &keyname, rdata, 0, &namelist)); + dns_message_addname(msg, ISC_LIST_HEAD(namelist), + DNS_SECTION_ADDITIONAL); + + return (ISC_R_SUCCESS); + + failure: + + if (dynbuf != NULL) + isc_buffer_free(&dynbuf); + return (result); +} + +isc_result_t +dns_tkey_buildgssquery(dns_message_t *msg, dns_name_t *name, + dns_name_t *gname, void *cred, + isc_uint32_t lifetime, void **context) +{ + dns_rdata_tkey_t tkey; + isc_result_t result; + isc_stdtime_t now; + isc_buffer_t token; + unsigned char array[1024]; + + REQUIRE(msg != NULL); + REQUIRE(name != NULL); + REQUIRE(gname != NULL); + REQUIRE(context != NULL && *context == NULL); + + isc_buffer_init(&token, array, sizeof(array)); + result = dst_gssapi_initctx(gname, cred, NULL, &token, context); + if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) + return (result); + + tkey.common.rdclass = dns_rdataclass_any; + tkey.common.rdtype = dns_rdatatype_tkey; + ISC_LINK_INIT(&tkey.common, link); + tkey.mctx = NULL; + dns_name_init(&tkey.algorithm, NULL); + dns_name_clone(DNS_TSIG_GSSAPI_NAME, &tkey.algorithm); + isc_stdtime_get(&now); + tkey.inception = now; + tkey.expire = now + lifetime; + tkey.mode = DNS_TKEYMODE_GSSAPI; + tkey.error = 0; + tkey.key = isc_buffer_base(&token); + tkey.keylen = isc_buffer_usedlength(&token); + tkey.other = NULL; + tkey.otherlen = 0; + + RETERR(buildquery(msg, name, &tkey)); + + return (ISC_R_SUCCESS); + + failure: + return (result); +} + +isc_result_t +dns_tkey_builddeletequery(dns_message_t *msg, dns_tsigkey_t *key) { + dns_rdata_tkey_t tkey; + + REQUIRE(msg != NULL); + REQUIRE(key != NULL); + + tkey.common.rdclass = dns_rdataclass_any; + tkey.common.rdtype = dns_rdatatype_tkey; + ISC_LINK_INIT(&tkey.common, link); + tkey.mctx = msg->mctx; + dns_name_init(&tkey.algorithm, NULL); + dns_name_clone(key->algorithm, &tkey.algorithm); + tkey.inception = tkey.expire = 0; + tkey.mode = DNS_TKEYMODE_DELETE; + tkey.error = 0; + tkey.keylen = tkey.otherlen = 0; + tkey.key = tkey.other = NULL; + + return (buildquery(msg, &key->name, &tkey)); +} + +static isc_result_t +find_tkey(dns_message_t *msg, dns_name_t **name, dns_rdata_t *rdata, + int section) +{ + dns_rdataset_t *tkeyset; + isc_result_t result; + + result = dns_message_firstname(msg, section); + while (result == ISC_R_SUCCESS) { + *name = NULL; + dns_message_currentname(msg, section, name); + tkeyset = NULL; + result = dns_message_findtype(*name, dns_rdatatype_tkey, 0, + &tkeyset); + if (result == ISC_R_SUCCESS) { + result = dns_rdataset_first(tkeyset); + if (result != ISC_R_SUCCESS) + return (result); + dns_rdataset_current(tkeyset, rdata); + return (ISC_R_SUCCESS); + } + result = dns_message_nextname(msg, section); + } + if (result == ISC_R_NOMORE) + return (ISC_R_NOTFOUND); + return (result); +} + +isc_result_t +dns_tkey_processdhresponse(dns_message_t *qmsg, dns_message_t *rmsg, + dst_key_t *key, isc_buffer_t *nonce, + dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring) +{ + dns_rdata_t qtkeyrdata = DNS_RDATA_INIT, rtkeyrdata = DNS_RDATA_INIT; + dns_name_t keyname, *tkeyname, *theirkeyname, *ourkeyname, *tempname; + dns_rdataset_t *theirkeyset = NULL, *ourkeyset = NULL; + dns_rdata_t theirkeyrdata = DNS_RDATA_INIT; + dst_key_t *theirkey = NULL; + dns_rdata_tkey_t qtkey, rtkey; + unsigned char secretdata[256]; + unsigned int sharedsize; + isc_buffer_t *shared = NULL, secret; + isc_region_t r, r2; + isc_result_t result; + isc_boolean_t freertkey = ISC_FALSE; + + REQUIRE(qmsg != NULL); + REQUIRE(rmsg != NULL); + REQUIRE(key != NULL); + REQUIRE(dst_key_alg(key) == DNS_KEYALG_DH); + REQUIRE(dst_key_isprivate(key)); + if (outkey != NULL) + REQUIRE(*outkey == NULL); + + if (rmsg->rcode != dns_rcode_noerror) + return (ISC_RESULTCLASS_DNSRCODE + rmsg->rcode); + RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER)); + RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL)); + freertkey = ISC_TRUE; + + RETERR(find_tkey(qmsg, &tempname, &qtkeyrdata, + DNS_SECTION_ADDITIONAL)); + RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL)); + + if (rtkey.error != dns_rcode_noerror || + rtkey.mode != DNS_TKEYMODE_DIFFIEHELLMAN || + rtkey.mode != qtkey.mode || + !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm) || + rmsg->rcode != dns_rcode_noerror) + { + tkey_log("dns_tkey_processdhresponse: tkey mode invalid " + "or error set"); + result = DNS_R_INVALIDTKEY; + dns_rdata_freestruct(&qtkey); + goto failure; + } + + dns_rdata_freestruct(&qtkey); + + dns_name_init(&keyname, NULL); + dns_name_clone(dst_key_name(key), &keyname); + + ourkeyname = NULL; + ourkeyset = NULL; + RETERR(dns_message_findname(rmsg, DNS_SECTION_ANSWER, &keyname, + dns_rdatatype_key, 0, &ourkeyname, + &ourkeyset)); + + result = dns_message_firstname(rmsg, DNS_SECTION_ANSWER); + while (result == ISC_R_SUCCESS) { + theirkeyname = NULL; + dns_message_currentname(rmsg, DNS_SECTION_ANSWER, + &theirkeyname); + if (dns_name_equal(theirkeyname, ourkeyname)) + goto next; + theirkeyset = NULL; + result = dns_message_findtype(theirkeyname, dns_rdatatype_key, + 0, &theirkeyset); + if (result == ISC_R_SUCCESS) { + RETERR(dns_rdataset_first(theirkeyset)); + break; + } + next: + result = dns_message_nextname(rmsg, DNS_SECTION_ANSWER); + } + + if (theirkeyset == NULL) { + tkey_log("dns_tkey_processdhresponse: failed to find server " + "key"); + result = ISC_R_NOTFOUND; + goto failure; + } + + dns_rdataset_current(theirkeyset, &theirkeyrdata); + RETERR(dns_dnssec_keyfromrdata(theirkeyname, &theirkeyrdata, + rmsg->mctx, &theirkey)); + + RETERR(dst_key_secretsize(key, &sharedsize)); + RETERR(isc_buffer_allocate(rmsg->mctx, &shared, sharedsize)); + + RETERR(dst_key_computesecret(theirkey, key, shared)); + + isc_buffer_init(&secret, secretdata, sizeof(secretdata)); + + r.base = rtkey.key; + r.length = rtkey.keylen; + if (nonce != NULL) + isc_buffer_usedregion(nonce, &r2); + else { + r2.base = isc_mem_get(rmsg->mctx, 0); + r2.length = 0; + } + RETERR(compute_secret(shared, &r2, &r, &secret)); + if (nonce == NULL) + isc_mem_put(rmsg->mctx, r2.base, 0); + + isc_buffer_usedregion(&secret, &r); + result = dns_tsigkey_create(tkeyname, &rtkey.algorithm, + r.base, r.length, ISC_TRUE, + NULL, rtkey.inception, rtkey.expire, + rmsg->mctx, ring, outkey); + isc_buffer_free(&shared); + dns_rdata_freestruct(&rtkey); + dst_key_free(&theirkey); + return (result); + + failure: + if (shared != NULL) + isc_buffer_free(&shared); + + if (theirkey != NULL) + dst_key_free(&theirkey); + + if (freertkey) + dns_rdata_freestruct(&rtkey); + + return (result); +} + +isc_result_t +dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg, + dns_name_t *gname, void *cred, void **context, + dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring) +{ + dns_rdata_t rtkeyrdata = DNS_RDATA_INIT, qtkeyrdata = DNS_RDATA_INIT; + dns_name_t *tkeyname; + dns_rdata_tkey_t rtkey, qtkey; + isc_buffer_t outtoken; + dst_key_t *dstkey = NULL; + isc_region_t r; + isc_result_t result; + unsigned char array[1024]; + + REQUIRE(qmsg != NULL); + REQUIRE(rmsg != NULL); + REQUIRE(gname != NULL); + if (outkey != NULL) + REQUIRE(*outkey == NULL); + + if (rmsg->rcode != dns_rcode_noerror) + return (ISC_RESULTCLASS_DNSRCODE + rmsg->rcode); + RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER)); + RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL)); + + RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata, + DNS_SECTION_ADDITIONAL)); + RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL)); + + if (rtkey.error != dns_rcode_noerror || + rtkey.mode != DNS_TKEYMODE_GSSAPI || + !dns_name_equal(&rtkey.algorithm, &rtkey.algorithm)) + { + tkey_log("dns_tkey_processdhresponse: tkey mode invalid " + "or error set"); + result = DNS_R_INVALIDTKEY; + goto failure; + } + + isc_buffer_init(&outtoken, array, sizeof(array)); + r.base = rtkey.key; + r.length = rtkey.keylen; + RETERR(dst_gssapi_initctx(gname, cred, &r, &outtoken, context)); + + dstkey = NULL; + RETERR(dst_key_fromgssapi(dns_rootname, *context, rmsg->mctx, + &dstkey)); + + RETERR(dns_tsigkey_createfromkey(tkeyname, DNS_TSIG_GSSAPI_NAME, + dstkey, ISC_TRUE, NULL, + rtkey.inception, rtkey.expire, + rmsg->mctx, ring, outkey)); + + dns_rdata_freestruct(&rtkey); + return (result); + + failure: + return (result); +} + +isc_result_t +dns_tkey_processdeleteresponse(dns_message_t *qmsg, dns_message_t *rmsg, + dns_tsig_keyring_t *ring) +{ + dns_rdata_t qtkeyrdata = DNS_RDATA_INIT, rtkeyrdata = DNS_RDATA_INIT; + dns_name_t *tkeyname, *tempname; + dns_rdata_tkey_t qtkey, rtkey; + dns_tsigkey_t *tsigkey = NULL; + isc_result_t result; + + REQUIRE(qmsg != NULL); + REQUIRE(rmsg != NULL); + + if (rmsg->rcode != dns_rcode_noerror) + return(ISC_RESULTCLASS_DNSRCODE + rmsg->rcode); + + RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER)); + RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL)); + + RETERR(find_tkey(qmsg, &tempname, &qtkeyrdata, + DNS_SECTION_ADDITIONAL)); + RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL)); + + if (rtkey.error != dns_rcode_noerror || + rtkey.mode != DNS_TKEYMODE_DELETE || + rtkey.mode != qtkey.mode || + !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm) || + rmsg->rcode != dns_rcode_noerror) + { + tkey_log("dns_tkey_processdeleteresponse: tkey mode invalid " + "or error set"); + result = DNS_R_INVALIDTKEY; + dns_rdata_freestruct(&qtkey); + dns_rdata_freestruct(&rtkey); + goto failure; + } + + dns_rdata_freestruct(&qtkey); + + RETERR(dns_tsigkey_find(&tsigkey, tkeyname, &rtkey.algorithm, ring)); + + dns_rdata_freestruct(&rtkey); + + /* + * Mark the key as deleted. + */ + dns_tsigkey_setdeleted(tsigkey); + /* + * Release the reference. + */ + dns_tsigkey_detach(&tsigkey); + + failure: + return (result); +} |