diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib/dns/tsig.h | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h new file mode 100644 index 0000000..ed74e77 --- /dev/null +++ b/src/lib/dns/tsig.h @@ -0,0 +1,445 @@ +// 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/. + +// IMPORTANT: the server side of this code MUST NOT be used until +// it was fixed, cf RFC 8945. Note that Kea uses only the client side. + +#ifndef TSIG_H +#define TSIG_H 1 + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> + +#include <exceptions/exceptions.h> + +#include <dns/tsigerror.h> +#include <dns/tsigkey.h> +#include <dns/tsigrecord.h> + +namespace isc { +namespace dns { + +/// An exception that is thrown for logic errors identified in TSIG +/// sign/verify operations. +/// +/// Note that this exception is not thrown for TSIG protocol errors such as +/// verification failures. In general, this exception indicates an internal +/// program bug. +class TSIGContextError : public isc::Exception { +public: + TSIGContextError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// TSIG session context. +/// +/// The \c TSIGContext class maintains a context of a signed session of +/// DNS transactions by TSIG. In many cases a TSIG signed session consists +/// of a single set of request (e.g. normal query) and reply (e.g. normal +/// response), where the request is initially signed by the client, and the +/// reply is signed by the server using the initial signature. As mentioned +/// in RFC2845, a session can consist of multiple exchanges in a TCP +/// connection. As also mentioned in the RFC, an AXFR response often contains +/// multiple DNS messages, which can belong to the same TSIG session. +/// This class supports all these cases. +/// +/// A \c TSIGContext object is generally constructed with a TSIG key to be +/// used for the session, and keeps track of various kinds of session specific +/// information, such as the original digest while waiting for a response or +/// verification error information that is to be used for a subsequent +/// response. +/// +/// This class has two main methods, \c sign() and \c verify(). +/// The \c sign() method signs given data (which is supposed to be a complete +/// DNS message without the TSIG itself) using the TSIG key and other +/// related information associated with the \c TSIGContext object. +/// The \c verify() method verifies a given DNS message that contains a TSIG +/// RR using the key and other internal information. +/// +/// In general, a DNS client that wants to send a signed query will construct +/// a \c TSIGContext object with the TSIG key that the client is intending to +/// use, and sign the query with the context. The client will keeps the +/// context, and verify the response with it. +/// +/// On the other hand, a DNS server will construct a \c TSIGContext object +/// with the information of the TSIG RR included in a query with a set of +/// possible keys (in the form of a \c TSIGKeyRing object). The constructor +/// in this mode will identify the appropriate TSIG key (or internally record +/// an error if it doesn't find a key). The server will then verify the +/// query with the context, and generate a signed response using the same +/// same context. +/// +/// When multiple messages belong to the same TSIG session, either side +/// (signer or verifier) will keep using the same context. It records +/// the latest session state (such as the previous digest) so that repeated +/// calls to \c sign() or \c verify() work correctly in terms of the TSIG +/// protocol. +/// +/// \b Examples +/// +/// This is a typical client application that sends a TSIG signed query +/// and verifies the response. +/// +/// \code +/// // "renderer" is of MessageRenderer to render the message. +/// // (TSIGKey would be configured from config or command line in real app) +/// TSIGContext ctx(TSIGKey("key.example:MSG6Ng==")); +/// Message message(Message::RENDER); +/// message.addQuestion(Question(Name("www.example.com"), RRClass::IN(), +/// RRType::A())); +/// message.toWire(renderer, ctx); +/// +/// // sendto, then recvfrom. received result in (data, data_len) +/// +/// message.clear(Message::PARSE); +/// InputBuffer buffer(data, data_len); +/// message.fromWire(buffer); +/// TSIGError tsig_error = ctx.verify(message.getTSIGRecord(), +/// data, data_len); +/// if (tsig_error == TSIGError::NOERROR()) { +/// // okay. ctx can be continuously used if it's receiving subsequent +/// // signed responses from a TCP stream. +/// } else if (message.getRcode() == Rcode::NOTAUTH()) { +/// // hard error. give up this transaction per RFC2845 4.6. +/// } else { +/// // Other error: discard response keep waiting with the same ctx +/// // for another (again, RFC2845 4.6). +/// } \endcode +/// +/// And this is a typical server application that authenticates a signed +/// query and returns a response according to the result. +/// +/// \code +/// // Assume "message" is of type Message for query handling and +/// // "renderer" is of MessageRenderer to render responses. +/// Message message(Message::RENDER); +/// +/// TSIGKeyRing keyring; // this must be configured with keys somewhere +/// +/// // Receive a query and store it in (data, data_len) +/// InputBuffer buffer(data, data_len); +/// message.clear(Message::PARSE); +/// message.fromWire(buffer); +/// +/// const TSIGRecord* tsig = message.getTSIGRecord(); +/// if (tsig) { +/// TSIGContext ctx(tsig->getName(), tsig->getRdata().getAlgorithm(), +/// keyring); +/// ctx.verify(tsig, data, data_len); +/// +/// // prepare response +/// message.makeResponse(); +/// //... +/// message.toWire(renderer, ctx); +/// +/// // send the response data back to the client. +/// // If this is a beginning of a signed session over a TCP and +/// // server has more data to send to the client, this ctx +/// // will be used to sign subsequent messages. +/// } \endcode +/// +/// <b>TCP Consideration</b> +/// +/// RFC2845 describes the case where a single TSIG session is used for +/// multiple DNS messages (Section 4.4). This class supports signing and +/// verifying the messages in this scenario, but does not care if the messages +/// were delivered over a TCP connection or not. If, for example, the +/// same \c TSIGContext object is used to sign two independent DNS queries +/// sent over UDP, they will be considered to belong to the same TSIG +/// session, and, as a result, verification will be likely to fail. +/// +/// \b Copyability +/// +/// This class is currently non copyable based on the observation of the +/// typical usage as described above. But there is no strong technical +/// reason why this class cannot be copyable. If we see the need for it +/// in future we may change the implementation on this point. +/// +/// <b>Note to developers:</b> +/// One basic design choice is to make the \c TSIGContext class is as +/// independent from the \c Message class. This is because the latter is +/// much more complicated, depending on many other classes, while TSIG is +/// a very specific part of the entire DNS protocol set. If the \c TSIGContext +/// class depends on \c \c Message, it will be more vulnerable to changes +/// to other classes, and will be more difficult to test due to the +/// direct or indirect dependencies. The interface of \c sign() that takes +/// opaque data (instead of, e.g., a \c Message or \c MessageRenderer object) +/// is therefore a deliberate design decision. +class TSIGContext : boost::noncopyable { +public: + /// Internal state of context + /// + /// The constants of this enum type define a specific state of + /// \c TSIGContext to adjust the behavior. The definition is public + /// and the state can be seen via the \c getState() method, but this is + /// mostly private information. It's publicly visible mainly for testing + /// purposes; there is no API for the application to change the state + /// directly. + enum State { + INIT, ///< Initial state + SENT_REQUEST, ///< Client sent a signed request, waiting response + RECEIVED_REQUEST, ///< Server received a signed request + SENT_RESPONSE, ///< Server sent a signed response + VERIFIED_RESPONSE ///< Client successfully verified a response + }; + + /// \name Constructors and destructor + /// + //@{ + /// Constructor from a TSIG key. + /// + /// \exception std::bad_alloc Resource allocation for internal data fails + /// + /// \param key The TSIG key to be used for TSIG sessions with this context. + explicit TSIGContext(const TSIGKey& key); + + /// Constructor from key parameters and key ring. + TSIGContext(const Name& key_name, const Name& algorithm_name, + const TSIGKeyRing& keyring); + + /// The destructor. + virtual ~TSIGContext(); + //@} + + /// Sign a DNS message. + /// + /// This method computes the TSIG MAC for the given data, which is + /// generally expected to be a complete, wire-format DNS message + /// that doesn't contain a TSIG RR, based on the TSIG key and + /// other context information of \c TSIGContext, and returns a + /// result in the form of a (pointer object pointing to) + /// \c TSIGRecord object. + /// + /// The caller of this method will use the returned value to render a + /// complete TSIG RR into the message that has been signed so that it + /// will become a complete TSIG-signed message. + /// + /// In general, this method is called once by a client to send a + /// signed request or one more times by a server to sign + /// response(s) to a signed request. To avoid allowing accidental + /// misuse, if this method is called after a "client" validates a + /// response, an exception of class \c TSIGContextError will be + /// thrown. + /// + /// \note Normal applications are not expected to call this method + /// directly; they will usually use the \c Message::toWire() method + /// with a \c TSIGContext object being a parameter and have the + /// \c Message class create a complete signed message. + /// + /// This method treats the given data as opaque, even though it's generally + /// expected to represent a wire-format DNS message (see also the class + /// description), and doesn't inspect it in any way. For example, it + /// doesn't check whether the data length is sane for a valid DNS message. + /// This is also the reason why this method takes the \c qid parameter, + /// which will be used as the original ID of the resulting + /// \c TSIGRecordx object, even though this value should be stored in the + /// first two octets (in wire format) of the given data. + /// + /// \note This method still checks and rejects empty data (null pointer + /// data or the specified data length is 0) in order to avoid catastrophic + /// effect such as program crash. Empty data is not necessarily invalid + /// for HMAC computation, but obviously it doesn't make sense for a DNS + /// message. + /// + /// This method provides the strong exception guarantee; unless the method + /// returns (without an exception being thrown), the internal state of + /// the \c TSIGContext won't be modified. + /// + /// \exception TSIGContextError Context already verified a response. + /// \exception InvalidParameter \c data is 0 or \c data_len is 0 + /// \exception cryptolink::LibraryError Some unexpected error in the + /// underlying crypto operation + /// \exception std::bad_alloc Temporary resource allocation failure + /// + /// \param qid The QID to be as the value of the original ID field of + /// the resulting TSIG record + /// \param data Points to the wire-format data to be signed + /// \param data_len The length of \c data in bytes + /// + /// \return A TSIG record for the given data along with the context. + virtual ConstTSIGRecordPtr + sign(const uint16_t qid, const void* const data, const size_t data_len); + + /// Verify a DNS message. + /// + /// This method verifies given data along with the context and a given + /// TSIG in the form of a \c TSIGRecord object. The data to be verified + /// is generally expected to be a complete, wire-format DNS message, + /// exactly as received by the host, and ending with a TSIG RR. + /// After verification process this method updates its internal state, + /// and returns the result in the form of a \c TSIGError object. + /// Possible return values are (see the \c TSIGError class description + /// for the mnemonics): + /// + /// - \c NOERROR: The data has been verified correctly. + /// - \c FORMERR: \c TSIGRecord is not given (see below). + /// - \c BAD_KEY: Appropriate key is not found or specified key doesn't + /// match for the data. + /// - \c BAD_TIME: The current time doesn't fall in the range specified + /// in the TSIG. + /// - \c BAD_SIG: The signature given in the TSIG doesn't match against + /// the locally computed digest or is the signature is + /// invalid in other way. + /// - \c BAD_MODE: Not yet implemented TKEY error + /// - \c BAD_NAME: Not yet implemented TKEY error + /// - \c BAD_ALG: Not yet implemented TKEY error + /// - \c BAD_TRUNC: The signature or truncated signature length is too + /// small. + /// + /// If this method is called by a DNS client waiting for a signed + /// response and the result is not \c NOERROR, the context can be used + /// to try validating another signed message as described in RFC2845 + /// Section 4.6. + /// + /// If this method is called by a DNS server that tries to authenticate + /// a signed request, and if the result is not \c NOERROR, the + /// corresponding error condition is recorded in the context so that + /// the server can return a response indicating what was wrong by calling + /// \c sign() with the updated context. + /// + /// In general, this method is called once by a server for + /// authenticating a signed request or one more times by a client to + /// validate signed response(s) to a signed request. To avoid allowing + /// accidental misuse, if this method is called after a "server" signs + /// a response, an exception of class \c TSIGContextError will be thrown. + /// + /// The \c record parameter can be 0; in that case this method simply + /// returns \c FORMERR as the case described in Section 4.6 of RFC2845, + /// i.e., receiving an unsigned response to a signed request. This way + /// a client can transparently pass the result of + /// \c Message::getTSIGRecord() without checking whether it isn't 0 + /// and take an appropriate action based on the result of this method. + /// + /// This method handles the given data mostly as opaque. It digests + /// the data assuming it begins with a DNS header and ends with a TSIG + /// RR whose length is given by calling \c TSIGRecord::getLength() on + /// \c record, but otherwise it doesn't parse the data to confirm the + /// assumption. It's caller's responsibility to ensure the data is + /// valid and consistent with \c record. To avoid disruption, this + /// method performs minimal validation on the given \c data and \c record: + /// \c data must not be 0; \c data_len must not be smaller than the + /// sum of the DNS header length (fixed, 12 octets) and the length of + /// the TSIG RR. If this check fails it throws an \c InvalidParameter + /// exception. + /// + /// One unexpected case that is not covered by this method is that a + /// client receives a signed response to an unsigned request. RFC2845 is + /// silent about such cases; BIND 9 explicitly identifies the case and + /// rejects it. With this implementation, the client can know that the + /// response contains a TSIG via the result of + /// \c Message::getTSIGRecord() and that it is an unexpected TSIG due to + /// the fact that it doesn't have a corresponding \c TSIGContext. + /// It's up to the client implementation whether to react to such a case + /// explicitly (for example, it could either ignore the TSIG and accept + /// the response or drop it). + /// + /// This method provides the strong exception guarantee; unless the method + /// returns (without an exception being thrown), the internal state of + /// the \c TSIGContext won't be modified. + /// + /// \todo Signature truncation support based on RFC4635 + /// + /// \exception TSIGContextError Context already signed a response. + /// \exception InvalidParameter \c data is 0 or \c data_len is too small. + /// + /// \param record The \c TSIGRecord to be verified with \c data + /// \param data Points to the wire-format data (exactly as received) to + /// be verified + /// \param data_len The length of \c data in bytes + /// \return The \c TSIGError that indicates verification result + virtual TSIGError + verify(const TSIGRecord* const record, const void* const data, const size_t data_len); + + /// \brief Check whether the last verified message was signed. + /// + /// RFC2845 allows for some of the messages not to be signed. However, + /// the last message must be signed and the class has no knowledge if a + /// given message is the last one, therefore it can't check directly. + /// + /// It is up to the caller to check if the last verified message was signed + /// after all are verified by calling this function. + /// + /// \return If the last message was signed or not. + /// \exception TSIGContextError if no message was verified yet. + virtual bool lastHadSignature() const; + + /// Return the expected length of TSIG RR after \c sign() + /// + /// This method returns the length of the TSIG RR that would be + /// produced as a result of \c sign() with the state of the context + /// at the time of the call. The expected length can be decided + /// from the key and the algorithm (which determines the MAC size if + /// included) and the recorded TSIG error. Specifically, if a key + /// related error has been identified, the MAC will be excluded; if + /// a time error has occurred, the TSIG will include "other data". + /// + /// This method is provided mainly for the convenience of the Message + /// class, which needs to know the expected TSIG length in rendering a + /// signed DNS message so that it can handle truncated messages with TSIG + /// correctly. Normal applications wouldn't need this method. The Python + /// binding for this method won't be provided for the same reason. + /// + /// \exception None + /// + /// \return The expected TSIG RR length in bytes + virtual size_t getTSIGLength() const; + + /// Return the current state of the context + /// + /// \note + /// The states are visible in public mainly for testing purposes. + /// Normal applications won't have to deal with them. + /// + /// \exception None + virtual State getState() const; + + /// Return the TSIG error as a result of the latest verification + /// + /// This method can be called even before verifying anything, but the + /// returned value is meaningless in that case. + /// + /// \exception None + virtual TSIGError getError() const; + + /// \name Protocol constants and defaults + /// + //@{ + /// The recommended fudge value (in seconds) by RFC2845. + /// + /// Right now fudge is not tunable, and all TSIGs generated by this API + /// will have this value of fudge. + static const uint16_t DEFAULT_FUDGE = 300; + //@} + +protected: + /// \brief Update internal HMAC state by more data. + /// + /// This is used mostly internally, when we need to verify a message without + /// TSIG signature in the middle of signed TCP stream. However, it is also + /// used in tests, so it's protected instead of private, to allow tests + /// in. + /// + /// It doesn't contain sanity checks, and it is not tested directly. But + /// we may want to add these one day to allow generating the skipped TSIG + /// messages too. Until then, do not use this method. + void update(const void* const data, size_t len); + +private: + struct TSIGContextImpl; + TSIGContextImpl* impl_; +}; + +typedef boost::shared_ptr<TSIGContext> TSIGContextPtr; +typedef boost::shared_ptr<TSIGKey> TSIGKeyPtr; + +} +} + +#endif // TSIG_H + +// Local Variables: +// mode: c++ +// End: |