Kea 3.1.1
tkey_exchange.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 <d2srv/d2_log.h>
10#include <dns/messagerenderer.h>
11#include <dns/opcode.h>
12#include <dns/rdataclass.h>
13#include <stats/stats_mgr.h>
14#include <gss_tsig_context.h>
15#include <gss_tsig_key.h>
16#include <gss_tsig_log.h>
17#include <tkey_exchange.h>
18#include <limits>
19#include <sstream>
20
21namespace isc {
22namespace gss_tsig {
23
24namespace {
25
26// OutputBuffer objects are pre-allocated before data is written to them.
27// This is a default number of bytes for the buffers we create within
28// TKeyExchange class.
29const size_t DEFAULT_BUFFER_SIZE = 4096;
30
31}
32
33using namespace isc::asiolink;
34using namespace isc::asiodns;
35using namespace isc::dns;
36using namespace isc::dns::rdata;
37using namespace isc::dns::rdata::generic;
38using namespace isc::log;
39using namespace isc::stats;
40using namespace isc::util;
41using namespace std;
42
43string
45 switch (status) {
46 case SUCCESS:
47 return ("response received and is ok");
48 case TIMEOUT:
49 return ("no response, timeout");
50 case IO_STOPPED:
51 return ("IO was stopped");
53 return ("response received but invalid");
55 return ("response received but not signed");
56 case BAD_CREDENTIALS:
57 return ("bad client credentials");
58 default:
59 return ("other, unclassified error");
60 }
61}
62
63// This class provides the implementation for the TKeyExchange. This allows for
64// the separation of the TKeyExchange interface from the implementation details.
65// Currently, implementation uses IOFetch object to handle asynchronous
66// communication with the DNS. This design may be revisited in the future. If
67// implementation is changed, the TKeyExchange API will remain unchanged thanks
68// to this separation.
70public:
79
92 TKeyExchangeImpl(const IOServicePtr& io_service, const DnsServerPtr& server,
93 const GssTsigKeyPtr& key, TKeyExchange::Callback* callback,
94 uint32_t timeout, OM_uint32 flags);
95
97 virtual ~TKeyExchangeImpl();
98
104 virtual void operator()(IOFetch::Result result);
105
108 void doExchange();
109
111 void cancel();
112
117 return (io_service_);
118 }
119
124 io_service_ = io_service;
125 }
126
127private:
135 void doExchangeInternal(GssApiBufferPtr intoken);
136
140 void callCallback(TKeyExchange::Status status);
141
143 void acquireCredentials();
144
149 GssApiBufferPtr readTKey(const OutputBufferPtr& outbuf);
150
154 bool verifyTKey();
155
160 void createTKeyRequest(const GssApiBufferPtr& outtoken);
161
165 void incrStats(const string& stat);
166
168 IOServicePtr io_service_;
169
171 State state_;
172
174 OutputBufferPtr in_buf_;
175
177 OutputBufferPtr out_buf_;
178
181 TKeyExchange::Callback* callback_;
182
184 DnsServerPtr server_;
185
187 GssTsigKeyPtr key_;
188
190 OM_uint32 flags_;
191
193 GssApiCredPtr cred_;
194
196 MessagePtr msg_;
197
199 uint32_t timeout_;
200
202 IOFetchPtr io_fetch_;
203};
204
206 const DnsServerPtr& server,
207 const GssTsigKeyPtr& key,
208 TKeyExchange::Callback* callback,
209 uint32_t timeout, OM_uint32 flags)
210 : io_service_(io_service), state_(NONE), in_buf_(), out_buf_(),
211 callback_(callback), server_(server), key_(key), flags_(flags), cred_(),
212 msg_(), timeout_(timeout) {
213 if (!io_service) {
214 isc_throw(BadValue, "null IOService");
215 }
216 if (key->getSecCtx().get() != GSS_C_NO_CONTEXT) {
217 isc_throw(BadValue, "wrong security context state");
218 }
219}
220
224
225void
227 // Get the status from IO. If no success, we just call user's callback
228 // and pass the status code.
229 GssApiBufferPtr intoken;
230 switch (result) {
231 case IOFetch::SUCCESS:
232 intoken = readTKey(out_buf_);
233 if (!intoken || intoken->empty()) {
235 incrStats("tkey-error");
236 callCallback(TKeyExchange::INVALID_RESPONSE);
237 } else {
238 // The TKEY payload from the server response is used for
239 // the GSS-API security context establishment.
240
241 doExchangeInternal(intoken);
242 }
243 return;
244
247 incrStats("tkey-timeout");
248 callCallback(TKeyExchange::TIMEOUT);
249 return;
250
251 case IOFetch::STOPPED:
253 incrStats("tkey-error");
254 callCallback(TKeyExchange::IO_STOPPED);
255 return;
256
257 default:
259 .arg(result);
260 incrStats("tkey-error");
261 callCallback(TKeyExchange::OTHER);
262 return;
263 }
264}
265
266void
267TKeyExchangeImpl::acquireCredentials() {
268 const string& cred_princ = server_->getClientPrincipal();
269 if (cred_princ.empty()) {
270 return;
271 }
272
273 OM_uint32 lifetime(0);
274 GssApiName cname(cred_princ);
275 cred_.reset(new GssApiCred(cname, GSS_C_INITIATE, lifetime));
276 if (lifetime == 0) {
277 isc_throw(GssCredExpired, "credentials expired for " << cred_princ);
278 }
279}
280
281bool
282TKeyExchangeImpl::verifyTKey() {
283 // The last TKEY response must be signed.
284 const TSIGRecord* tsig = msg_->getTSIGRecord();
285 if (!tsig) {
287 return (false);
288 }
289 GssTsigContextPtr tkey_ctx(new GssTsigContext(*key_));
290 tkey_ctx->setState(TSIGContext::SENT_REQUEST);
291 TSIGError error = tkey_ctx->verify(tsig, out_buf_->getData(),
292 out_buf_->getLength());
293 if (error != TSIGError::NOERROR()) {
295 return (false);
296 }
297
299
300 return (true);
301}
302
303void
304TKeyExchangeImpl::createTKeyRequest(const GssApiBufferPtr& outtoken) {
305 // Create a TKEY request.
306 msg_.reset(new Message(Message::RENDER));
307 // QID is added by IOFetch using the cryptolink::generateQid.
308 msg_->setOpcode(Opcode::QUERY());
309 msg_->setRcode(Rcode::NOERROR());
310 msg_->setHeaderFlag(Message::HEADERFLAG_QR, false);
311 msg_->setHeaderFlag(Message::HEADERFLAG_RD, false);
312
313 msg_->addQuestion(Question(key_->getKeyName(), RRClass::ANY(),
314 RRType::TKEY()));
315
316 // Create the TKEY Resource Record.
317 RRsetPtr tkey_rrset(new RRset(key_->getKeyName(), RRClass::ANY(),
318 RRType::TKEY(), RRTTL(0)));
319 Name algorithm("gss-tsig.");
320 uint32_t inception = key_->getInception32();
321 uint32_t expire = key_->getExpire32();
322 uint16_t mode = TKEY::GSS_API_MODE;
323 uint16_t error = Rcode::NOERROR().getCode();
324 size_t key_length = outtoken->getLength();
325 if (key_length > std::numeric_limits<uint16_t>::max()) {
326 isc_throw(BadValue, "TKEY value too long: " << key_length);
327 }
328 uint16_t key_len = static_cast<uint16_t>(key_length);
329 ConstRdataPtr tkey_rdata(new TKEY(algorithm, inception, expire, mode, error,
330 key_len, outtoken->getValue(), 0, 0));
331 tkey_rrset->addRdata(tkey_rdata);
332 msg_->addRRset(Message::SECTION_ADDITIONAL, tkey_rrset);
333
334 // Encode the TKEY request.
335 MessageRenderer renderer;
336 in_buf_.reset(new OutputBuffer(DEFAULT_BUFFER_SIZE));
337 renderer.setBuffer(in_buf_.get());
338 renderer.setLengthLimit(DEFAULT_BUFFER_SIZE);
339 msg_->toWire(renderer);
340}
341
342void
343TKeyExchangeImpl::callCallback(TKeyExchange::Status status) {
344 // Once we are done with internal business, let's call a callback supplied
345 // by a caller.
346 if (callback_) {
347 (*callback_)(status);
348 }
349 if (status == TKeyExchange::SUCCESS) {
350 state_ = SUCCESS;
351 } else {
352 state_ = FAILURE;
353 }
354}
355
356void
357TKeyExchangeImpl::doExchangeInternal(GssApiBufferPtr intoken) {
358 GssApiName named_gname(server_->getServerPrincipal());
359
360 GssApiBufferPtr outtoken(new GssApiBuffer());
361 OM_uint32 lifetime(0);
362 bool ret;
363 try {
364 ret = key_->getSecCtx().init(cred_, named_gname, flags_, *intoken,
365 *outtoken, lifetime);
366 } catch (const isc::Exception& ex) {
368 .arg(ex.what());
369 incrStats("tkey-error");
370 callCallback(TKeyExchange::OTHER);
371 return;
372 }
373 if (ret) {
374 // Established.
375 if (!outtoken->empty()) {
376 // The RFC is not consistent about this case because it specifies it
377 // and at the same time requires the server to sign the last response
378 // so the first security context to be established is the server one
379 // and any further exchange does not make sense...
381 }
382 lifetime = key_->getSecCtx().getLifetime();
383 if (lifetime < server_->getKeyLifetime()) {
384 ostringstream msg;
385 msg << "too short credential lifetime: " << lifetime
386 << " < " << server_->getKeyLifetime();
388 .arg(msg.str());
389 incrStats("tkey-error");
390 callCallback(TKeyExchange::BAD_CREDENTIALS);
391 return;
392 }
394 .arg(key_->getSecCtx().getLifetime());
395
396 if (verifyTKey()) {
397 incrStats("tkey-success");
398 callCallback(TKeyExchange::SUCCESS);
399 } else {
400 incrStats("tkey-error");
402 }
403
404 return;
405 }
406
407 if (outtoken->empty()) {
409 incrStats("tkey-error");
410 callCallback(TKeyExchange::INVALID_RESPONSE);
411 return;
412 }
413
414 createTKeyRequest(outtoken);
416 .arg(in_buf_->getLength());
417 incrStats("tkey-sent");
418
419 // Send the TKEY request.
420 IOAddress io_addr = server_->getIpAddress();
421 out_buf_.reset(new OutputBuffer(DEFAULT_BUFFER_SIZE));
422
423 // Do not use msg because IOFetch code will mess it!
424 io_fetch_.reset(new IOFetch(server_->getKeyProto(), io_service_, in_buf_,
425 io_addr, server_->getPort(), out_buf_,
426 this, static_cast<int>(timeout_)));
427 io_service_->post(*io_fetch_);
428}
429
430void
432 if (state_ != NONE) {
433 isc_throw(InvalidOperation, "initiating exchange from invalid state");
434 }
435
436 state_ = STARTED;
437
438 // start acquire credentials
439 try {
440 acquireCredentials();
441 } catch (const isc::Exception& ex) {
443 .arg(ex.what());
444 incrStats("tkey-error");
445 callCallback(TKeyExchange::BAD_CREDENTIALS);
446 return;
447 }
448
449 GssApiBufferPtr intoken(new GssApiBuffer());
450 doExchangeInternal(intoken);
451}
452
453void
455 if (io_fetch_) {
456 io_fetch_->stop();
457 }
458 state_ = STOPPED;
459}
460
462TKeyExchangeImpl::readTKey(const OutputBufferPtr& outbuf) {
463 // Decode the TKEY response.
464 size_t msg_len = outbuf->getLength();
465 if (msg_len == 0) {
467 return (GssApiBufferPtr());
468 }
469 const void* msg_buf = outbuf->getData();
470 if (!msg_buf) {
472 return (GssApiBufferPtr());
473 }
474
476 .arg(msg_len);
477
478 msg_.reset(new Message(Message::PARSE));
479 InputBuffer recv_buf(msg_buf, msg_len);
480 msg_->fromWire(recv_buf);
481
482 // Validate the TKEY response.
483 if (!msg_->getHeaderFlag(Message::HEADERFLAG_QR)) {
485 }
486 if (msg_->getRcode() != Rcode::NOERROR()) {
488 .arg(msg_->getRcode().toText());
489 return (GssApiBufferPtr());
490 }
491 if (msg_->getOpcode() != Opcode::QUERY()) {
493 .arg(msg_->getOpcode());
494 return (GssApiBufferPtr());
495 }
496 // According to RFC3645, the TKEY is found in the ANSWER section.
497 if (msg_->getRRCount(Message::SECTION_ANSWER) != 1) {
499 .arg(msg_->getRRCount(Message::SECTION_ANSWER));
500 return (GssApiBufferPtr());
501 }
502 RRsetPtr rrset = *msg_->beginSection(Message::SECTION_ANSWER);
503 if (!rrset) {
505 return (GssApiBufferPtr());
506 }
507 if (rrset->getClass() != RRClass::ANY()) {
509 .arg(rrset->getClass().toText());
510 }
511 if (rrset->getType() != RRType::TKEY()) {
513 .arg(rrset->getType().toText());
514 return (GssApiBufferPtr());
515 }
516 if (rrset->getTTL() != RRTTL(0)) {
518 .arg(rrset->getTTL().toText());
519 }
520 if (rrset->getRdataCount() != 1) {
522 .arg(rrset->getRdataCount());
523 if (rrset->getRdataCount() == 0) {
525 return (GssApiBufferPtr());
526 }
527 }
528 auto rdata_it = rrset->getRdataIterator();
529 const TKEY& tkey = dynamic_cast<const TKEY&>(rdata_it->getCurrent());
530 if (tkey.getError() != Rcode::NOERROR_CODE) {
532 .arg(tkey.getError());
533 return (GssApiBufferPtr());
534 }
535 return (GssApiBufferPtr(new GssApiBuffer(tkey.getKeyLen(), tkey.getKey())));
536}
537
538void
539TKeyExchangeImpl::incrStats(const string& stat) {
540 StatsMgr& mgr = StatsMgr::instance();
541 mgr.addValue(stat, static_cast<int64_t>(1));
542 if (server_) {
543 mgr.addValue(StatsMgr::generateName("server", server_->getID(), stat),
544 static_cast<int64_t>(1));
545 }
546}
547
548const OM_uint32 TKeyExchange::TKEY_EXCHANGE_FLAGS = (GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG);
549const uint32_t TKeyExchange::TKEY_EXCHANGE_IO_TIMEOUT = 3000;
550
552 const DnsServerPtr& server, const GssTsigKeyPtr& key,
553 Callback* callback, uint32_t timeout, OM_uint32 flags)
554 : impl_(new TKeyExchangeImpl(io_service, server, key, callback, timeout, flags)) {
555}
556
560
561void
563 impl_->doExchange();
564}
565
566void
568 impl_->cancel();
569}
570
573 return (impl_->getIOService());
574}
575
576void
578 impl_->setIOService(io_service);
579}
580
581} // namespace gss_tsig
582} // 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:85
Result
Result of Upstream Fetch.
Definition io_fetch.h:58
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:152
static const Opcode & QUERY()
A constant object for the QUERY Opcode.
Definition opcode.h:178
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:228
uint16_t getCode() const
Returns the Rcode code value.
Definition rcode.h:106
@ NOERROR_CODE
0: No error (RFC1035)
Definition rcode.h:44
@ SENT_REQUEST
Client sent a signed request, waiting response.
Definition tsig.h:183
static const TSIGError & NOERROR()
A constant TSIG error object derived from Rcode::NOERROR()
Definition tsigerror.h:227
static const uint16_t GSS_API_MODE
The GSS_API constant for the Mode field.
Definition rdataclass.h:514
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.
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.
isc::asiolink::IOServicePtr getIOService()
Gets IO service.
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.
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 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).
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:61
#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:247
@ error
Definition db_log.h:118
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:670
const isc::log::MessageID TKEY_EXCHANGE_FAIL_NO_RDATA
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:574
Defines the logger used by the top-level component of kea-lfc.