Kea 3.1.1
gss_tsig_context.cc
Go to the documentation of this file.
1// Copyright (C) 2021-2025 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <dns/rrclass.h>
10#include <gss_tsig_context.h>
11#include <gss_tsig_log.h>
12#include <gss_tsig_messages.h>
13#include <util/io.h>
14
15#include <limits>
16
17using namespace isc;
18using namespace isc::d2;
19using namespace isc::dns;
20using namespace isc::dns::rdata::any;
21using namespace isc::gss_tsig;
22using namespace isc::log;
23using namespace isc::util;
24using namespace std;
25
26namespace {
27
29const size_t MESSAGE_HEADER_LEN = 12;
30
32const size_t DIGEST_LEN = 128;
33static_assert(DIGEST_LEN <= numeric_limits<uint16_t>::max(),
34 "DIGEST_LEN must fit in a uint16_t");
35
42void
43digestPreviousMAC(OutputBuffer& buffer, const vector<uint8_t>& previous_digest) {
44 if (previous_digest.empty()) {
45 // The previous digest was already used. We're in the middle of
46 // TCP stream somewhere and we already pushed some unsigned message
47 // into the MAC state.
48 return;
49 }
50
51 const uint16_t previous_digest_len(previous_digest.size());
52 buffer.writeUint16(previous_digest_len);
53 buffer.writeData(&previous_digest[0], previous_digest_len);
54}
55
68void
69digestTSIGVariables(OutputBuffer& buffer, const GssTsigKey& key,
70 uint16_t rrclass, uint32_t rrttl,
71 uint64_t time_signed, uint16_t fudge, uint16_t error,
72 uint16_t otherlen, const void* otherdata,
73 bool time_variables_only) {
74 if (!time_variables_only) {
75 key.getKeyName().toWire(buffer);
76 buffer.writeUint16(rrclass);
77 buffer.writeUint32(rrttl);
78 key.getAlgorithmName().toWire(buffer);
79 }
80 buffer.writeUint16(time_signed >> 32);
81 buffer.writeUint32(time_signed & 0xffffffff);
82 buffer.writeUint16(fudge);
83 if (!time_variables_only) {
84 buffer.writeUint16(error);
85 buffer.writeUint16(otherlen);
86 if (otherlen > 0) {
87 buffer.writeData(otherdata, otherlen);
88 }
89 }
90}
91
110void
111digestDNSMessage(OutputBuffer& buffer, uint16_t qid, const void* data,
112 size_t data_len) {
113 if (!data || (data_len < MESSAGE_HEADER_LEN)) {
114 // Should not happen as it was already checked by the caller.
115 isc_throw(Unexpected, "digestDNSMessage called with bad data");
116 }
117 OutputBuffer hdr(MESSAGE_HEADER_LEN);
118 const uint8_t* msgptr = static_cast<const uint8_t*>(data);
119
120 // Install the original ID.
121 hdr.writeUint16(qid);
122 msgptr += sizeof(uint16_t);
123
124 // Copy the rest of the header except the ARCOUNT field.
125 hdr.writeData(msgptr, 8);
126 msgptr += 8;
127
128 // Install the adjusted ARCOUNT (we don't care even if the value is bogus
129 // and it underflows; it would simply result in verification failure).
130 hdr.writeUint16(InputBuffer(msgptr, sizeof(uint16_t)).readUint16() - 1);
131 msgptr += 2;
132
133 // Digest the header and the rest of the DNS message.
134 buffer.writeData(hdr.getData(), hdr.getLength());
135 buffer.writeData(msgptr, data_len - MESSAGE_HEADER_LEN);
136}
137
138}
139
140namespace isc {
141namespace gss_tsig {
142
144 : TSIGContext(key), state_(INIT), key_(key), previous_digest_(),
145 error_(TSIGError::NOERROR()), previous_timesigned_(0),
146 last_sig_dist_(-1), tbs_(1024) {
147}
148
151
153GssTsigContext::sign(const uint16_t qid, const void* const data,
154 const size_t data_len) {
155 if (state_ == VERIFIED_RESPONSE) {
157 "TSIG sign attempt after verifying a response");
158 }
159
160 if (!data || (data_len == 0)) {
161 isc_throw(InvalidParameter, "TSIG sign error: empty data is given");
162 }
163
165 const uint64_t now = static_cast<uint64_t>(time(0));
166
167 // For responses adjust the error code.
168 if (state_ == RECEIVED_REQUEST) {
169 error = error_;
170 }
171
172 // For errors related to key or MAC, return an unsigned response as
173 // specified in Section 4.3 of RFC2845.
174 if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
176 tsig(new TSIGRecord(key_.getKeyName(),
177 TSIG(key_.getAlgorithmName(),
178 now, DEFAULT_FUDGE, 0, 0,
179 qid, error.getCode(), 0, 0)));
180 previous_digest_.clear();
181 state_ = SENT_RESPONSE;
182 return (tsig);
183 }
184
185 if (!key_.getSecCtx().get()) {
186 isc_throw(Unexpected, "sign called with null security context");
187 }
188
189 // If the context has previous MAC (either the Request MAC or its own
190 // previous MAC), digest it.
191 if (state_ != INIT) {
192 digestPreviousMAC(tbs_, previous_digest_);
193 }
194
195 // Digest the message (without TSIG).
196 tbs_.writeData(data, data_len);
197
198 // Digest TSIG variables.
199 // First, prepare some non constant variables.
200 uint64_t time_signed = now;
201 uint16_t otherlen = 0;
202 OutputBuffer otherdata(otherlen);
203 // For BADTIME error, we include 6 bytes of other data.
204 // (6 bytes = size of time signed value).
205 if (error == TSIGError::BAD_TIME()) {
206 time_signed = previous_timesigned_;
207 otherlen = 6;
208 otherdata.writeUint16(now >> 32);
209 otherdata.writeUint32(now & 0xffffffff);
210 }
211 // Then calculate the digest. If state_ is SENT_RESPONSE we are sending
212 // a continued message in the same TCP stream so skip digesting
213 // variables except for time related variables (RFC2845 4.4).
214 digestTSIGVariables(tbs_, key_,
215 TSIGRecord::getClass().getCode(),
216 TSIGRecord::TSIG_TTL, time_signed,
217 DEFAULT_FUDGE, error.getCode(),
218 otherlen, otherlen ? otherdata.getData() : 0,
219 state_ == SENT_RESPONSE);
220
221 // Get the final digest, update internal state, then finish.
222 GssApiBuffer gtbs(tbs_.getLength(), tbs_.getData());
223 // Consume the to be signed buffer.
224 tbs_.clear();
225 GssApiBuffer gsign;
226 key_.getSecCtx().sign(gtbs, gsign);
227 if (gsign.getLength() > DIGEST_LEN) {
228 isc_throw(OutOfRange, "internal error: digest is larger than "
229 << DIGEST_LEN);
230 }
232 tsig(new TSIGRecord(key_.getKeyName(),
233 TSIG(key_.getAlgorithmName(),
234 time_signed, DEFAULT_FUDGE,
235 gsign.getLength(), gsign.getValue(),
236 qid, error.getCode(), otherlen,
237 otherlen ? otherdata.getData() : 0)));
238 previous_digest_ = gsign.getContent();
239 if (state_ == INIT) {
240 state_ = SENT_REQUEST;
241 } else {
242 state_ = SENT_RESPONSE;
243 }
244 return (tsig);
245}
246
249 const void* const data, const size_t data_len) {
250 if (state_ == SENT_RESPONSE) {
252 "TSIG verify attempt after sending a response");
253 }
254
255 // This helper method is used from verify(). It's expected to be called
256 // just before verify() returns. It updates internal state based on
257 // the verification result and return the TSIGError to be returned to
258 // the caller of verify(), so that verify() can call this method within
259 // its 'return' statement.
260 auto postVerifyUpdate = [&] (TSIGError error) -> TSIGError {
261 if (state_ == INIT) {
262 state_ = RECEIVED_REQUEST;
263 } else if ((state_ == SENT_REQUEST) &&
264 (error == TSIGError::NOERROR())) {
265 state_ = VERIFIED_RESPONSE;
266 }
267 error_ = error;
268 return (error);
269 };
270
271 // This code supports an obsolete feature of TSIG removed by RFC 8945:
272 // in a zone transfer it was allowed to not signed up to 99 consecutive
273 // messages. Note that all modern DNS servers sign all messages.
274 // Hopefully this code will be never used.
275 if (!record) {
276 if ((last_sig_dist_ >= 0) && (last_sig_dist_ < 99)) {
277 // It is not signed, but in the middle of TCP stream. We just
278 // update the MAC state and consider this message OK.
279 update(data, data_len);
280 // This one is not signed, the last signed is one message further
281 // now.
282 ++last_sig_dist_;
283 // No digest to return now. Just say it's OK.
284 return (postVerifyUpdate(TSIGError::NOERROR()));
285 }
286 // This case happens when we sent a signed request and have received an
287 // unsigned response. According to RFC2845 Section 4.6 this case should be
288 // considered a "format error" (although the specific error code
289 // wouldn't matter much for the caller).
290 return (postVerifyUpdate(TSIGError::FORMERR()));
291 }
292
293 const TSIG& tsig_rdata = record->getRdata();
294
295 if (data_len < MESSAGE_HEADER_LEN + record->getLength()) {
297 "TSIG verify: data length is invalid: " << data_len);
298 }
299 if (!data) {
300 isc_throw(InvalidParameter, "TSIG verify: empty data is invalid");
301 }
302
303 if (!key_.getSecCtx().get()) {
304 isc_throw(Unexpected, "verify called with null security context");
305 }
306
307 // This message is signed and we won't throw any more.
308 last_sig_dist_ = 0;
309
310 // Check key: whether we first verify it with a known key or we verify
311 // it using the consistent key in the context. If the check fails we are
312 // done with BADKEY.
313 if ((state_ == INIT) && (error_ == TSIGError::BAD_KEY())) {
314 return (postVerifyUpdate(TSIGError::BAD_KEY()));
315 }
316 if ((key_.getKeyName() != record->getName()) ||
317 (key_.getAlgorithmName() != tsig_rdata.getAlgorithm())) {
318 return (postVerifyUpdate(TSIGError::BAD_KEY()));
319 }
320
321 // Check time: the current time must be in the range of
322 // [time signed - fudge, time signed + fudge]. Otherwise verification
323 // fails with BADTIME. (RFC2845 Section 4.6.2)
324 // Note: for simplicity we don't explicitly catch the case of too small
325 // current time causing underflow. With the fact that fudge is quite
326 // small and (for now) non configurable, it shouldn't be a real concern
327 // in practice.
328 const uint64_t now = static_cast<uint64_t>(time(0));
329 if (tsig_rdata.getTimeSigned() + DEFAULT_FUDGE < now ||
330 tsig_rdata.getTimeSigned() - DEFAULT_FUDGE > now) {
331 if (state_ == INIT) {
332 const uint8_t* digest =
333 static_cast<const uint8_t*>(tsig_rdata.getMAC());
334 if (digest) {
335 previous_digest_.assign(digest,
336 digest + tsig_rdata.getMACSize());
337 }
338 previous_timesigned_ = tsig_rdata.getTimeSigned();
339 }
340 return (postVerifyUpdate(TSIGError::BAD_TIME()));
341 }
342
343 // Handling empty MAC. While RFC2845 doesn't explicitly prohibit other
344 // cases, it can only reasonably happen in a response with BADSIG or
345 // BADKEY. We reject other cases as if it were BADSIG to avoid unexpected
346 // acceptance of a bogus signature. This behavior follows the BIND 9
347 // implementation.
348 if (tsig_rdata.getMACSize() == 0) {
349 TSIGError error = TSIGError(tsig_rdata.getError());
350 if ((error != TSIGError::BAD_SIG()) &&
351 (error != TSIGError::BAD_KEY())) {
352 error = TSIGError::BAD_SIG();
353 }
354 return (postVerifyUpdate(error));
355 }
356
357 // If the context has previous MAC (either the Request MAC or its own
358 // previous MAC), digest it.
359 if (state_ != INIT) {
360 digestPreviousMAC(tbs_, previous_digest_);
361 }
362
363 // No signature length check with GSS-TSIG.
364
365 //
366 // Digest DNS message (excluding the trailing TSIG RR and adjusting the
367 // QID and ARCOUNT header fields)
368 //
369 digestDNSMessage(tbs_, tsig_rdata.getOriginalID(),
370 data, data_len - record->getLength());
371
372 // Digest TSIG variables. If state_ is VERIFIED_RESPONSE, it's a
373 // continuation of the same TCP stream and skip digesting them except
374 // for time related variables (RFC2845 4.4).
375 // Note: we use the constant values for RR class and TTL specified
376 // in RFC2845, not received values (we reject other values in constructing
377 // the TSIGRecord).
378 digestTSIGVariables(tbs_, key_,
379 TSIGRecord::getClass().getCode(),
381 tsig_rdata.getTimeSigned(),
382 tsig_rdata.getFudge(), tsig_rdata.getError(),
383 tsig_rdata.getOtherLen(),
384 tsig_rdata.getOtherData(),
385 state_ == VERIFIED_RESPONSE);
386
387 // Verify the digest with the received signature.
388 try {
389 GssApiBuffer gtbv(tbs_.getLength(), tbs_.getData());
390 // Consume the to be verified buffer.
391 tbs_.clear();
392 GssApiBuffer gsign(tsig_rdata.getMACSize(), tsig_rdata.getMAC());
393 key_.getSecCtx().verify(gtbv, gsign);
395 const uint8_t* digest =
396 static_cast<const uint8_t*>(tsig_rdata.getMAC());
397 previous_digest_.assign(digest, digest + tsig_rdata.getMACSize());
398 return (postVerifyUpdate(TSIGError::NOERROR()));
399 } catch (const Exception& ex) {
401 .arg(ex.what());
402 }
403 return (postVerifyUpdate(TSIGError::BAD_SIG()));
404}
405
406bool
408 if (last_sig_dist_ == -1) {
409 isc_throw(TSIGContextError, "No message was verified yet");
410 }
411 return (last_sig_dist_ == 0);
412}
413
414size_t
416 //
417 // The space required for an TSIG record is:
418 //
419 // n1 bytes for the (key) name
420 // 2 bytes for the type
421 // 2 bytes for the class
422 // 4 bytes for the ttl
423 // 2 bytes for the rdlength
424 // n2 bytes for the algorithm name
425 // 6 bytes for the time signed
426 // 2 bytes for the fudge
427 // 2 bytes for the MAC size
428 // x bytes for the MAC
429 // 2 bytes for the original id
430 // 2 bytes for the error
431 // 2 bytes for the other data length
432 // y bytes for the other data (at most)
433 // ---------------------------------
434 // 26 + n1 + n2 + x + y bytes
435 //
436
437 // Normally the digest length ("x") is the length of the underlying
438 // hash output. If a key related error occurred, however, the
439 // corresponding TSIG will be "unsigned", and the digest length will be 0.
440 size_t digest_len = DIGEST_LEN;
441 if (error_ == TSIGError::BAD_KEY() || error_ == TSIGError::BAD_SIG()) {
442 digest_len = 0;
443 }
444
445 // Other Len ("y") is normally 0; if BAD_TIME error occurred, the
446 // subsequent TSIG will contain 48 bits of the server current time.
447 size_t other_len = 0;
448 if (error_ == TSIGError::BAD_TIME()) {
449 other_len = 6;
450 }
451
452 return (26 + key_.getKeyName().getLength() +
453 key_.getAlgorithmName().getLength() +
454 digest_len + other_len);
455}
456
457void
458GssTsigContext::update(const void* const data, size_t len) {
459 // Use the previous digest and never use it again.
460 digestPreviousMAC(tbs_, previous_digest_);
461 previous_digest_.clear();
462 // Push the message there.
463 tbs_.writeData(data, len);
464}
465
466} // end of namespace isc::gss_tsig
467} // end of namespace isc
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
A generic exception that is thrown when an unexpected error condition occurs.
void toWire(AbstractMessageRenderer &renderer) const
Render the Name in the wire format with compression.
Definition name.cc:498
An exception that is thrown for logic errors identified in TSIG sign/verify operations.
Definition tsig.h:31
@ RECEIVED_REQUEST
Server received a signed request.
Definition tsig.h:184
@ SENT_REQUEST
Client sent a signed request, waiting response.
Definition tsig.h:183
@ SENT_RESPONSE
Server sent a signed response.
Definition tsig.h:185
@ VERIFIED_RESPONSE
Client successfully verified a response.
Definition tsig.h:186
@ INIT
Initial state.
Definition tsig.h:182
static const uint16_t DEFAULT_FUDGE
The recommended fudge value (in seconds) by RFC2845.
Definition tsig.h:414
TSIGContext(const TSIGKey &key)
Constructor from a TSIG key.
Definition tsig.cc:264
TSIG errors.
Definition tsigerror.h:22
static const TSIGError & BAD_SIG()
A constant TSIG error object for the BADSIG code (see TSIGError::BAD_SIG_CODE).
Definition tsigerror.h:323
static const TSIGError & BAD_KEY()
A constant TSIG error object for the BADKEY code (see TSIGError::BAD_KEY_CODE).
Definition tsigerror.h:329
static const TSIGError & BAD_TIME()
A constant TSIG error object for the BADTIME code (see TSIGError::BAD_TIME_CODE).
Definition tsigerror.h:335
static const TSIGError & NOERROR()
A constant TSIG error object derived from Rcode::NOERROR()
Definition tsigerror.h:227
static const TSIGError & FORMERR()
A constant TSIG error object derived from Rcode::FORMERR()
Definition tsigerror.h:233
const Name & getAlgorithmName() const
Return the algorithm name.
Definition tsigkey.cc:209
const Name & getKeyName() const
Getter Methods.
Definition tsigkey.cc:204
TSIG resource record.
Definition tsigrecord.h:51
static const RRClass & getClass()
Return the RR class of TSIG.
Definition tsigrecord.cc:79
static const uint32_t TSIG_TTL
The TTL value to be used in TSIG RRs.
Definition tsigrecord.h:270
size_t getLength() const
Return the length of the TSIG record.
Definition tsigrecord.h:211
const Name & getName() const
Return the owner name of the TSIG RR, which is the TSIG key name.
Definition tsigrecord.h:168
const rdata::any::TSIG & getRdata() const
Return the RDATA of the TSIG RR.
Definition tsigrecord.h:175
rdata::TSIG class represents the TSIG RDATA as defined in RFC2845.
Definition rdataclass.h:44
uint16_t getOriginalID() const
Return the value of the Original ID field.
uint16_t getError() const
Return the value of the Error field.
const Name & getAlgorithm() const
Return the algorithm name.
uint16_t getOtherLen() const
Return the value of the Other Len field.
const void * getOtherData() const
Return the value of the Other Data field.
const void * getMAC() const
Return the value of the MAC field.
uint16_t getFudge() const
Return the value of the Fudge field.
uint16_t getMACSize() const
Return the value of the MAC Size field.
uint64_t getTimeSigned() const
Return the value of the Time Signed field.
std::vector< uint8_t > getContent() const
Get the content as a vector.
void * getValue()
Get the value.
size_t getLength() const
Get the length.
virtual bool lastHadSignature() const override
Check whether the last verified message was signed.
virtual ~GssTsigContext()
Destructor.
virtual dns::ConstTSIGRecordPtr sign(const uint16_t qid, const void *const data, const size_t data_len) override
Sign a DNS message.
virtual size_t getTSIGLength() const override
Return the expected length of TSIG RR after sign().
void update(const void *const data, size_t len)
Update internal MAC state by more data.
virtual dns::TSIGError verify(const dns::TSIGRecord *const record, const void *const data, const size_t data_len) override
a DNS message.
GssTsigContext(GssTsigKey &key)
Constructor.
GSS-TSIG extension of the D2 TSIG key class.
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition buffer.h:81
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition buffer.h:346
void writeUint16(uint16_t data)
Write an unsigned 16-bit integer in host byte order into the buffer in network byte order.
Definition buffer.h:501
void writeData(const void *data, size_t len)
Copy an arbitrary length of data into the buffer.
Definition buffer.h:559
void writeUint32(uint32_t data)
Write an unsigned 32-bit integer in host byte order into the buffer in network byte order.
Definition buffer.h:531
const uint8_t * getData() const
Return a pointer to the head of the data stored in the buffer.
Definition buffer.h:398
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Implements a TSIGContext derived class which can be used as the value of TSIGContext pointers so with...
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
boost::shared_ptr< const TSIGRecord > ConstTSIGRecordPtr
A pointer-like type pointing to an immutable TSIGRecord object.
Definition tsigrecord.h:283
const isc::log::MessageID GSS_TSIG_VERIFIED
const isc::log::MessageID GSS_TSIG_VERIFY_FAILED
isc::log::Logger gss_tsig_logger("gss-tsig-hooks")
const int DBGLVL_TRACE_BASIC
Trace basic operations.
uint16_t readUint16(void const *const buffer, size_t const length)
uint16_t wrapper over readUint.
Definition io.h:76
Defines the logger used by the top-level component of kea-lfc.