tsig.c revision adccda3b4c426b93a45ce075bd23602b63b6afe9
/*
* Copyright (C) 2004-2010 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 1999-2002 Internet Software Consortium.
*
* 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: tsig.c,v 1.146 2011/01/10 07:38:22 marka Exp $
*/
/*! \file */
#include <config.h>
#include <stdlib.h>
#include <isc/refcount.h>
#include <dns/keyvalues.h>
#include <dns/fixedname.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatastruct.h>
#ifndef DNS_TSIG_MAXGENERATEDKEYS
#define DNS_TSIG_MAXGENERATEDKEYS 4096
#endif
#define algname_is_allocated(algname) \
((algname) != dns_tsig_hmacmd5_name && \
(algname) != dns_tsig_hmacsha1_name && \
(algname) != dns_tsig_hmacsha224_name && \
(algname) != dns_tsig_hmacsha256_name && \
(algname) != dns_tsig_hmacsha384_name && \
(algname) != dns_tsig_hmacsha512_name && \
(algname) != dns_tsig_gssapi_name && \
#define BADTIMELEN 6
static unsigned char hmacmd5_ndata[] = "\010hmac-md5\007sig-alg\003reg\003int";
static dns_name_t hmacmd5 = {
{(void *)-1, (void *)-1},
};
static unsigned char gsstsig_ndata[] = "\010gss-tsig";
static unsigned char gsstsig_offsets[] = { 0, 9 };
static dns_name_t gsstsig = {
{(void *)-1, (void *)-1},
};
/*
* Since Microsoft doesn't follow its own standard, we will use this
* alternate name as a second guess.
*/
static unsigned char gsstsigms_ndata[] = "\003gss\011microsoft\003com";
static dns_name_t gsstsigms = {
{(void *)-1, (void *)-1},
};
static unsigned char hmacsha1_ndata[] = "\011hmac-sha1";
static unsigned char hmacsha1_offsets[] = { 0, 10 };
static dns_name_t hmacsha1 = {
{(void *)-1, (void *)-1},
};
static unsigned char hmacsha224_ndata[] = "\013hmac-sha224";
static unsigned char hmacsha224_offsets[] = { 0, 12 };
static dns_name_t hmacsha224 = {
{(void *)-1, (void *)-1},
};
static unsigned char hmacsha256_ndata[] = "\013hmac-sha256";
static unsigned char hmacsha256_offsets[] = { 0, 12 };
static dns_name_t hmacsha256 = {
{(void *)-1, (void *)-1},
};
static unsigned char hmacsha384_ndata[] = "\013hmac-sha384";
static unsigned char hmacsha384_offsets[] = { 0, 12 };
static dns_name_t hmacsha384 = {
{(void *)-1, (void *)-1},
};
static unsigned char hmacsha512_ndata[] = "\013hmac-sha512";
static unsigned char hmacsha512_offsets[] = { 0, 12 };
static dns_name_t hmacsha512 = {
{(void *)-1, (void *)-1},
};
static isc_result_t
static void
static void
static void
static void
char message[4096];
char namestr[DNS_NAME_FORMATSIZE];
char creatorstr[DNS_NAME_FORMATSIZE];
return;
else
else
level, "tsig key '%s' (%s): %s",
else
}
static void
}
}
static void
/*
* We may have been removed from the LRU list between
* removing the read lock and aquiring the write lock.
*/
}
}
}
/*
* A supplemental routine just to add a key to ring. Note that reference
* counter should be counted separately because we may be adding the key
* as part of creation of the key, in which case the reference counter was
* already initialized. Also note we don't need RWLOCK for the reference
* counter: it's protected by a separate lock.
*/
static isc_result_t
{
ring->writecount++;
/*
* Do on the fly cleaning. Find some nodes we might not
* want around any more.
*/
ring->writecount = 0;
}
/*
* Add the new key to the LRU list and remove the least
* recently used key if there are too many keys on the list.
*/
}
return (result);
}
{
unsigned int refs = 0;
return (ISC_R_NOMEMORY);
if (ret != ISC_R_SUCCESS)
goto cleanup_key;
ret = DNS_R_BADALG;
goto cleanup_name;
}
ret = DNS_R_BADALG;
goto cleanup_name;
}
ret = DNS_R_BADALG;
goto cleanup_name;
}
ret = DNS_R_BADALG;
goto cleanup_name;
}
ret = DNS_R_BADALG;
goto cleanup_name;
}
ret = DNS_R_BADALG;
goto cleanup_name;
}
ret = DNS_R_BADALG;
goto cleanup_name;
}
ret = DNS_R_BADALG;
goto cleanup_name;
}
} else {
ret = DNS_R_BADALG;
goto cleanup_name;
}
goto cleanup_name;
}
if (ret != ISC_R_SUCCESS)
goto cleanup_algorithm;
NULL);
}
goto cleanup_algorithm;
}
if (ret != ISC_R_SUCCESS) {
goto cleanup_algorithm;
}
} else
refs = 1;
refs++;
if (ret != ISC_R_SUCCESS)
goto cleanup_creator;
if (ret != ISC_R_SUCCESS)
goto cleanup_refs;
}
/*
* Ignore this if it's a GSS key, since the key size is meaningless.
*/
char namestr[DNS_NAME_FORMATSIZE];
"the key '%s' is too short to be secure",
namestr);
}
return (ISC_R_SUCCESS);
while (refs-- > 0)
}
}
return (ret);
}
/*
* Find a few nodes to destroy if possible.
*/
static void
{
/*
* Start up a new iterator each time.
*/
origin);
return;
}
for (;;) {
/* delete the key */
goto again;
}
}
origin);
return;
}
}
}
static void
}
static unsigned int
return (DST_ALG_HMACMD5);
return (DST_ALG_HMACSHA1);
return (DST_ALG_HMACSHA224);
return (DST_ALG_HMACSHA256);
return (DST_ALG_HMACSHA384);
return (DST_ALG_HMACSHA512);
return (DST_ALG_GSSAPI);
return (DST_ALG_GSSAPI);
} else
return (0);
}
static isc_result_t
char namestr[1024];
char creatorstr[1024];
char algorithmstr[1024];
char keystr[4096];
int n;
isc_buffer_t b;
unsigned int dstalg;
if (n == EOF)
return (ISC_R_NOMORE);
if (n != 6)
return (ISC_R_FAILURE);
return (DNS_R_EXPIRED);
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
return (result);
if (dstalg == 0)
return (DNS_R_BADALG);
if (result != ISC_R_SUCCESS)
return (result);
return (result);
}
static void
{
int length = 0;
char namestr[DNS_NAME_FORMATSIZE];
char creatorstr[DNS_NAME_FORMATSIZE];
char algorithmstr[DNS_NAME_FORMATSIZE];
if (result == ISC_R_SUCCESS)
}
unsigned int references;
ring->references--;
if (references != 0)
return (DNS_R_CONTINUE);
origin);
goto destroy;
}
for (;;) {
origin);
if (result == ISC_R_NOMORE)
goto destroy;
}
}
return (result);
}
{
if (length > 0)
isc_buffer_t b;
isc_buffer_add(&b, length);
if (result != ISC_R_SUCCESS)
return (result);
}
isc_buffer_t b;
isc_buffer_add(&b, length);
if (result != ISC_R_SUCCESS)
return (result);
}
isc_buffer_t b;
isc_buffer_add(&b, length);
if (result != ISC_R_SUCCESS)
return (result);
}
isc_buffer_t b;
isc_buffer_add(&b, length);
if (result != ISC_R_SUCCESS)
return (result);
}
isc_buffer_t b;
isc_buffer_add(&b, length);
if (result != ISC_R_SUCCESS)
return (result);
}
isc_buffer_t b;
isc_buffer_add(&b, length);
if (result != ISC_R_SUCCESS)
return (result);
}
} else if (length > 0)
return (DNS_R_BADALG);
return (result);
}
void
}
static void
}
}
}
void
unsigned int refs;
if (refs == 0)
}
void
}
unsigned char data[128];
isc_region_t r;
unsigned char badtimedata[BADTIMELEN];
unsigned int sigsize = 0;
/*
* If this is a response, there should be a query tsig.
*/
return (DNS_R_EXPECTEDTSIG);
if (is_response(msg))
else
} else {
}
unsigned char header[DNS_MESSAGE_HEADERLEN];
if (ret != ISC_R_SUCCESS)
return (ret);
/*
* If this is a response, digest the query signature.
*/
if (is_response(msg)) {
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
NULL);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
if (isc_buffer_availablelength(&databuf) <
ret = ISC_R_NOSPACE;
goto cleanup_context;
}
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
}
/*
* Digest the header.
*/
isc_buffer_usedregion(&headerbuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest the remainder of the message.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
if (msg->tcp_continuation == 0) {
/*
* Digest the name, class, ttl, alg.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
}
/* Digest the timesigned and fudge */
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
if (msg->tcp_continuation == 0) {
/*
* Digest the error and other data length.
*/
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest the error and other data.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
}
}
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
goto cleanup_context;
}
if (ret != ISC_R_SUCCESS)
goto cleanup_signature;
if (digestbits != 0) {
} else
} else {
}
if (ret != ISC_R_SUCCESS)
goto cleanup_signature;
if (ret != ISC_R_SUCCESS)
goto cleanup_rdata;
if (ret != ISC_R_SUCCESS)
goto cleanup_dynbuf;
}
if (ret != ISC_R_SUCCESS)
goto cleanup_rdata;
if (ret != ISC_R_SUCCESS)
goto cleanup_owner;
if (ret != ISC_R_SUCCESS)
goto cleanup_owner;
if (ret != ISC_R_SUCCESS)
goto cleanup_rdatalist;
== ISC_R_SUCCESS);
/* Windows does not like the tsig name being compressed. */
return (ISC_R_SUCCESS);
goto cleanup_rdata;
return (ret);
}
{
unsigned char data[32];
unsigned char header[DNS_MESSAGE_HEADERLEN];
unsigned int siglen;
unsigned int alg;
if (msg->tcp_continuation) {
return (DNS_R_UNEXPECTEDTSIG);
}
/*
* There should be a TSIG record...
*/
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) &&
return (DNS_R_UNEXPECTEDTSIG);
/*
* If we're here, we know the message is well formed and contains a
* TSIG record.
*/
if (ret != ISC_R_SUCCESS)
return (ret);
if (ret != ISC_R_SUCCESS)
return (ret);
if (is_response(msg)) {
if (ret != ISC_R_SUCCESS)
return (ret);
if (ret != ISC_R_SUCCESS)
return (ret);
}
/*
* Do the key name and algorithm match that of the query?
*/
if (is_response(msg) &&
"key name and algorithm do not match");
return (DNS_R_TSIGVERIFYFAILURE);
}
/*
* Get the current time.
*/
/*
* Find dns_tsigkey_t based on keyname.
*/
if (ret != ISC_R_SUCCESS) {
if (ret != ISC_R_SUCCESS)
return (ret);
return (DNS_R_TSIGVERIFYFAILURE);
}
}
/*
* Is the time ok?
*/
return (DNS_R_CLOCKSKEW);
return (DNS_R_CLOCKSKEW);
}
/*
* Check digest length.
*/
if (ret != ISC_R_SUCCESS)
return (ret);
return (DNS_R_FORMERR);
}
"signature length below minimum");
return (DNS_R_FORMERR);
}
"truncated signature length too small");
return (DNS_R_TSIGVERIFYFAILURE);
}
return (DNS_R_TSIGVERIFYFAILURE);
}
}
if (ret != ISC_R_SUCCESS)
return (ret);
if (is_response(msg)) {
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
}
}
/*
* Extract the header.
*/
isc_buffer_usedregion(source, &r);
/*
* Decrement the additional field counter.
*/
/*
* Put in the original id.
*/
/*
* Digest the modified header.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest all non-TSIG records.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest the key name.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest the key algorithm.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
}
if (ret == DST_R_VERIFYFAILURE) {
"signature failed to verify(1)");
goto cleanup_context;
} else if (ret != ISC_R_SUCCESS)
goto cleanup_context;
return (DNS_R_TSIGVERIFYFAILURE);
}
return (DNS_R_CLOCKSKEW);
else
return (DNS_R_TSIGERRORSET);
}
return (ISC_R_SUCCESS);
return (ret);
}
static isc_result_t
unsigned char data[32];
unsigned char header[DNS_MESSAGE_HEADERLEN];
if (!is_response(msg))
return (DNS_R_EXPECTEDRESPONSE);
/*
* Extract and parse the previous TSIG
*/
if (ret != ISC_R_SUCCESS)
return (ret);
if (ret != ISC_R_SUCCESS)
return (ret);
/*
* If there is a TSIG in this message, do some checks.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_querystruct;
if (ret != ISC_R_SUCCESS)
goto cleanup_querystruct;
/*
* Do the key name and algorithm match that of the query?
*/
"key name and algorithm do not match");
goto cleanup_querystruct;
}
/*
* Is the time ok?
*/
goto cleanup_querystruct;
"signature is in the future");
goto cleanup_querystruct;
}
}
if (ret != ISC_R_SUCCESS)
goto cleanup_querystruct;
/*
* Digest the length of the query signature
*/
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest the data of the query signature
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
}
}
/*
* Extract the header.
*/
isc_buffer_usedregion(source, &r);
/*
* Decrement the additional field counter if necessary.
*/
if (has_tsig) {
}
/*
* Put in the original id.
*/
/* XXX Can TCP transfers be forwarded? How would that work? */
if (has_tsig) {
}
/*
* Digest the modified header.
*/
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest all non-TSIG records.
*/
if (has_tsig)
else
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
/*
* Digest the time signed and fudge.
*/
if (has_tsig) {
isc_buffer_usedregion(&databuf, &r);
if (ret != ISC_R_SUCCESS)
goto cleanup_context;
else
} else {
"signature is empty");
}
goto cleanup_context;
}
if (ret == DST_R_VERIFYFAILURE) {
"signature failed to verify(2)");
goto cleanup_context;
}
else if (ret != ISC_R_SUCCESS)
goto cleanup_context;
}
return (ISC_R_SUCCESS);
return (ret);
}
{
return (ISC_R_NOTFOUND);
}
return (ISC_R_NOTFOUND);
}
/*
* The key has expired.
*/
return (ISC_R_NOTFOUND);
}
#if 0
/*
* MPAXXX We really should look at the inception time.
*/
return (ISC_R_NOTFOUND);
}
#endif
return (ISC_R_SUCCESS);
}
static void
}
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS) {
return (result);
}
ring->writecount = 0;
return (ISC_R_SUCCESS);
}
{
if (result == ISC_R_SUCCESS)
return (result);
}
void
{
source->references++;
}
void
unsigned int references;
ring->references--;
if (references == 0)
}
void
do {
if (result == ISC_R_NOMORE)
return;
} while (result == ISC_R_SUCCESS);
}