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