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