Kea 3.2.0-git
gss_tsig_api.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 <gss_tsig_api.h>
10#include <gss_tsig_log.h>
11#include <gss_tsig_messages.h>
12#include <cstring>
13#include <limits>
14#include <sstream>
15
16using namespace std;
17
18namespace isc {
19namespace gss_tsig {
20
22}
23
26
27string
28gssApiErrMsg(OM_uint32 major, OM_uint32 minor) {
29 ostringstream msg;
30 GssApiBuffer msg_major;
31 OM_uint32 minor_stat = 0;
32 OM_uint32 msg_ctx = 0;
33 OM_uint32 major_stat = gss_display_status(&minor_stat, major,
34 GSS_C_GSS_CODE, GSS_C_NULL_OID,
35 &msg_ctx, msg_major.getPtr());
36 if (major_stat != GSS_S_COMPLETE) {
37 // gssApiErrMsg is likely to be used in exception handles, we
38 // can't throw here. But at the same time we want to explain
39 // what was the nature of the problem, so at least we print
40 // something on stderr, hoping the message will get to the user.
41 cerr << "gss_display_status(major=" << major << ") failed with "
42 << major_stat << endl;
43 }
44 msg << "GSSAPI error: Major = '";
45 if (!msg_major.empty()) {
46 msg << static_cast<char*>(msg_major.getValue());
47 }
48
49 if (minor != 0) {
50 GssApiBuffer msg_minor;
51 minor_stat = msg_ctx = 0;
52 major_stat = gss_display_status(&minor_stat, minor,
53 GSS_C_MECH_CODE, GSS_C_NULL_OID,
54 &msg_ctx, msg_minor.getPtr());
55 if (major_stat != GSS_S_COMPLETE) {
56 // gssApiErrMsg is likely to be used in exception handles, we
57 // can't throw here. But at the same time we want to explain
58 // what was the nature of the problem, so at least we print
59 // something on stderr, hoping the message will get to the user.
60 cerr << "gss_display_status(minor=" << minor << ") failed with "
61 << major_stat << endl;
62 }
63 msg << "' (" << major << "), Minor = '";
64 if (!msg_minor.empty()) {
65 msg << static_cast<char*>(msg_minor.getValue());
66 }
67 msg << "' (" << minor << ").";
68 } else {
69 msg << "' (" << major << ").";
70 }
71 return (msg.str());
72}
73
75 memset(&buffer_, 0, sizeof(gss_buffer_desc));
76}
77
78GssApiBuffer::GssApiBuffer(size_t length, const void* value) {
79 memset(&buffer_, 0, sizeof(gss_buffer_desc));
80 if (length > numeric_limits<uint32_t>::max()) {
81 isc_throw(OutOfRange, "GssApiBuffer constructor: length " << length
82 << " is too large");
83 }
84 buffer_.length = length;
85 if (buffer_.length > 0) {
86 // The GSS-API uses gss_release_buffer() to get rid of the buffer.
87 // That function uses free(), hence we need to use malloc() to allocate.
88 buffer_.value = malloc(buffer_.length);
89 if (!buffer_.value) {
90 buffer_.length = 0;
91 isc_throw(GssApiError, "GssApiBuffer constructor failed with "
92 << "'Cannot allocate memory'");
93 }
94 memmove(buffer_.value, value, buffer_.length);
95 }
96}
97
98GssApiBuffer::GssApiBuffer(const vector<uint8_t>& content) {
99 memset(&buffer_, 0, sizeof(gss_buffer_desc));
100 if (content.size() > numeric_limits<uint32_t>::max()) {
101 isc_throw(OutOfRange, "GssApiBuffer constructor: vector size " <<
102 content.size() << " is too large");
103 }
104 buffer_.length = content.size();
105 if (buffer_.length > 0) {
106 // The GSS-API uses gss_release_buffer() to get rid of the buffer.
107 // That function uses free(), hence we need to use malloc() to allocate.
108 buffer_.value = malloc(buffer_.length);
109 if (!buffer_.value) {
110 buffer_.length = 0;
111 isc_throw(GssApiError, "GssApiBuffer constructor failed with "
112 << "'Cannot allocate memory'");
113 }
114 memmove(buffer_.value, &content[0], buffer_.length);
115 }
116}
117
118GssApiBuffer::GssApiBuffer(const string& content) {
119 memset(&buffer_, 0, sizeof(gss_buffer_desc));
120 if (content.empty()) {
121 return;
122 }
123 if (content.size() >= numeric_limits<uint32_t>::max()) {
124 isc_throw(OutOfRange, "GssApiBuffer constructor: string size "
125 << content.size() << " is too large");
126 }
127 // The GSS-API uses gss_release_buffer() to get rid of the buffer.
128 // That function uses free(), hence we need to use malloc() to allocate.
129 buffer_.length = content.size();
130 buffer_.value = malloc(buffer_.length + 1);
131 if (!buffer_.value) {
132 buffer_.length = 0;
133 isc_throw(GssApiError, "GssApiBuffer constructor failed with "
134 << "'Cannot allocate memory'");
135 }
136 memset(buffer_.value, 0, buffer_.length + 1);
137 memmove(buffer_.value, content.c_str(), buffer_.length);
138}
139
140
142 if (buffer_.value) {
143 OM_uint32 minor = 0;
144 OM_uint32 major = gss_release_buffer(&minor, &buffer_);
145 if (major != GSS_S_COMPLETE) {
146 cerr << "gss_release_buffer failed with " << major << endl;
147 }
148 }
149}
150
151vector<uint8_t>
153 vector<uint8_t> content;
154 content.resize(buffer_.length);
155 if (buffer_.length > 0) {
156 memmove(&content[0], buffer_.value, buffer_.length);
157 }
158 return (vector<uint8_t>(content));
159}
160
161string
162GssApiBuffer::getString(bool trim) const {
163 if (buffer_.length == 0) {
164 return (string());
165 } else if (trim) {
166 return (string(static_cast<char*>(buffer_.value)));
167 } else {
168 return (string(static_cast<char*>(buffer_.value), buffer_.length));
169 }
170}
171
172GssApiName::GssApiName() : GssApiLastError(), name_(GSS_C_NO_NAME) {
173}
174
175GssApiName::GssApiName(const string& gname)
176 : GssApiLastError(), name_(GSS_C_NO_NAME) {
177 if (gname.size() >= numeric_limits<uint32_t>::max()) {
178 isc_throw(OutOfRange, "GssApiName constructor: string size "
179 << gname.size() << " is too large");
180 }
181 GssApiBuffer buf(gname);
182 OM_uint32 minor = 0;
183 OM_uint32 major = gss_import_name(&minor, buf.getPtr(),
184 GSS_C_NO_OID, &name_);
185 if (major != GSS_S_COMPLETE) {
186 isc_throw(GssApiError, "gss_import_name failed with "
187 << gssApiErrMsg(major, minor));
188 }
189}
190
192 if (name_) {
193 OM_uint32 minor = 0;
194 OM_uint32 major = gss_release_name(&minor, &name_);
195 if (major != GSS_S_COMPLETE) {
196 cerr << "gss_release_name failed with " << major << endl;
197 }
198 }
199}
200
201bool
203 OM_uint32 minor = 0;
204 int ret = -1;
205 OM_uint32 major = gss_compare_name(&minor, name_, other.name_, &ret);
206 if (major != GSS_S_COMPLETE) {
207 setLastError(major);
208 isc_throw(GssApiError, "gss_compare_name failed with "
209 << gssApiErrMsg(major, minor));
210 }
211 return (ret == 1);
212}
213
214string
216 GssApiBuffer buf;
217 OM_uint32 minor = 0;
218 OM_uint32 major = gss_display_name(&minor, name_, buf.getPtr(), 0);
219 if (major != GSS_S_COMPLETE) {
220 setLastError(major);
221 isc_throw(GssApiError, "gss_display_name failed with "
222 << gssApiErrMsg(major, minor));
223 }
224 return (buf.getString());
225}
226
227GssApiCred::GssApiCred() : GssApiLastError(), cred_(GSS_C_NO_CREDENTIAL) {
228}
229
230GssApiCred::GssApiCred(GssApiName& gname, gss_cred_usage_t cred_usage,
231 OM_uint32& lifetime)
232 : GssApiLastError(), cred_(GSS_C_NO_CREDENTIAL) {
233 cred_ = GSS_C_NO_CREDENTIAL;
234 lifetime = 0;
235 GssApiOidSet mech_oid_set;
236 OM_uint32 minor = 0;
237 OM_uint32 major = gss_acquire_cred(&minor, gname.get(), GSS_C_INDEFINITE,
238 mech_oid_set.get(), cred_usage,
239 &cred_, 0, &lifetime);
240 if (major != GSS_S_COMPLETE) {
241 isc_throw(GssApiError, "gss_acquire_cred failed with "
242 << gssApiErrMsg(major, minor));
243 }
244}
245
247 if (cred_) {
248 OM_uint32 minor = 0;
249 OM_uint32 major = gss_release_cred(&minor, &cred_);
250 if (major != GSS_S_COMPLETE) {
251 cerr << "gss_release_cred failed with " << major << endl;
252 }
253 }
254}
255
256void
257GssApiCred::inquire(GssApiName& name, gss_cred_usage_t& cred_usage,
258 OM_uint32& lifetime) {
259 // cred_usage 0 means GSS_C_BOTH.
260 lifetime = 0;
261 OM_uint32 minor = 0;
262 OM_uint32 major = gss_inquire_cred(&minor, cred_, name.getPtr(),
263 &lifetime, &cred_usage, 0);
264 if (major != GSS_S_COMPLETE) {
265 setLastError(major);
266 isc_throw(GssApiError, "gss_inquire_cred failed with "
267 << gssApiErrMsg(major, minor));
268 }
269}
270
272
273GssApiSecCtx::GssApiSecCtx(gss_ctx_id_t sec_ctx)
274 : GssApiLastError(), sec_ctx_(sec_ctx) {
275}
276
277GssApiSecCtx::GssApiSecCtx(const vector<uint8_t>& import)
278 : GssApiLastError(), sec_ctx_(GSS_C_NO_CONTEXT) {
279 GssApiBuffer buf(import);
280 OM_uint32 minor = 0;
281 OM_uint32 major = gss_import_sec_context(&minor, buf.getPtr(), &sec_ctx_);
282 if (major != GSS_S_COMPLETE) {
283 isc_throw(GssApiError, "gss_import_sec_context failed with "
284 << gssApiErrMsg(major, minor));
285 }
286}
287
289 if (sec_ctx_) {
290 OM_uint32 minor = 0;
291 OM_uint32 major = gss_delete_sec_context(&minor, &sec_ctx_, 0);
292 if (major != GSS_S_COMPLETE) {
293 cerr << "gss_delete_sec_context failed with " << major << endl;
294 }
295 }
296}
297
298vector<uint8_t>
300 GssApiBuffer buf;
301 OM_uint32 minor = 0;
302 OM_uint32 major = gss_export_sec_context(&minor, &sec_ctx_, buf.getPtr());
303 if (major != GSS_S_COMPLETE) {
304 setLastError(major);
305 isc_throw(GssApiError, "gss_export_sec_context failed with "
306 << gssApiErrMsg(major, minor));
307 }
308 return (buf.getContent());
309}
310
311OM_uint32
313 OM_uint32 lifetime = 0;
314 OM_uint32 minor = 0;
315 OM_uint32 major = gss_context_time(&minor, sec_ctx_, &lifetime);
316 if (major != GSS_S_COMPLETE) {
317 setLastError(major);
318 isc_throw(GssApiError, "gss_context_time failed with "
319 << gssApiErrMsg(major, minor));
320 }
321 return (lifetime);
322}
323
324void
326 OM_uint32& lifetime, OM_uint32& flags,
327 bool& local, bool& established) {
328 lifetime = flags = 0;
329 local = established = false;
330 int locally_initiated = 0;
331 int open = 0;
332 OM_uint32 minor = 0;
333 OM_uint32 major = gss_inquire_context(&minor, sec_ctx_,
334 source.getPtr(), target.getPtr(),
335 &lifetime, 0, &flags,
336 &locally_initiated, &open);
337 if (major != GSS_S_COMPLETE) {
338 setLastError(major);
339 isc_throw(GssApiError, "gss_inquire_context failed with "
340 << gssApiErrMsg(major, minor));
341 }
342 local = (locally_initiated != 0);
343 established = (open != 0);
344}
345
346void
348 OM_uint32 minor = 0;
349 OM_uint32 major = gss_get_mic(&minor, sec_ctx_, GSS_C_QOP_DEFAULT,
350 gmessage.getPtr(), gsig.getPtr());
351 if (major != GSS_S_COMPLETE) {
352 setLastError(major);
353 isc_throw(GssApiError, "gss_get_mic failed with "
354 << gssApiErrMsg(major, minor));
355 }
356}
357
358void
360 OM_uint32 minor = 0;
361 OM_uint32 major = gss_verify_mic(&minor, sec_ctx_, gmessage.getPtr(),
362 gsig.getPtr(), 0);
363 if (major != GSS_S_COMPLETE) {
364 string err_msg = gssApiErrMsg(major, minor);
365 // Should use minor == G_BAD_DIRECTION but the code point is
366 // in a generated include not provided by all packages.
367 if (ignore_bad_direction_ && (major == GSS_S_BAD_MIC) &&
368#ifdef G_BAD_DIRECTION
369 (minor == G_BAD_DIRECTION)
370#else
371 (err_msg.find("wrong direction") != string::npos)
372#endif
373 ) {
375 return;
376 }
377 setLastError(major);
378 isc_throw(GssApiError, "gss_verify_mic failed with " << err_msg);
379 }
380}
381
382bool
383GssApiSecCtx::init(GssApiCredPtr credp, GssApiName& target, OM_uint32 flags,
384 GssApiBuffer& intoken, GssApiBuffer& outtoken,
385 OM_uint32& lifetime) {
386 gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
387 if (credp) {
388 cred = credp->get();
389 }
390 lifetime = 0;
391 OM_uint32 ret_flags = 0;
392 OM_uint32 minor = 0;
393 OM_uint32 major = gss_init_sec_context(&minor, cred,
394 &sec_ctx_, target.get(),
396 flags, GSS_C_INDEFINITE,
397 GSS_C_NO_CHANNEL_BINDINGS,
398 intoken.getPtr(), 0,
399 outtoken.getPtr(), &ret_flags,
400 &lifetime);
401 switch (major) {
402 case GSS_S_COMPLETE:
403 if ((flags & GSS_C_REPLAY_FLAG) &&
404 ((ret_flags & GSS_C_REPLAY_FLAG) == 0)) {
405 isc_throw(GssApiError, "gss_init_sec_context failed to grant "
406 "requested anti-replay");
407 }
408 if ((flags & GSS_C_SEQUENCE_FLAG) &&
409 ((ret_flags & GSS_C_SEQUENCE_FLAG) == 0)) {
410 isc_throw(GssApiError, "gss_init_sec_context failed to grant "
411 "requested sequence");
412 }
413 if ((flags & GSS_C_MUTUAL_FLAG) &&
414 ((ret_flags & GSS_C_MUTUAL_FLAG) == 0)) {
415 isc_throw(GssApiError, "gss_init_sec_context failed to grant "
416 "requested mutual authentication");
417 }
418 return (true);
419 case GSS_S_CONTINUE_NEEDED:
420 return (false);
421 default:
422 setLastError(major);
423 isc_throw(GssApiError, "gss_init_sec_context failed with "
424 << gssApiErrMsg(major, minor));
425 }
426}
427
428bool
430 GssApiName& source, GssApiBuffer& outtoken) {
431 OM_uint32 minor = 0;
432 OM_uint32 major = gss_accept_sec_context(&minor, &sec_ctx_, cred.get(),
433 intoken.getPtr(),
434 GSS_C_NO_CHANNEL_BINDINGS,
435 source.getPtr(), 0,
436 outtoken.getPtr(), 0, 0, 0);
437 switch (major) {
438 case GSS_S_COMPLETE:
439 return (true);
440 case GSS_S_CONTINUE_NEEDED:
441 return (false);
442 default:
443 setLastError(major);
444 isc_throw(GssApiError, "gss_accept_sec_context failed with "
445 << gssApiErrMsg(major, minor));
446 }
447}
448
449GssApiOid::GssApiOid() : oid_(GSS_C_NO_OID) {
450 // The GSS-API uses gss_release_oid() to release OID buffer. That function
451 // uses free(), hence we need to use malloc() to allocate it.
452 oid_ = static_cast<gss_OID>(malloc(sizeof(gss_OID_desc)));
453 if (!oid_) {
454 isc_throw(GssApiError, "GssApiOid constructor failed with "
455 << "'Cannot allocate memory' (desc)");
456 }
457 memset(oid_, 0, sizeof(gss_OID_desc));
458}
459
460GssApiOid::GssApiOid(const vector<uint8_t>& elements) : oid_(GSS_C_NO_OID) {
461 if (elements.size() > 1024) {
462 isc_throw(OutOfRange, "Too large argument to GssApiOid ("
463 << elements.size() << " > 1024)");
464 }
465 // The GSS-API uses gss_release_oid() to release OID buffer. That function
466 // uses free(), hence we need to use malloc() to allocate it.
467 oid_ = static_cast<gss_OID>(malloc(sizeof(gss_OID_desc)));
468 if (!oid_) {
469 isc_throw(GssApiError, "GssApiOid constructor failed with "
470 << "'Cannot allocate memory' (desc)");
471 }
472 memset(oid_, 0, sizeof(gss_OID_desc));
473 oid_->length = elements.size();
474 if (oid_->length > 0) {
475 // The GSS-API uses gss_release_oid_set() to release OID buffer.
476 // That function uses free(), hence we need to use malloc() to allocate.
477 oid_->elements = malloc(oid_->length);
478 if (!oid_->elements) {
479 oid_->length = 0;
480 isc_throw(GssApiError, "GssApiOid constructor failed with "
481 << "'Cannot allocate memory' (elements)");
482 }
483 memmove(oid_->elements, &elements[0], oid_->length);
484 }
485}
486
487GssApiOid::GssApiOid(const string& str) : oid_(GSS_C_NO_OID) {
488#ifdef HAVE_GSS_STR_TO_OID
489 GssApiBuffer buf(str);
490 OM_uint32 minor = 0;
491 OM_uint32 major = gss_str_to_oid(&minor, buf.getPtr(), &oid_);
492 if (major != GSS_S_COMPLETE) {
493 isc_throw(GssApiError, "gss_str_to_oid failed with "
494 << gssApiErrMsg(major, minor));
495 }
496#else
497 isc_throw(NotImplemented, "gss_str_to_oid(" << str << ") is not available");
498#endif
499}
500
502 if (oid_) {
503 OM_uint32 minor = 0;
504 OM_uint32 major = gss_release_oid(&minor, &oid_);
505 if (major != GSS_S_COMPLETE) {
506 cerr << "gss_release_oid failed with " << major << endl;
507 }
508 }
509}
510
511string
513 GssApiBuffer buf;
514 OM_uint32 minor = 0;
515 OM_uint32 major = gss_oid_to_str(&minor, oid_, buf.getPtr());
516 if (major != GSS_S_COMPLETE) {
517 isc_throw(GssApiError, "gss_oid_to_str failed with "
518 << gssApiErrMsg(major, minor));
519 }
520 return (buf.getString(true));
521}
522
523namespace {
524
525vector<uint8_t> ISC_GSS_KRB5_MECHANISM_vect =
526 { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02 };
527}
528
529GssApiOid ISC_GSS_KRB5_MECHANISM(ISC_GSS_KRB5_MECHANISM_vect);
530
531namespace {
532
533vector<uint8_t> ISC_GSS_SPNEGO_MECHANISM_vect =
534 { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02 };
535}
536
537GssApiOid ISC_GSS_SPNEGO_MECHANISM(ISC_GSS_SPNEGO_MECHANISM_vect);
538
540 oid_set_ = GSS_C_NO_OID_SET;
541 if (!fill) {
542 return;
543 }
544 OM_uint32 minor = 0;
545 OM_uint32 major = gss_create_empty_oid_set(&minor, &oid_set_);
546 if (major != GSS_S_COMPLETE) {
547 isc_throw(GssApiError, "gss_create_empty_oid_set failed with "
548 << gssApiErrMsg(major, minor));
549 }
550 minor = 0;
551 major = gss_add_oid_set_member(&minor, ISC_GSS_KRB5_MECHANISM.get(),
552 &oid_set_);
553 if (major != GSS_S_COMPLETE) {
554 isc_throw(GssApiError, "gss_add_oid_set_member(KRB5) failed with "
555 << gssApiErrMsg(major, minor));
556 }
557 minor = 0;
558 major = gss_add_oid_set_member(&minor, ISC_GSS_SPNEGO_MECHANISM.get(),
559 &oid_set_);
560 if (major != GSS_S_COMPLETE) {
561 isc_throw(GssApiError, "gss_add_oid_set_member(SPNEGO) failed with "
562 << gssApiErrMsg(major, minor));
563 }
564}
565
567 if (oid_set_) {
568 OM_uint32 minor = 0;
569 OM_uint32 major = gss_release_oid_set(&minor, &oid_set_);
570 if (major != GSS_S_COMPLETE) {
571 cerr << "gss_release_oid_set failed with " << major << endl;
572 }
573 }
574}
575
576} // end of namespace isc::gss_tsig
577} // end of namespace isc
A generic exception that is thrown when a function is not implemented.
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
std::vector< uint8_t > getContent() const
Get the content as a vector.
bool empty() const
Empty predicate.
gss_buffer_t getPtr()
Get pointer.
void * getValue()
Get the value.
std::string getString(bool trim=false) const
Get the content as a string.
GSS-API credential.
void inquire(GssApiName &name, gss_cred_usage_t &cred_usage, OM_uint32 &lifetime)
Inquire.
gss_cred_id_t get()
Get the value.
GSS-API exception.
void setLastError(int error)
Set the last error.
virtual ~GssApiLastError()
Destructor.
gss_name_t * getPtr()
Get pointer.
std::string toString()
textual representation.
gss_name_t get()
Get the value.
bool compare(GssApiName &other)
Compare.
gss_OID_set get()
Get the value.
GssApiOidSet(bool fill=true)
Constructor.
std::string toString()
Get textual representation.
void sign(GssApiBuffer &gmessage, GssApiBuffer &gsig)
Sign.
static bool ignore_bad_direction_
Ignore bad direction flag.
bool init(GssApiCredPtr credp, GssApiName &target, OM_uint32 flags, GssApiBuffer &intoken, GssApiBuffer &outtoken, OM_uint32 &lifetime)
Init.
void verify(GssApiBuffer &gmessage, GssApiBuffer &gsig)
Verify.
std::vector< uint8_t > serialize()
Export.
OM_uint32 getLifetime()
Get the lifetime (validity in seconds).
GssApiSecCtx(gss_ctx_id_t sec_ctx)
Constructor.
void inquire(GssApiName &source, GssApiName &target, OM_uint32 &lifetime, OM_uint32 &flags, bool &local, bool &established)
Inquire.
bool accept(GssApiCred &cred, GssApiBuffer &intoken, GssApiName &source, GssApiBuffer &outtoken)
Accept.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
C++ binding for the GSS-API.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
GssApiOid ISC_GSS_SPNEGO_MECHANISM(ISC_GSS_SPNEGO_MECHANISM_vect)
The SPNEGO OID.
string gssApiErrMsg(OM_uint32 major, OM_uint32 minor)
An the error message.
GssApiOid ISC_GSS_KRB5_MECHANISM(ISC_GSS_KRB5_MECHANISM_vect)
The Kerberos 5 OID.
isc::log::Logger gss_tsig_logger("gss-tsig-hooks")
const isc::log::MessageID GSS_TSIG_IGNORED_BAD_DIRECTION
boost::shared_ptr< GssApiCred > GssApiCredPtr
Shared pointer to GSS-API credential.
Defines the logger used by the top-level component of kea-lfc.