Kea 3.3.3
tkey_exchange.cc
Go to the documentation of this file.
1// Copyright (C) 2021-2026 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 <d2srv/d2_log.h>
10#include <dns/messagerenderer.h>
11#include <dns/opcode.h>
12#include <dns/rdataclass.h>
17#include <stats/stats_mgr.h>
18
19#include <cstdint>
20#include <limits>
21#include <sstream>
22
23namespace isc {
24namespace gss_tsig {
25
26namespace {
27
28// OutputBuffer objects are pre-allocated before data is written to them.
29// This is a default number of bytes for the buffers we create within
30// TKeyExchange class.
31const size_t DEFAULT_BUFFER_SIZE = 8192;
32
33}
34
35using namespace isc::asiolink;
36using namespace isc::asiodns;
37using namespace isc::dns;
38using namespace isc::dns::rdata;
39using namespace isc::dns::rdata::generic;
40using namespace isc::log;
41using namespace isc::stats;
42using namespace isc::util;
43using namespace std;
44
45string
47 switch (status) {
48 case SUCCESS:
49 return ("response received and is ok");
50 case TIMEOUT:
51 return ("no response, timeout");
52 case IO_STOPPED:
53 return ("IO was stopped");
55 return ("response received but invalid");
57 return ("response received but not signed");
58 case BAD_CREDENTIALS:
59 return ("bad client credentials");
60 default:
61 return ("other, unclassified error");
62 }
63}
64
65// This class provides the implementation for the TKeyExchange. This allows for
66// the separation of the TKeyExchange interface from the implementation details.
67// Currently, implementation uses IOFetch object to handle asynchronous
68// communication with the DNS. This design may be revisited in the future. If
69// implementation is changed, the TKeyExchange API will remain unchanged thanks
70// to this separation.
72public:
81
94 TKeyExchangeImpl(const IOServicePtr& io_service, const DnsServerPtr& server,
95 const GssTsigKeyPtr& key, TKeyExchange::Callback* callback,
96 uint32_t timeout, OM_uint32 flags);
97
99 virtual ~TKeyExchangeImpl();
100
106 virtual void operator()(IOFetch::Result result);
107
110 void doExchange();
111
113 void cancel();
114
119 return (io_service_);
120 }
121
126 io_service_ = io_service;
127 }
128
129private:
137 void doExchangeInternal(GssApiBufferPtr intoken);
138
142 void callCallback(TKeyExchange::Status status);
143
145 void acquireCredentials();
146
151 GssApiBufferPtr readTKey(const OutputBufferPtr& outbuf);
152
156 bool verifyTKey();
157
162 void createTKeyRequest(const GssApiBufferPtr& outtoken);
163
167 void incrStats(const string& stat);
168
170 IOServicePtr io_service_;
171
173 State state_;
174
176 OutputBufferPtr in_buf_;
177
179 OutputBufferPtr out_buf_;
180
183 TKeyExchange::Callback* callback_;
184
186 DnsServerPtr server_;
187
189 GssTsigKeyPtr key_;
190
192 OM_uint32 flags_;
193
195 GssApiCredPtr cred_;
196
198 MessagePtr msg_;
199
201 uint32_t timeout_;
202
204 IOFetchPtr io_fetch_;
205};
206
208 const DnsServerPtr& server,
209 const GssTsigKeyPtr& key,
210 TKeyExchange::Callback* callback,
211 uint32_t timeout, OM_uint32 flags)
212 : io_service_(io_service), state_(NONE), in_buf_(), out_buf_(),
213 callback_(callback), server_(server), key_(key), flags_(flags), cred_(),
214 msg_(), timeout_(timeout) {
215 if (!io_service) {
216 isc_throw(BadValue, "null IOService");
217 }
218 if (key->getSecCtx().get() != GSS_C_NO_CONTEXT) {
219 isc_throw(BadValue, "wrong security context state");
220 }
221}
222
226
227void
229 // Get the status from IO. If no success, we just call user's callback
230 // and pass the status code.
232 .arg(IOFetch::resultToText(result));
233 GssApiBufferPtr intoken;
234 switch (result) {
235 case IOFetch::SUCCESS:
236 intoken = readTKey(out_buf_);
237 if (!intoken || intoken->empty()) {
239 incrStats("tkey-error");
240 callCallback(TKeyExchange::INVALID_RESPONSE);
241 } else {
242 // The TKEY payload from the server response is used for
243 // the GSS-API security context establishment.
244
245 doExchangeInternal(intoken);
246 }
247 return;
248
251 incrStats("tkey-timeout");
252 callCallback(TKeyExchange::TIMEOUT);
253 return;
254
255 case IOFetch::STOPPED:
257 incrStats("tkey-error");
258 callCallback(TKeyExchange::IO_STOPPED);
259 return;
260
261 default:
263 .arg(IOFetch::resultToText(result));
264 incrStats("tkey-error");
265 callCallback(TKeyExchange::OTHER);
266 return;
267 }
268}
269
270void
271TKeyExchangeImpl::acquireCredentials() {
272 const string& cred_princ = server_->getClientPrincipal();
273 if (cred_princ.empty()) {
274 return;
275 }
276
277 OM_uint32 lifetime(0);
278 GssApiName cname(cred_princ);
279 cred_.reset(new GssApiCred(cname, GSS_C_INITIATE, lifetime));
280 if (lifetime == 0) {
281 isc_throw(GssCredExpired, "credentials expired for " << cred_princ);
282 }
283}
284
285bool
286TKeyExchangeImpl::verifyTKey() {
287 // The last TKEY response must be signed.
288 const TSIGRecord* tsig = msg_->getTSIGRecord();
289 if (!tsig) {
291 return (false);
292 }
293 GssTsigContextPtr tkey_ctx(new GssTsigContext(*key_));
294 tkey_ctx->setState(TSIGContext::SENT_REQUEST);
295 TSIGError error = tkey_ctx->verify(tsig, out_buf_->getData(),
296 out_buf_->getLength());
297 if (error != TSIGError::NOERROR()) {
299 return (false);
300 }
301
303
304 return (true);
305}
306
307void
308TKeyExchangeImpl::createTKeyRequest(const GssApiBufferPtr& outtoken) {
309 // Create a TKEY request.
310 msg_.reset(new Message(Message::RENDER));
311 // QID is added by IOFetch using the cryptolink::generateQid.
312 msg_->setOpcode(Opcode::QUERY());
313 msg_->setRcode(Rcode::NOERROR());
314 msg_->setHeaderFlag(Message::HEADERFLAG_QR, false);
315 msg_->setHeaderFlag(Message::HEADERFLAG_RD, false);
316
317 msg_->addQuestion(Question(key_->getKeyName(), RRClass::ANY(),
318 RRType::TKEY()));
319
320 // Create the TKEY Resource Record.
321 RRsetPtr tkey_rrset(new RRset(key_->getKeyName(), RRClass::ANY(),
322 RRType::TKEY(), RRTTL(0)));
323 Name algorithm("gss-tsig.");
324 uint32_t inception = key_->getInception32();
325 uint32_t expire = key_->getExpire32();
326 uint16_t mode = TKEY::GSS_API_MODE;
327 uint16_t error = Rcode::NOERROR().getCode();
328 size_t key_length = outtoken->getLength();
329 if (key_length > std::numeric_limits<uint16_t>::max()) {
330 isc_throw(BadValue, "TKEY value too long: " << key_length);
331 }
332 uint16_t key_len = static_cast<uint16_t>(key_length);
333 ConstRdataPtr tkey_rdata(new TKEY(algorithm, inception, expire, mode, error,
334 key_len, outtoken->getValue(), 0, 0));
335 tkey_rrset->addRdata(tkey_rdata);
336 msg_->addRRset(Message::SECTION_ADDITIONAL, tkey_rrset);
337
338 // Encode the TKEY request.
339 MessageRenderer renderer;
340 in_buf_.reset(new OutputBuffer(DEFAULT_BUFFER_SIZE));
341 renderer.setBuffer(in_buf_.get());
342 renderer.setLengthLimit(DEFAULT_BUFFER_SIZE);
343 msg_->toWire(renderer);
344}
345
346void
347TKeyExchangeImpl::callCallback(TKeyExchange::Status status) {
348 // Once we are done with internal business, let's call a callback supplied
349 // by a caller.
350 if (callback_) {
351 (*callback_)(status);
352 }
353 if (status == TKeyExchange::SUCCESS) {
354 state_ = SUCCESS;
355 } else {
356 state_ = FAILURE;
357 }
358}
359
360void
361TKeyExchangeImpl::doExchangeInternal(GssApiBufferPtr intoken) {
362 GssApiName named_gname(server_->getServerPrincipal());
363
364 GssApiBufferPtr outtoken(new GssApiBuffer());
365 OM_uint32 lifetime(0);
366 bool ret;
367 try {
368 ret = key_->getSecCtx().init(cred_, named_gname, flags_, *intoken,
369 *outtoken, lifetime);
370 } catch (const isc::Exception& ex) {
372 .arg(ex.what());
373 incrStats("tkey-error");
374 callCallback(TKeyExchange::OTHER);
375 return;
376 }
377 if (ret) {
378 // Established.
379 if (!outtoken->empty()) {
380 // The RFC is not consistent about this case because it specifies it
381 // and at the same time requires the server to sign the last response
382 // so the first security context to be established is the server one
383 // and any further exchange does not make sense...
385 }
386 lifetime = key_->getSecCtx().getLifetime();
387 if (lifetime < server_->getKeyLifetime()) {
388 ostringstream msg;
389 msg << "too short credential lifetime: " << lifetime
390 << " < " << server_->getKeyLifetime();
392 .arg(msg.str());
393 incrStats("tkey-error");
394 callCallback(TKeyExchange::BAD_CREDENTIALS);
395 return;
396 }
398 .arg(key_->getSecCtx().getLifetime());
399
400 if (verifyTKey()) {
401 incrStats("tkey-success");
402 callCallback(TKeyExchange::SUCCESS);
403 } else {
404 incrStats("tkey-error");
406 }
407
408 return;
409 }
410
411 if (outtoken->empty()) {
413 incrStats("tkey-error");
414 callCallback(TKeyExchange::INVALID_RESPONSE);
415 return;
416 }
417
418 createTKeyRequest(outtoken);
420 .arg(in_buf_->getLength());
421 incrStats("tkey-sent");
422
423 // Send the TKEY request.
424 IOAddress io_addr = server_->getIpAddress();
425 out_buf_.reset(new OutputBuffer(DEFAULT_BUFFER_SIZE));
426
427 // Do not use msg because IOFetch code will mess it!
428 io_fetch_.reset(new IOFetch(server_->getKeyProto(), io_service_, in_buf_,
429 io_addr, server_->getPort(), out_buf_,
430 this, static_cast<int>(timeout_)));
431 io_service_->post(*io_fetch_);
432}
433
434void
436 if (state_ != NONE) {
437 isc_throw(InvalidOperation, "initiating exchange from invalid state");
438 }
439
440 state_ = STARTED;
441
442 // start acquire credentials
443 try {
444 acquireCredentials();
445 } catch (const isc::Exception& ex) {
447 .arg(ex.what());
448 incrStats("tkey-error");
449 callCallback(TKeyExchange::BAD_CREDENTIALS);
450 return;
451 }
452
453 GssApiBufferPtr intoken(new GssApiBuffer());
454 doExchangeInternal(intoken);
455}
456
457void
459 if (io_fetch_) {
460 io_fetch_->stop();
461 }
462 state_ = STOPPED;
463}
464
466TKeyExchangeImpl::readTKey(const OutputBufferPtr& outbuf) {
467 // Decode the TKEY response.
468 size_t msg_len = outbuf->getLength();
469 if (msg_len == 0) {
471 return (GssApiBufferPtr());
472 }
473 const void* msg_buf = outbuf->getData();
474 if (!msg_buf) {
476 return (GssApiBufferPtr());
477 }
478
480 .arg(msg_len);
481
482 msg_.reset(new Message(Message::PARSE));
483 InputBuffer recv_buf(msg_buf, msg_len);
484 msg_->fromWire(recv_buf);
485
486 // Validate the TKEY response.
487 if (!msg_->getHeaderFlag(Message::HEADERFLAG_QR)) {
489 }
490 if (msg_->getRcode() != Rcode::NOERROR()) {
492 .arg(msg_->getRcode().toText());
493 return (GssApiBufferPtr());
494 }
495 if (msg_->getOpcode() != Opcode::QUERY()) {
497 .arg(msg_->getOpcode());
498 return (GssApiBufferPtr());
499 }
500 // According to RFC3645, the TKEY is found in the ANSWER section.
501 if (msg_->getRRCount(Message::SECTION_ANSWER) != 1) {
503 .arg(msg_->getRRCount(Message::SECTION_ANSWER));
504 return (GssApiBufferPtr());
505 }
506 RRsetPtr rrset = *msg_->beginSection(Message::SECTION_ANSWER);
507 if (!rrset) {
509 return (GssApiBufferPtr());
510 }
511 if (rrset->getClass() != RRClass::ANY()) {
513 .arg(rrset->getClass().toText());
514 }
515 if (rrset->getType() != RRType::TKEY()) {
517 .arg(rrset->getType().toText());
518 return (GssApiBufferPtr());
519 }
520 if (rrset->getTTL() != RRTTL(0)) {
522 .arg(rrset->getTTL().toText());
523 }
524 if (rrset->getRdataCount() != 1) {
526 .arg(rrset->getRdataCount());
527 if (rrset->getRdataCount() == 0) {
529 return (GssApiBufferPtr());
530 }
531 }
532 auto rdata_it = rrset->getRdataIterator();
533 const TKEY& tkey = dynamic_cast<const TKEY&>(rdata_it->getCurrent());
534 if (tkey.getError() != Rcode::NOERROR_CODE) {
536 .arg(tkey.getError());
537 return (GssApiBufferPtr());
538 }
539 return (GssApiBufferPtr(new GssApiBuffer(tkey.getKeyLen(), tkey.getKey())));
540}
541
542void
543TKeyExchangeImpl::incrStats(const string& stat) {
544 StatsMgr& mgr = StatsMgr::instance();
545 mgr.addValue(stat, static_cast<int64_t>(1));
546 if (server_) {
547 mgr.addValue(StatsMgr::generateName("server", server_->getID(), stat),
548 static_cast<int64_t>(1));
549 }
550}
551
552const OM_uint32 TKeyExchange::TKEY_EXCHANGE_FLAGS = (GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG);
553const uint32_t TKeyExchange::TKEY_EXCHANGE_IO_TIMEOUT = 3000;
554
556 const DnsServerPtr& server, const GssTsigKeyPtr& key,
557 Callback* callback, uint32_t timeout, OM_uint32 flags)
558 : impl_(new TKeyExchangeImpl(io_service, server, key, callback, timeout, flags)) {
559}
560
564
565void
567 impl_->doExchange();
568}
569
570void
572 impl_->cancel();
573}
574
577 return (impl_->getIOService());
578}
579
580void
582 impl_->setIOService(io_service);
583}
584
585} // namespace gss_tsig
586} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
This is a base class for exceptions thrown from the DNS library module.
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 function is called in a prohibited way.
I/O Fetch Callback.
Definition io_fetch.h:106
static std::string const & resultToText(Result const &result)
Convert enum to string.
Definition io_fetch.h:74
Result
Result of Upstream Fetch.
Definition io_fetch.h:61
void setBuffer(isc::util::OutputBuffer *buffer)
Set or reset a temporary output buffer.
virtual void setLengthLimit(size_t len)
Set the maximum length of rendered data that can fit in the corresponding DNS message without truncat...
The Message class encapsulates a standard DNS message.
Definition message.h:150
static const Opcode & QUERY()
A constant object for the QUERY Opcode.
Definition opcode.h:177
static const RRClass & ANY()
Definition rrclass.h:298
static const RRType & TKEY()
Definition rrtype.h:333
static const Rcode & NOERROR()
A constant object for the NOERROR Rcode (see Rcode::NOERROR_CODE).
Definition rcode.h:227
@ NOERROR_CODE
0: No error (RFC1035)
Definition rcode.h:43
uint16_t getCode() const
Returns the Rcode code value.
Definition rcode.h:105
@ SENT_REQUEST
Client sent a signed request, waiting response.
Definition tsig.h:184
static const TSIGError & NOERROR()
A constant TSIG error object derived from Rcode::NOERROR().
Definition tsigerror.h:228
static const uint16_t GSS_API_MODE
The GSS_API constant for the Mode field.
Definition rdataclass.h:516
const void * getKey() const
Return the value of the Key field.
uint16_t getError() const
Return the value of the Error field.
uint16_t getKeyLen() const
Return the value of the Key Len field.
TKeyExchangeImpl(const IOServicePtr &io_service, const DnsServerPtr &server, const GssTsigKeyPtr &key, TKeyExchange::Callback *callback, uint32_t timeout, OM_uint32 flags)
Constructor.
void setIOService(const isc::asiolink::IOServicePtr &io_service)
Sets IO service.
isc::asiolink::IOServicePtr getIOService()
Gets IO service.
State
The TKEY exchange state.
@ FAILURE
The TKEY exchange has failed.
@ STARTED
The TKEY exchange has been started.
@ SUCCESS
The TKEY exchange has succeeded.
@ STOPPED
The TKEY exchange has been canceled.
@ NONE
Initial state: no action has been initiated.
void cancel()
This function cancels the started TKEY exchange.
virtual void operator()(IOFetch::Result result)
This internal callback is called when the DNS update message exchange is complete.
void doExchange()
This function handles the repeated communication with the DNS server trying to complete the TKEY exch...
virtual ~TKeyExchangeImpl()
Destructor.
Callback for the TKeyExchange class.
void cancel()
This function cancels the in-flight TKEY exchange.
static std::string statusToText(Status status)
Convert a status to its textual form.
TKeyExchange(const isc::asiolink::IOServicePtr &io_service, const DnsServerPtr &server, const GssTsigKeyPtr &key, Callback *callback, uint32_t timeout=TKEY_EXCHANGE_IO_TIMEOUT, OM_uint32 flags=TKEY_EXCHANGE_FLAGS)
Constructor.
isc::asiolink::IOServicePtr getIOService()
Gets IO service.
void doExchange()
This function handles the repeated communication with the DNS server trying to complete the TKEY exch...
void setIOService(const isc::asiolink::IOServicePtr io_service)
Sets IO service.
static const uint32_t TKEY_EXCHANGE_IO_TIMEOUT
The default IO timeout used for IO operations (in milliseconds) set to 3000 (3 seconds).
Status
A status code of the TKeyExchange.
@ SUCCESS
Response received and is ok.
@ BAD_CREDENTIALS
Bad client credentials.
@ IO_STOPPED
IO was stopped.
@ UNSIGNED_RESPONSE
Response received but not signed.
@ INVALID_RESPONSE
Response received but invalid.
@ TIMEOUT
No response, timeout.
@ OTHER
Other, unclassified error.
static const OM_uint32 TKEY_EXCHANGE_FLAGS
The default TKEY exchange flags.
virtual ~TKeyExchange()
Virtual destructor, does nothing.
static StatsMgr & instance()
Statistics Manager accessor method.
static std::string generateName(const std::string &context, Type index, const std::string &stat_name)
Generates statistic name in a given context.
The InputBuffer class is a buffer abstraction for manipulating read-only data.
Definition buffer.h:81
Defines a State within the State Model.
Definition state_model.h:64
#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_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
boost::shared_ptr< IOFetch > IOFetchPtr
Defines a pointer to an IOFetch.
Definition io_fetch.h:268
@ error
Definition db_log.h:127
boost::shared_ptr< const Rdata > ConstRdataPtr
Definition rdata.h:69
boost::shared_ptr< AbstractRRset > RRsetPtr
A pointer-like type pointing to an RRset object.
Definition rrset.h:50
boost::shared_ptr< Message > MessagePtr
Pointer-like type pointing to a Message.
Definition message.h:668
const isc::log::MessageID TKEY_EXCHANGE_FAIL_NO_RDATA
const isc::log::MessageID TKEY_EXCHANGE_IO_FETCH_RESULT
const isc::log::MessageID TKEY_EXCHANGE_FAIL_NOT_SIGNED
const isc::log::MessageID TKEY_EXCHANGE_FAIL_NO_RESPONSE_ANSWER
const isc::log::MessageID TKEY_EXCHANGE_RECEIVE_MESSAGE
const isc::log::MessageID TKEY_EXCHANGE_RDATA_COUNT
const isc::log::MessageID TKEY_EXCHANGE_FAIL_RESPONSE_ERROR
const isc::log::MessageID TKEY_EXCHANGE_FAIL_EMPTY_RESPONSE
boost::shared_ptr< DnsServer > DnsServerPtr
A pointer to a DNS server.
const isc::log::MessageID TKEY_EXCHANGE_FAILED_TO_VERIFY
const isc::log::MessageID TKEY_EXCHANGE_RESPONSE_TTL
const isc::log::MessageID TKEY_EXCHANGE_VERIFIED
const isc::log::MessageID TKEY_EXCHANGE_NOT_A_RESPONSE
const isc::log::MessageID TKEY_EXCHANGE_FAIL_WRONG_RESPONSE_OPCODE
const isc::log::MessageID TKEY_EXCHANGE_FAIL_IO_ERROR
const isc::log::MessageID TKEY_EXCHANGE_FAIL_EMPTY_IN_TOKEN
boost::shared_ptr< GssApiBuffer > GssApiBufferPtr
Shared pointer to GSS-API buffer.
const isc::log::MessageID TKEY_EXCHANGE_FAIL_EMPTY_OUT_TOKEN
const isc::log::MessageID TKEY_EXCHANGE_FAIL_NULL_RESPONSE
const isc::log::MessageID TKEY_EXCHANGE_OUT_TOKEN_NOT_EMPTY
const isc::log::MessageID TKEY_EXCHANGE_FAIL_IO_STOPPED
boost::shared_ptr< GssTsigContext > GssTsigContextPtr
Type of pointer to a GSS-TSIG context.
const isc::log::MessageID TKEY_EXCHANGE_SEND_MESSAGE
const isc::log::MessageID TKEY_EXCHANGE_FAIL_WRONG_RESPONSE_ANSWER_COUNT
const isc::log::MessageID TKEY_EXCHANGE_FAIL_TKEY_ERROR
const isc::log::MessageID TKEY_EXCHANGE_FAIL_TO_INIT
const isc::log::MessageID TKEY_EXCHANGE_ANSWER_CLASS
const isc::log::MessageID TKEY_EXCHANGE_FAIL_WRONG_RESPONSE_ANSWER_TYPE
isc::log::Logger gss_tsig_logger("gss-tsig-hooks")
const isc::log::MessageID TKEY_EXCHANGE_VALID
const isc::log::MessageID BAD_CLIENT_CREDENTIALS
boost::shared_ptr< GssApiCred > GssApiCredPtr
Shared pointer to GSS-API credential.
boost::shared_ptr< GssTsigKey > GssTsigKeyPtr
Type of pointer to a GSS-TSIG key.
const isc::log::MessageID TKEY_EXCHANGE_FAIL_IO_TIMEOUT
const int DBGLVL_TRACE_BASIC
Trace basic operations.
PerfMonMgrPtr mgr
PerfMonMgr singleton.
boost::shared_ptr< OutputBuffer > OutputBufferPtr
Type of pointers to output buffers.
Definition buffer.h:583
Defines the logger used by the top-level component of kea-lfc.