diff options
Diffstat (limited to 'src/lib/dns/tsigrecord.h')
-rw-r--r-- | src/lib/dns/tsigrecord.h | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/lib/dns/tsigrecord.h b/src/lib/dns/tsigrecord.h new file mode 100644 index 0000000..0d5a375 --- /dev/null +++ b/src/lib/dns/tsigrecord.h @@ -0,0 +1,300 @@ +// Copyright (C) 2011-2015 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/. + +#ifndef TSIGRECORD_H +#define TSIGRECORD_H 1 + +#include <ostream> +#include <string> + +#include <boost/shared_ptr.hpp> + +#include <util/buffer.h> + +#include <dns/name.h> +#include <dns/rdataclass.h> + +namespace isc { +namespace util { +class OutputBuffer; +} +namespace dns { +class AbstractMessageRenderer; + +/// TSIG resource record. +/// +/// A \c TSIGRecord class object represents a TSIG resource record and is +/// responsible for conversion to and from wire format TSIG record based on +/// the protocol specification (RFC2845). +/// This class is provided so that other classes and applications can handle +/// TSIG without knowing protocol details of TSIG, such as that it uses a +/// fixed constant of TTL. +/// +/// \todo So the plan is to eventually provide the "from wire" constructor. +/// It's not yet provided in the current phase of development. +/// +/// \note +/// This class could be a derived class of \c AbstractRRset. That way +/// it would be able to be used in a polymorphic way; for example, +/// an application can construct a TSIG RR by itself and insert it to a +/// \c Message object as a generic RRset. On the other hand, it would mean +/// this class would have to implement an \c RdataIterator (even though it +/// can be done via straightforward forwarding) while the iterator is mostly +/// redundant since there should be one and only one RDATA for a valid TSIG +/// RR. Likewise, some methods such as \c setTTL() method wouldn't be well +/// defined due to such special rules for TSIG as using a fixed TTL. +/// Overall, TSIG is a very special RR type that simply uses the compatible +/// resource record format, and it will be unlikely that a user wants to +/// handle it through a generic interface in a polymorphic way. +/// We therefore chose to define it as a separate class. This is also +/// similar to why \c EDNS is a separate class. +class TSIGRecord { +public: + /// + /// \name Constructors + /// + /// We use the default copy constructor, default copy assignment operator, + /// (and default destructor) intentionally. + //@{ + /// Constructor from TSIG key name and RDATA + /// + /// \exception std::bad_alloc Resource allocation for copying the name or + /// RDATA fails + TSIGRecord(const Name& key_name, const rdata::any::TSIG& tsig_rdata); + + /// Constructor from resource record (RR) parameters. + /// + /// This constructor is intended to be used in the context of parsing + /// an incoming DNS message that contains a TSIG. The parser would + /// first extract the owner name, RR type (which is TSIG) class, TTL and + /// the TSIG RDATA from the message. This constructor is expected to + /// be given these RR parameters (except the RR type, because it must be + /// TSIG). + /// + /// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0). + /// If the RR class or TTL is different from the expected one, this + /// implementation considers it an invalid record and throws an exception + /// of class \c DNSMessageFORMERR. + /// + /// \note This behavior is not specified in the protocol specification, + /// but this implementation rejects unexpected values for the following + /// reasons (but in any case, this won't matter much in practice as + /// RFC2848 clearly states these fields always have the fixed values and + /// any sane implementation of TSIG signer will follow that): + /// According to the RFC editor (in a private communication), the intended + /// use of the TSIG TTL field is to signal protocol extensions (currently + /// no such extension is defined), so this field may actually be + /// validly non 0 in future. However, until the implementation supports + /// that extension it may not always be able to handle the extended + /// TSIG as intended; the extension may even affect digest computation. + /// There's a related issue on this point. Different implementations + /// interpret the RFC in different ways on the received TTL when + /// digesting the message: BIND 9 uses the received value (even if + /// it's not 0) as part of the TSIG variables; NLnet Labs' LDNS and NSD + /// always use a fixed constant of 0 regardless of the received TTL value. + /// This means if and when an extension with non 0 TTL is introduced + /// there will be interoperability problems in the form of verification + /// failure. By explicitly rejecting it (and subsequently returning + /// a response with a format error) we can indicate the source of the + /// problem more clearly than a "bad signature" TSIG error, which can + /// happen for various reasons. On the other hand, rejecting unexpected + /// RR classes is mostly for consistency; the RFC lists these two fields + /// in the same way, so it makes more sense to handle them equally. + /// (BIND 9 rejects unexpected RR classes for TSIG, but that is part of + /// general check on RR classes on received RRs; it generally requests + /// all classes are the same, and if the protocol specifies the use of + /// a particular class for a particular type of RR, it requests that + /// class be used). + /// + /// Likewise, if \c rdata is not of type \c any::TSIG, an exception of + /// class DNSMessageFORMERR will be thrown. When the caller is a + /// DNS message parser and builds \c rdata from incoming wire format + /// data as described above, this case happens when the RR class is + /// different from ANY (in the implementation, the type check takes place + /// before the explicit check against the RR class explained in the + /// previous paragraph). + /// + /// The \c length parameter is intended to be the length of the TSIG RR + /// (from the beginning of the owner name to the end of the RDATA) when + /// the caller is a DNS message parser. Note that it is the actual length + /// for the RR in the format; if the owner name or the algorithm name + /// (in the RDATA) is compressed (although the latter should not be + /// compressed according to RFC3597), the length must be the size of the + /// compressed data. The length is recorded inside the class and will + /// be returned via subsequent calls to \c getLength(). It's intended to + /// be used in the context TSIG verification; in the verify process + /// the MAC computation must be performed for the original data without + /// TSIG, so, to avoid parsing the entire data in the verify process + /// again, it's necessary to record information that can identify the + /// length to be digested for the MAC. This parameter serves for that + /// purpose. + /// + /// \note Since the constructor doesn't take the wire format data per se, + /// it doesn't (and cannot) check the validity of \c length, and simply + /// accepts any given value. It even accepts obviously invalid values + /// such as 0. It's caller's responsibility to provide a valid value of + /// length, and, the verifier's responsibility to use the length safely. + /// + /// <b>DISCUSSION:</b> this design is fragile in that it introduces + /// a tight coupling between message parsing and TSIG verification via + /// the \c TSIGRecord class. In terms of responsibility decoupling, + /// the ideal way to have \c TSIGRecord remember the entire wire data + /// along with the length of the TSIG. Then in the TSIG verification + /// we could refer to the necessary potion of data solely from a + /// \c TSIGRecord object. However, this approach would require expensive + /// heavy copy of the original data or introduce another kind of coupling + /// between the data holder and this class (if the original data is freed + /// while a \c TSIGRecord object referencing the data still exists, the + /// result will be catastrophic). As a "best current compromise", we + /// use the current design. We may reconsider it if it turns out to + /// cause a big problem or we come up with a better idea. + /// + /// \exception DNSMessageFORMERR Given RR parameters are invalid for TSIG. + /// \exception std::bad_alloc Internal resource allocation fails. + /// + /// \param name The owner name of the TSIG RR + /// \param rrclass The RR class of the RR. Must be \c RRClass::ANY() + /// (see above) + /// \param ttl The TTL of the RR. Must be 0 (see above) + /// \param rdata The RDATA of the RR. Must be of type \c any::TSIG. + /// \param length The size of the RR (see above) + TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl, + const rdata::Rdata& rdata, size_t length); + //@} + + /// Return the owner name of the TSIG RR, which is the TSIG key name + /// + /// \exception None + const Name& getName() const { return (key_name_); } + + /// Return the RDATA of the TSIG RR + /// + /// \exception None + const rdata::any::TSIG& getRdata() const { return (rdata_); } + + /// \name Protocol constants and defaults + /// + //@{ + /// Return the RR class of TSIG + /// + /// TSIG always uses the ANY RR class. This static method returns it, + /// when, though unlikely, an application wants to know which class TSIG + /// is supposed to use. + /// + /// \exception None + static const RRClass& getClass(); + + /// Return the TTL value of TSIG + /// + /// TSIG always uses 0 TTL. This static method returns it, + /// when, though unlikely, an application wants to know the TTL TSIG + /// is supposed to use. + /// + /// \exception None + static const RRTTL& getTTL(); + //@} + + /// Return the length of the TSIG record + /// + /// When constructed from the key name and RDATA, it is the length of + /// the record when it is rendered by the \c toWire() method. + /// + /// \note When constructed "from wire", that will mean the length of + /// the wire format data for the TSIG RR. The length will be necessary + /// to verify the message once parse is completed. + /// + /// \exception None + size_t getLength() const { return (length_); } + + /// \brief Render the \c TSIG RR in the wire format. + /// + /// This method renders the TSIG record as a form of a DNS TSIG RR + /// via \c renderer, which encapsulates output buffer and other rendering + /// contexts. + /// + /// Normally this version of \c toWire() method tries to compress the + /// owner name of the RR whenever possible, but this method intentionally + /// skips owner name compression. This is due to a report that some + /// Windows clients refuse a TSIG if its owner name is compressed + /// (See http://marc.info/?l=bind-workers&m=126637138430819&w=2). + /// Reportedly this seemed to be specific to GSS-TSIG, but this + /// implementation skip compression regardless of the algorithm. + /// + /// If by adding the TSIG RR the message size would exceed the limit + /// maintained in \c renderer, this method skips rendering the RR + /// and returns 0 and mark \c renderer as "truncated" (so that a + /// subsequent call to \c isTruncated() on \c renderer will result in + /// \c true); otherwise it returns 1, which is the number of RR + /// rendered. + /// + /// \note If the caller follows the specification of adding TSIG + /// as described in RFC2845, this should not happen; the caller is + /// generally expected to leave a sufficient room in the message for + /// the TSIG. But this method checks the unexpected case nevertheless. + /// + /// \exception std::bad_alloc Internal resource allocation fails (this + /// should be rare). + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \return 1 if the TSIG RR fits in the message size limit; otherwise 0. + int toWire(AbstractMessageRenderer& renderer) const; + + /// \brief Render the \c TSIG RR in the wire format. + /// + /// This method is same as \c toWire(AbstractMessageRenderer&)const + /// except it renders the RR in an \c OutputBuffer and therefore + /// does not care about message size limit. + /// As a consequence it always returns 1. + int toWire(isc::util::OutputBuffer& buffer) const; + + /// Convert the TSIG record to a string. + /// + /// The output format is the same as the result of \c toText() for + /// other normal types of RRsets (with always using the same RR class + /// and TTL). It also ends with a newline. + /// + /// \exception std::bad_alloc Internal resource allocation fails (this + /// should be rare). + /// + /// \return A string representation of \c TSIG record + std::string toText() const; + + /// The TTL value to be used in TSIG RRs. + static const uint32_t TSIG_TTL = 0; + //@} + +private: + const Name key_name_; + const rdata::any::TSIG rdata_; + const size_t length_; +}; + +/// A pointer-like type pointing to a \c TSIGRecord object. +typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr; + +/// A pointer-like type pointing to an immutable \c TSIGRecord object. +typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr; + +/// Insert the \c TSIGRecord as a string into stream. +/// +/// This method convert \c record into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param record A \c TSIGRecord object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const TSIGRecord& record); +} +} + +#endif // TSIGRECORD_H + +// Local Variables: +// mode: c++ +// End: |