summaryrefslogtreecommitdiffstats
path: root/src/lib/dns/tsig.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/dns/tsig.cc581
1 files changed, 581 insertions, 0 deletions
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
new file mode 100644
index 0000000..d7c85de
--- /dev/null
+++ b/src/lib/dns/tsig.cc
@@ -0,0 +1,581 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <sys/time.h>
+
+#include <stdint.h>
+
+#include <cassert>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/time_utilities.h>
+
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/tsig.h>
+#include <dns/tsigerror.h>
+#include <dns/tsigkey.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::cryptolink;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace dns {
+namespace {
+typedef boost::shared_ptr<HMAC> HMACPtr;
+
+// TSIG uses 48-bit unsigned integer to represent time signed.
+// Since gettimeWrapper() returns a 64-bit *signed* integer, we
+// make sure it's stored in an unsigned 64-bit integer variable and
+// represents a value in the expected range. (In reality, however,
+// gettimeWrapper() will return a positive integer that will fit
+// in 48 bits)
+uint64_t
+getTSIGTime() {
+ return (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
+}
+}
+
+struct TSIGContext::TSIGContextImpl {
+ TSIGContextImpl(const TSIGKey& key,
+ TSIGError error = TSIGError::NOERROR()) :
+ state_(INIT), key_(key), error_(error),
+ previous_timesigned_(0), digest_len_(0),
+ last_sig_dist_(-1) {
+ if (error == TSIGError::NOERROR()) {
+ // In normal (NOERROR) case, the key should be valid, and we
+ // should be able to pre-create a corresponding HMAC object,
+ // which will be likely to be used for sign or verify later.
+ // We do this in the constructor so that we can know the expected
+ // digest length in advance. The creation should normally succeed,
+ // but the key information could be still broken, which could
+ // trigger an exception inside the cryptolink module. We ignore
+ // it at this moment; a subsequent sign/verify operation will try
+ // to create the HMAC, which would also fail.
+ try {
+ hmac_.reset(CryptoLink::getCryptoLink().createHMAC(
+ key_.getSecret(), key_.getSecretLength(),
+ key_.getAlgorithm()),
+ deleteHMAC);
+ } catch (const isc::Exception&) {
+ return;
+ }
+ size_t digestbits = key_.getDigestbits();
+ size_t default_digest_len = hmac_->getOutputLength();
+ if (digestbits > 0) {
+ digest_len_ = (digestbits + 7) / 8;
+ // sanity (cf. RFC 4635)
+ if ((digest_len_ < 10) ||
+ (digest_len_ < (default_digest_len / 2)) ||
+ (digest_len_ > default_digest_len)) {
+ // should emit a warning?
+ digest_len_ = default_digest_len;
+ }
+ } else {
+ digest_len_ = default_digest_len;
+ }
+ }
+ }
+
+ // This helper method is used from verify(). It's expected to be called
+ // just before verify() returns. It updates internal state based on
+ // the verification result and return the TSIGError to be returned to
+ // the caller of verify(), so that verify() can call this method within
+ // its 'return' statement.
+ TSIGError postVerifyUpdate(TSIGError error, const void* digest,
+ uint16_t digest_len)
+ {
+ if (state_ == INIT) {
+ state_ = RECEIVED_REQUEST;
+ } else if (state_ == SENT_REQUEST && error == TSIGError::NOERROR()) {
+ state_ = VERIFIED_RESPONSE;
+ }
+ if (digest != NULL) {
+ previous_digest_.assign(static_cast<const uint8_t*>(digest),
+ static_cast<const uint8_t*>(digest) +
+ digest_len);
+ }
+ error_ = error;
+ return (error);
+ }
+
+ // A shortcut method to create an HMAC object for sign/verify. If one
+ // has been successfully created in the constructor, return it; otherwise
+ // create a new one and return it. In the former case, the ownership is
+ // transferred to the caller; the stored HMAC will be reset after the
+ // call.
+ HMACPtr createHMAC() {
+ if (hmac_) {
+ HMACPtr ret = HMACPtr();
+ ret.swap(hmac_);
+ return (ret);
+ }
+ return (HMACPtr(CryptoLink::getCryptoLink().createHMAC(
+ key_.getSecret(), key_.getSecretLength(),
+ key_.getAlgorithm()),
+ deleteHMAC));
+ }
+
+ // The following three are helper methods to compute the digest for
+ // TSIG sign/verify in order to unify the common code logic for sign()
+ // and verify() and to keep these callers concise.
+ // These methods take an HMAC object, which will be updated with the
+ // calculated digest.
+ // Note: All methods construct a local OutputBuffer as a work space with a
+ // fixed initial buffer size to avoid intermediate buffer extension.
+ // This should be efficient enough, especially for fundamentally expensive
+ // operation like cryptographic sign/verify, but if the creation of the
+ // buffer in each helper method is still identified to be a severe
+ // performance bottleneck, we could have this class a buffer as a member
+ // variable and reuse it throughout the object's lifetime. Right now,
+ // we prefer keeping the scope for local things as small as possible.
+ void digestPreviousMAC(HMACPtr hmac);
+ void digestTSIGVariables(HMACPtr hmac, uint16_t rrclass, uint32_t rrttl,
+ uint64_t time_signed, uint16_t fudge,
+ uint16_t error, uint16_t otherlen,
+ const void* otherdata,
+ bool time_variables_only) const;
+ void digestDNSMessage(HMACPtr hmac, uint16_t qid, const void* data,
+ size_t data_len) const;
+ State state_;
+ const TSIGKey key_;
+ vector<uint8_t> previous_digest_;
+ TSIGError error_;
+ uint64_t previous_timesigned_; // only meaningful for response with BADTIME
+ size_t digest_len_;
+ HMACPtr hmac_;
+ // This is the distance from the last verified signed message. Value of 0
+ // means the last message was signed. Special value -1 means there was no
+ // signed message yet.
+ int last_sig_dist_;
+};
+
+void
+TSIGContext::TSIGContextImpl::digestPreviousMAC(HMACPtr hmac) {
+ // We should have ensured the digest size fits 16 bits within this class
+ // implementation.
+ assert(previous_digest_.size() <= 0xffff);
+
+ if (previous_digest_.empty()) {
+ // The previous digest was already used. We're in the middle of
+ // TCP stream somewhere and we already pushed some unsigned message
+ // into the HMAC state.
+ return;
+ }
+
+ OutputBuffer buffer(sizeof(uint16_t) + previous_digest_.size());
+ const uint16_t previous_digest_len(previous_digest_.size());
+ buffer.writeUint16(previous_digest_len);
+ if (previous_digest_len != 0) {
+ buffer.writeData(&previous_digest_[0], previous_digest_len);
+ }
+ hmac->update(buffer.getData(), buffer.getLength());
+}
+
+void
+TSIGContext::TSIGContextImpl::digestTSIGVariables(
+ HMACPtr hmac, uint16_t rrclass, uint32_t rrttl, uint64_t time_signed,
+ uint16_t fudge, uint16_t error, uint16_t otherlen, const void* otherdata,
+ bool time_variables_only) const {
+ // It's bit complicated, but we can still predict the necessary size of
+ // the data to be digested. So we precompute it to avoid possible
+ // reallocation inside OutputBuffer (not absolutely necessary, but this
+ // is a bit more efficient)
+ size_t data_size = 8;
+ if (!time_variables_only) {
+ data_size += 10 + key_.getKeyName().getLength() +
+ key_.getAlgorithmName().getLength();
+ }
+ OutputBuffer buffer(data_size);
+
+ if (!time_variables_only) {
+ key_.getKeyName().toWire(buffer);
+ buffer.writeUint16(rrclass);
+ buffer.writeUint32(rrttl);
+ key_.getAlgorithmName().toWire(buffer);
+ }
+ buffer.writeUint16(time_signed >> 32);
+ buffer.writeUint32(time_signed & 0xffffffff);
+ buffer.writeUint16(fudge);
+
+ if (!time_variables_only) {
+ buffer.writeUint16(error);
+ buffer.writeUint16(otherlen);
+ }
+
+ hmac->update(buffer.getData(), buffer.getLength());
+ if (!time_variables_only && otherlen > 0) {
+ hmac->update(otherdata, otherlen);
+ }
+}
+
+// In digestDNSMessage, we exploit some minimum knowledge of DNS message
+// format:
+// - the header section has a fixed length of 12 octets (MESSAGE_HEADER_LEN)
+// - the offset in the header section to the ID field is 0
+// - the offset in the header section to the ARCOUNT field is 10 (and the field
+// length is 2 octets)
+// We could construct a separate Message object from the given data, adjust
+// fields via the Message interfaces and then render it back to a separate
+// buffer, but that would be overkilling. The DNS message header has a
+// fixed length and necessary modifications are quite straightforward, so
+// we do the job using lower level interfaces.
+namespace {
+const size_t MESSAGE_HEADER_LEN = 12;
+}
+
+void
+TSIGContext::TSIGContextImpl::digestDNSMessage(HMACPtr hmac,
+ uint16_t qid, const void* data,
+ size_t data_len) const {
+ OutputBuffer buffer(MESSAGE_HEADER_LEN);
+ const uint8_t* msgptr = static_cast<const uint8_t*>(data);
+
+ // Install the original ID
+ buffer.writeUint16(qid);
+ msgptr += sizeof(uint16_t);
+
+ // Copy the rest of the header except the ARCOUNT field.
+ buffer.writeData(msgptr, 8);
+ msgptr += 8;
+
+ // Install the adjusted ARCOUNT (we don't care even if the value is bogus
+ // and it underflows; it would simply result in verification failure)
+ buffer.writeUint16(InputBuffer(msgptr, sizeof(uint16_t)).readUint16() - 1);
+ msgptr += 2;
+
+ // Digest the header and the rest of the DNS message
+ hmac->update(buffer.getData(), buffer.getLength());
+ hmac->update(msgptr, data_len - MESSAGE_HEADER_LEN);
+}
+
+TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key)) {
+}
+
+TSIGContext::TSIGContext(const Name& key_name, const Name& algorithm_name,
+ const TSIGKeyRing& keyring) : impl_(NULL) {
+ const TSIGKeyRing::FindResult result(keyring.find(key_name,
+ algorithm_name));
+ if (result.code == TSIGKeyRing::NOTFOUND) {
+ // If not key is found, create a dummy key with the specified key
+ // parameters and empty secret. In the common scenario this will
+ // be used in subsequent response with a TSIG indicating a BADKEY
+ // error.
+ impl_ = new TSIGContextImpl(TSIGKey(key_name, algorithm_name,
+ NULL, 0), TSIGError::BAD_KEY());
+ } else {
+ impl_ = new TSIGContextImpl(*result.key);
+ }
+}
+
+TSIGContext::~TSIGContext() {
+ delete impl_;
+}
+
+size_t
+TSIGContext::getTSIGLength() const {
+ //
+ // The space required for an TSIG record is:
+ //
+ // n1 bytes for the (key) name
+ // 2 bytes for the type
+ // 2 bytes for the class
+ // 4 bytes for the ttl
+ // 2 bytes for the rdlength
+ // n2 bytes for the algorithm name
+ // 6 bytes for the time signed
+ // 2 bytes for the fudge
+ // 2 bytes for the MAC size
+ // x bytes for the MAC
+ // 2 bytes for the original id
+ // 2 bytes for the error
+ // 2 bytes for the other data length
+ // y bytes for the other data (at most)
+ // ---------------------------------
+ // 26 + n1 + n2 + x + y bytes
+ //
+
+ // Normally the digest length ("x") is the length of the underlying
+ // hash output. If a key related error occurred, however, the
+ // corresponding TSIG will be "unsigned", and the digest length will be 0.
+ const size_t digest_len =
+ (impl_->error_ == TSIGError::BAD_KEY() ||
+ impl_->error_ == TSIGError::BAD_SIG()) ? 0 : impl_->digest_len_;
+
+ // Other Len ("y") is normally 0; if BAD_TIME error occurred, the
+ // subsequent TSIG will contain 48 bits of the server current time.
+ const size_t other_len = (impl_->error_ == TSIGError::BAD_TIME()) ? 6 : 0;
+
+ return (26 + impl_->key_.getKeyName().getLength() +
+ impl_->key_.getAlgorithmName().getLength() +
+ digest_len + other_len);
+}
+
+TSIGContext::State
+TSIGContext::getState() const {
+ return (impl_->state_);
+}
+
+TSIGError
+TSIGContext::getError() const {
+ return (impl_->error_);
+}
+
+ConstTSIGRecordPtr
+TSIGContext::sign(const uint16_t qid, const void* const data,
+ const size_t data_len) {
+ if (impl_->state_ == VERIFIED_RESPONSE) {
+ isc_throw(TSIGContextError,
+ "TSIG sign attempt after verifying a response");
+ }
+
+ if (data == NULL || data_len == 0) {
+ isc_throw(InvalidParameter, "TSIG sign error: empty data is given");
+ }
+
+ TSIGError error(TSIGError::NOERROR());
+ const uint64_t now = getTSIGTime();
+
+ // For responses adjust the error code.
+ if (impl_->state_ == RECEIVED_REQUEST) {
+ error = impl_->error_;
+ }
+
+ // For errors related to key or MAC, return an unsigned response as
+ // specified in Section 4.3 of RFC2845.
+ if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
+ ConstTSIGRecordPtr tsig(new TSIGRecord(
+ impl_->key_.getKeyName(),
+ any::TSIG(impl_->key_.getAlgorithmName(),
+ now, DEFAULT_FUDGE, 0, NULL,
+ qid, error.getCode(), 0, NULL)));
+ impl_->previous_digest_.clear();
+ impl_->state_ = SENT_RESPONSE;
+ return (tsig);
+ }
+
+ HMACPtr hmac(impl_->createHMAC());
+
+ // If the context has previous MAC (either the Request MAC or its own
+ // previous MAC), digest it.
+ if (impl_->state_ != INIT) {
+ impl_->digestPreviousMAC(hmac);
+ }
+
+ // Digest the message (without TSIG)
+ hmac->update(data, data_len);
+
+ // Digest TSIG variables.
+ // First, prepare some non constant variables.
+ const uint64_t time_signed = (error == TSIGError::BAD_TIME()) ?
+ impl_->previous_timesigned_ : now;
+ // For BADTIME error, we include 6 bytes of other data.
+ // (6 bytes = size of time signed value)
+ const uint16_t otherlen = (error == TSIGError::BAD_TIME()) ? 6 : 0;
+ OutputBuffer otherdatabuf(otherlen);
+ if (error == TSIGError::BAD_TIME()) {
+ otherdatabuf.writeUint16(now >> 32);
+ otherdatabuf.writeUint32(now & 0xffffffff);
+ }
+ const void* const otherdata =
+ (otherlen == 0) ? NULL : otherdatabuf.getData();
+ // Then calculate the digest. If state_ is SENT_RESPONSE we are sending
+ // a continued message in the same TCP stream so skip digesting
+ // variables except for time related variables (RFC2845 4.4).
+ impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+ TSIGRecord::TSIG_TTL, time_signed,
+ DEFAULT_FUDGE, error.getCode(),
+ otherlen, otherdata,
+ impl_->state_ == SENT_RESPONSE);
+
+ // Get the final digest, update internal state, then finish.
+ vector<uint8_t> digest = hmac->sign(impl_->digest_len_);
+ assert(digest.size() <= 0xffff); // cryptolink API should have ensured it.
+ ConstTSIGRecordPtr tsig(new TSIGRecord(
+ impl_->key_.getKeyName(),
+ any::TSIG(impl_->key_.getAlgorithmName(),
+ time_signed, DEFAULT_FUDGE,
+ digest.size(), &digest[0],
+ qid, error.getCode(), otherlen,
+ otherdata)));
+ // Exception free from now on.
+ impl_->previous_digest_.swap(digest);
+ impl_->state_ = (impl_->state_ == INIT) ? SENT_REQUEST : SENT_RESPONSE;
+ return (tsig);
+}
+
+TSIGError
+TSIGContext::verify(const TSIGRecord* const record, const void* const data,
+ const size_t data_len) {
+ if (impl_->state_ == SENT_RESPONSE) {
+ isc_throw(TSIGContextError,
+ "TSIG verify attempt after sending a response");
+ }
+
+ if (record == NULL) {
+ if (impl_->last_sig_dist_ >= 0 && impl_->last_sig_dist_ < 99) {
+ // It is not signed, but in the middle of TCP stream. We just
+ // update the HMAC state and consider this message OK.
+ update(data, data_len);
+ // This one is not signed, the last signed is one message further
+ // now.
+ impl_->last_sig_dist_++;
+ // No digest to return now. Just say it's OK.
+ return (impl_->postVerifyUpdate(TSIGError::NOERROR(), NULL, 0));
+ }
+ // This case happens when we sent a signed request and have received an
+ // unsigned response. According to RFC2845 Section 4.6 this case should be
+ // considered a "format error" (although the specific error code
+ // wouldn't matter much for the caller).
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), NULL, 0));
+ }
+
+ const any::TSIG& tsig_rdata = record->getRdata();
+
+ // Reject some obviously invalid data
+ if (data_len < MESSAGE_HEADER_LEN + record->getLength()) {
+ isc_throw(InvalidParameter,
+ "TSIG verify: data length is invalid: " << data_len);
+ }
+ if (data == NULL) {
+ isc_throw(InvalidParameter, "TSIG verify: empty data is invalid");
+ }
+
+ // This message is signed and we won't throw any more.
+ impl_->last_sig_dist_ = 0;
+
+ // Check key: whether we first verify it with a known key or we verify
+ // it using the consistent key in the context. If the check fails we are
+ // done with BADKEY.
+ if (impl_->state_ == INIT && impl_->error_ == TSIGError::BAD_KEY()) {
+ return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), NULL, 0));
+ }
+ if (impl_->key_.getKeyName() != record->getName() ||
+ impl_->key_.getAlgorithmName() != tsig_rdata.getAlgorithm()) {
+ return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), NULL, 0));
+ }
+
+ // Check time: the current time must be in the range of
+ // [time signed - fudge, time signed + fudge]. Otherwise verification
+ // fails with BADTIME. (RFC2845 Section 4.6.2)
+ // Note: for simplicity we don't explicitly catch the case of too small
+ // current time causing underflow. With the fact that fudge is quite
+ // small and (for now) non configurable, it shouldn't be a real concern
+ // in practice.
+ const uint64_t now = getTSIGTime();
+ if (tsig_rdata.getTimeSigned() + DEFAULT_FUDGE < now ||
+ tsig_rdata.getTimeSigned() - DEFAULT_FUDGE > now) {
+ const void* digest = NULL;
+ size_t digest_len = 0;
+ if (impl_->state_ == INIT) {
+ digest = tsig_rdata.getMAC();
+ digest_len = tsig_rdata.getMACSize();
+ impl_->previous_timesigned_ = tsig_rdata.getTimeSigned();
+ }
+ return (impl_->postVerifyUpdate(TSIGError::BAD_TIME(), digest,
+ digest_len));
+ }
+
+ // Handling empty MAC. While RFC2845 doesn't explicitly prohibit other
+ // cases, it can only reasonably happen in a response with BADSIG or
+ // BADKEY. We reject other cases as if it were BADSIG to avoid unexpected
+ // acceptance of a bogus signature. This behavior follows the BIND 9
+ // implementation.
+ if (tsig_rdata.getMACSize() == 0) {
+ TSIGError error = TSIGError(tsig_rdata.getError());
+ if (error != TSIGError::BAD_SIG() && error != TSIGError::BAD_KEY()) {
+ error = TSIGError::BAD_SIG();
+ }
+ return (impl_->postVerifyUpdate(error, NULL, 0));
+ }
+
+ HMACPtr hmac(impl_->createHMAC());
+
+ // If the context has previous MAC (either the Request MAC or its own
+ // previous MAC), digest it.
+ if (impl_->state_ != INIT) {
+ impl_->digestPreviousMAC(hmac);
+ }
+
+ // Signature length check based on RFC 4635 3.1
+ if (tsig_rdata.getMACSize() > hmac->getOutputLength()) {
+ // signature length too big
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), NULL, 0));
+ }
+ if ((tsig_rdata.getMACSize() < 10) ||
+ (tsig_rdata.getMACSize() < (hmac->getOutputLength() / 2))) {
+ // signature length below minimum
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), NULL, 0));
+ }
+ if (tsig_rdata.getMACSize() < impl_->digest_len_) {
+ // (truncated) signature length too small
+ return (impl_->postVerifyUpdate(TSIGError::BAD_TRUNC(), NULL, 0));
+ }
+
+ //
+ // Digest DNS message (excluding the trailing TSIG RR and adjusting the
+ // QID and ARCOUNT header fields)
+ //
+ impl_->digestDNSMessage(hmac, tsig_rdata.getOriginalID(),
+ data, data_len - record->getLength());
+
+ // Digest TSIG variables. If state_ is VERIFIED_RESPONSE, it's a
+ // continuation of the same TCP stream and skip digesting them except
+ // for time related variables (RFC2845 4.4).
+ // Note: we use the constant values for RR class and TTL specified
+ // in RFC2845, not received values (we reject other values in constructing
+ // the TSIGRecord).
+ impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+ TSIGRecord::TSIG_TTL,
+ tsig_rdata.getTimeSigned(),
+ tsig_rdata.getFudge(), tsig_rdata.getError(),
+ tsig_rdata.getOtherLen(),
+ tsig_rdata.getOtherData(),
+ impl_->state_ == VERIFIED_RESPONSE);
+
+ // Verify the digest with the received signature.
+ if (hmac->verify(tsig_rdata.getMAC(), tsig_rdata.getMACSize())) {
+ return (impl_->postVerifyUpdate(TSIGError::NOERROR(),
+ tsig_rdata.getMAC(),
+ tsig_rdata.getMACSize()));
+ }
+
+ return (impl_->postVerifyUpdate(TSIGError::BAD_SIG(), NULL, 0));
+}
+
+bool
+TSIGContext::lastHadSignature() const {
+ if (impl_->last_sig_dist_ == -1) {
+ isc_throw(TSIGContextError, "No message was verified yet");
+ }
+ return (impl_->last_sig_dist_ == 0);
+}
+
+void
+TSIGContext::update(const void* const data, size_t len) {
+ HMACPtr hmac(impl_->createHMAC());
+ // Use the previous digest and never use it again
+ impl_->digestPreviousMAC(hmac);
+ impl_->previous_digest_.clear();
+ // Push the message there
+ hmac->update(data, len);
+ impl_->hmac_ = hmac;
+}
+
+} // namespace dns
+} // namespace isc