diff options
Diffstat (limited to 'src/tsig.c')
-rw-r--r-- | src/tsig.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/tsig.c b/src/tsig.c new file mode 100644 index 0000000..0b660c1 --- /dev/null +++ b/src/tsig.c @@ -0,0 +1,326 @@ +/* + * Copyright 2019-2021 OARC, Inc. + * Copyright 2017-2018 Akamai Technologies + * Copyright 2006-2016 Nominum, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" + +#include "tsig.h" + +#include "log.h" +#include "opt.h" +#include "dns.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> +#include <openssl/evp.h> +#include <time.h> + +#define TSIG_HMACMD5_NAME "hmac-md5.sig-alg.reg.int" +#define TSIG_HMACSHA1_NAME "hmac-sha1" +#define TSIG_HMACSHA224_NAME "hmac-sha224" +#define TSIG_HMACSHA256_NAME "hmac-sha256" +#define TSIG_HMACSHA384_NAME "hmac-sha384" +#define TSIG_HMACSHA512_NAME "hmac-sha512" + +static unsigned char* decode64(const void* base64, int* len) +{ + unsigned char* out; + + assert(base64); + assert(len); + assert(*len); + + out = calloc(1, *len); + assert(out); + + int olen = *len; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_ENCODE_CTX evp; + EVP_DecodeInit(&evp); + if (EVP_DecodeUpdate(&evp, out, &olen, base64, *len)) { + free(out); + return 0; + } +#else + EVP_ENCODE_CTX* evp = EVP_ENCODE_CTX_new(); + if (!evp) { + free(out); + return 0; + } + EVP_DecodeInit(evp); + if (EVP_DecodeUpdate(evp, out, &olen, base64, *len)) { + free(out); + EVP_ENCODE_CTX_free(evp); + return 0; + } + EVP_ENCODE_CTX_free(evp); +#endif + + *len = olen; + + return out; +} + +perf_tsigkey_t* perf_tsig_parsekey(const char* arg) +{ + perf_tsigkey_t* tsigkey; + const char * sep1, *sep2, *alg, *name, *secret; + size_t alglen, namelen, secretlen; + int keylen; + const EVP_MD* md = 0; + + tsigkey = calloc(1, sizeof(*tsigkey)); + if (!tsigkey) { + perf_log_fatal("out of memory"); + return 0; // fix clang scan-build + } + + sep1 = strchr(arg, ':'); + if (sep1 == NULL) { + perf_log_warning("invalid TSIG [alg:]name:secret"); + perf_opt_usage(); + exit(1); + } + + sep2 = strchr(sep1 + 1, ':'); + if (sep2 == NULL) { + /* name:key */ + alg = NULL; + alglen = 0; + name = arg; + namelen = sep1 - arg; + secret = sep1 + 1; + } else { + /* [alg:]name:secret */ + alg = arg; + alglen = sep1 - arg; + name = sep1 + 1; + namelen = sep2 - sep1 - 1; + secret = sep2 + 1; + } + + /* Algorithm */ + + if (!alg || !strncasecmp(alg, "hmac-md5:", 9)) { + md = EVP_md5(); + tsigkey->alg = TSIG_HMACMD5_NAME; + tsigkey->alglen = sizeof(TSIG_HMACMD5_NAME) - 1; + } else if (!strncasecmp(alg, "hmac-sha1:", 10)) { + md = EVP_sha1(); + tsigkey->alg = TSIG_HMACSHA1_NAME; + tsigkey->alglen = sizeof(TSIG_HMACSHA1_NAME) - 1; + } else if (!strncasecmp(alg, "hmac-sha224:", 12)) { + md = EVP_sha224(); + tsigkey->alg = TSIG_HMACSHA224_NAME; + tsigkey->alglen = sizeof(TSIG_HMACSHA224_NAME) - 1; + } else if (!strncasecmp(alg, "hmac-sha256:", 12)) { + md = EVP_sha256(); + tsigkey->alg = TSIG_HMACSHA256_NAME; + tsigkey->alglen = sizeof(TSIG_HMACSHA256_NAME) - 1; + } else if (!strncasecmp(alg, "hmac-sha384:", 12)) { + md = EVP_sha384(); + tsigkey->alg = TSIG_HMACSHA384_NAME; + tsigkey->alglen = sizeof(TSIG_HMACSHA384_NAME) - 1; + } else if (!strncasecmp(alg, "hmac-sha512:", 12)) { + md = EVP_sha512(); + tsigkey->alg = TSIG_HMACSHA512_NAME; + tsigkey->alglen = sizeof(TSIG_HMACSHA512_NAME) - 1; + } else { + perf_log_warning("invalid TSIG algorithm %.*s", (int)alglen, alg); + perf_opt_usage(); + exit(1); + } + + if (namelen > sizeof(tsigkey->name)) { + perf_log_fatal("unable to setup TSIG, name too long"); + // fix clang scan-build / sonarcloud: + free(tsigkey); + return 0; + } + memcpy(tsigkey->name, name, namelen); + tsigkey->namelen = namelen; + for (namelen = 0; namelen < tsigkey->namelen; namelen++) { + tsigkey->name[namelen] = tolower(tsigkey->name[namelen]); + } + + /* Secret */ + + secretlen = strlen(secret); + if (!secretlen) { + perf_log_warning("unable to setup TSIG, secret empty"); + perf_opt_usage(); + exit(1); + } + + keylen = secretlen; + unsigned char* key = decode64(secret, &keylen); + if (!key) { + perf_log_fatal("unable to setup TSIG, invalid base64 secret"); + } + + /* Setup HMAC */ + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + if (!(tsigkey->hmac = calloc(1, sizeof(*tsigkey->hmac)))) { + perf_log_fatal("unable to setup TSIG, OpenSSL HMAC context failed to be created"); + } + HMAC_CTX_init(tsigkey->hmac); +#else + if (!(tsigkey->hmac = HMAC_CTX_new())) { + perf_log_fatal("unable to setup TSIG, OpenSSL HMAC context failed to be created"); + } +#endif + + if (!HMAC_Init_ex(tsigkey->hmac, key, keylen, md, 0)) { + perf_log_fatal("unable to setup TSIG, OpenSSL HMAC init failed"); + } + + free(key); + + return tsigkey; +} + +void perf_tsig_destroykey(perf_tsigkey_t** tsigkeyp) +{ + assert(tsigkeyp); + assert(*tsigkeyp); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX_cleanup((*tsigkeyp)->hmac); + free((*tsigkeyp)->hmac); +#else + HMAC_CTX_free((*tsigkeyp)->hmac); +#endif + + free(*tsigkeyp); + *tsigkeyp = 0; +} + +/* + * Appends a TSIG record to the packet. + */ +perf_result_t perf_add_tsig(perf_buffer_t* packet, perf_tsigkey_t* tsigkey) +{ + unsigned char* base; + size_t rdlen, totallen; + unsigned char tmpdata[512], md[EVP_MAX_MD_SIZE]; + unsigned int mdlen; + perf_buffer_t tmp; + uint32_t now; + perf_result_t result; + + now = time(NULL); + + if (!HMAC_Init_ex(tsigkey->hmac, 0, 0, 0, 0)) { + perf_log_fatal("adding TSIG: OpenSSL HMAC reinit failed"); + } + + if (!HMAC_Update(tsigkey->hmac, perf_buffer_base(packet), perf_buffer_usedlength(packet))) { + perf_log_fatal("adding TSIG: OpenSSL HMAC update failed"); + } + + /* Digest the TSIG record */ + perf_buffer_init(&tmp, tmpdata, sizeof tmpdata); + switch ((result = perf_dname_fromstring(tsigkey->name, tsigkey->namelen, &tmp))) { + case PERF_R_SUCCESS: + break; + case PERF_R_NOSPACE: + perf_log_warning("adding TSIG: out of space in digest record"); + return result; + default: + perf_log_warning("adding TSIG: invalid owner name"); + return result; + } + perf_buffer_putuint16(&tmp, 255); /* class ANY */ + perf_buffer_putuint32(&tmp, 0); /* ttl */ + switch ((result = perf_dname_fromstring(tsigkey->alg, tsigkey->alglen, &tmp))) { + case PERF_R_SUCCESS: + break; + case PERF_R_NOSPACE: + perf_log_warning("adding TSIG: out of space in digest record"); + return result; + default: + perf_log_warning("adding TSIG: invalid algorithm name"); + return result; + } + perf_buffer_putuint16(&tmp, 0); /* time high */ + perf_buffer_putuint32(&tmp, now); /* time low */ + perf_buffer_putuint16(&tmp, 300); /* fudge */ + perf_buffer_putuint16(&tmp, 0); /* error */ + perf_buffer_putuint16(&tmp, 0); /* other length */ + + if (!HMAC_Update(tsigkey->hmac, perf_buffer_base(&tmp), perf_buffer_usedlength(&tmp))) { + perf_log_fatal("adding TSIG: OpenSSL HMAC update failed"); + } + + mdlen = sizeof(md); + if (!HMAC_Final(tsigkey->hmac, md, &mdlen)) { + perf_log_fatal("adding TSIG: OpenSSL HMAC final failed"); + } + + /* Make sure everything will fit */ + rdlen = tsigkey->alglen + 18 + mdlen; + totallen = tsigkey->namelen + 12 + rdlen; + if (totallen > perf_buffer_availablelength(packet)) { + perf_log_warning("adding TSIG: out of space"); + return PERF_R_NOSPACE; + } + + base = perf_buffer_base(packet); + + /* Add the TSIG record. */ + switch ((result = perf_dname_fromstring(tsigkey->name, tsigkey->namelen, packet))) { + case PERF_R_SUCCESS: + break; + case PERF_R_NOSPACE: + perf_log_warning("adding TSIG: out of space"); + return result; + default: + perf_log_warning("adding TSIG: invalid owner name"); + return result; + } + perf_buffer_putuint16(packet, 250); /* type TSIG */ + perf_buffer_putuint16(packet, 255); /* class ANY */ + perf_buffer_putuint32(packet, 0); /* ttl */ + perf_buffer_putuint16(packet, rdlen); /* rdlen */ + switch ((result = perf_dname_fromstring(tsigkey->alg, tsigkey->alglen, packet))) { + case PERF_R_SUCCESS: + break; + case PERF_R_NOSPACE: + perf_log_warning("adding TSIG: out of space"); + return result; + default: + perf_log_warning("adding TSIG: invalid algorithm name"); + return result; + } + perf_buffer_putuint16(packet, 0); /* time high */ + perf_buffer_putuint32(packet, now); /* time low */ + perf_buffer_putuint16(packet, 300); /* fudge */ + perf_buffer_putuint16(packet, mdlen); /* digest len */ + perf_buffer_putmem(packet, md, mdlen); /* digest */ + perf_buffer_putmem(packet, base, 2); /* orig ID */ + perf_buffer_putuint16(packet, 0); /* error */ + perf_buffer_putuint16(packet, 0); /* other len */ + + base[11]++; /* increment additional record count */ + + return PERF_R_SUCCESS; +} |