]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/gss_context.cc
Merge pull request #12670 from jsoref/reword-remote
[thirdparty/pdns.git] / pdns / gss_context.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include "gss_context.hh"
24 #include "logger.hh"
25
26 #ifndef ENABLE_GSS_TSIG
27
28 std::tuple<size_t, size_t, size_t> GssContext::getCounts() { return std::tuple<size_t, size_t, size_t>(0, 0, 0); }
29 bool GssContext::supported() { return false; }
30 GssContext::GssContext() :
31 d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
32 GssContext::GssContext(const DNSName& /* label */) :
33 d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
34 void GssContext::setLocalPrincipal(const std::string& /* name */) {}
35 bool GssContext::getLocalPrincipal(std::string& /* name */) { return false; }
36 void GssContext::setPeerPrincipal(const std::string& /* name */) {}
37 bool GssContext::getPeerPrincipal(std::string& /* name */) { return false; }
38 void GssContext::generateLabel(const std::string& /* suffix */) {}
39 void GssContext::setLabel(const DNSName& /* label */) {}
40 bool GssContext::init(const std::string& /* input */, std::string& /* output */) { return false; }
41 bool GssContext::accept(const std::string& /* input */, std::string& /* output */) { return false; }
42 bool GssContext::destroy() { return false; }
43 bool GssContext::expired() { return false; }
44 bool GssContext::valid() { return false; }
45 bool GssContext::sign(const std::string& /* input */, std::string& /* output */) { return false; }
46 bool GssContext::verify(const std::string& /* input */, const std::string& /* signature */) { return false; }
47 GssContextError GssContext::getError() { return GSS_CONTEXT_UNSUPPORTED; }
48
49 #else
50
51 #include <unordered_map>
52
53 #include "lock.hh"
54
55 #define TSIG_GSS_EXPIRE_INTERVAL 60
56
57 class GssCredential : boost::noncopyable
58 {
59 public:
60 GssCredential(const std::string& name, const gss_cred_usage_t usage) :
61 d_nameS(name), d_usage(usage)
62 {
63 gss_buffer_desc buffer;
64
65 if (!name.empty()) {
66 buffer.length = name.size();
67 buffer.value = const_cast<void*>(static_cast<const void*>(name.c_str()));
68 OM_uint32 min;
69 auto maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
70 if (maj != GSS_S_COMPLETE) {
71 d_name = GSS_C_NO_NAME;
72 d_valid = false;
73 return;
74 }
75 }
76
77 renew();
78 };
79
80 ~GssCredential()
81 {
82 OM_uint32 tmp_min __attribute__((unused));
83 if (d_cred != GSS_C_NO_CREDENTIAL) {
84 (void)gss_release_cred(&tmp_min, &d_cred);
85 }
86 if (d_name != GSS_C_NO_NAME) {
87 (void)gss_release_name(&tmp_min, &d_name);
88 }
89 };
90
91 bool expired() const
92 {
93 if (d_expires == -1) {
94 return false;
95 }
96 return time(nullptr) > d_expires;
97 }
98
99 bool renew()
100 {
101 OM_uint32 time_rec, tmp_maj, tmp_min __attribute__((unused));
102 tmp_maj = gss_acquire_cred(&tmp_min, d_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, d_usage, &d_cred, nullptr, &time_rec);
103
104 if (tmp_maj != GSS_S_COMPLETE) {
105 d_valid = false;
106 (void)gss_release_name(&tmp_min, &d_name);
107 d_name = GSS_C_NO_NAME;
108 return false;
109 }
110
111 d_valid = true;
112
113 // We do not want forever, but a good time
114 if (time_rec == GSS_C_INDEFINITE) {
115 time_rec = 24 * 60 * 60;
116 }
117 d_expires = time(nullptr) + time_rec;
118
119 return true;
120 }
121
122 bool valid()
123 {
124 return d_valid && !expired();
125 }
126
127 std::string d_nameS;
128 gss_cred_usage_t d_usage;
129 gss_name_t d_name{GSS_C_NO_NAME};
130 gss_cred_id_t d_cred{GSS_C_NO_CREDENTIAL};
131 time_t d_expires{time(nullptr) + 60}; // partly initialized will be cleaned up
132 bool d_valid{false};
133 }; // GssCredential
134
135 static LockGuarded<std::unordered_map<std::string, std::shared_ptr<GssCredential>>> s_gss_accept_creds;
136 static LockGuarded<std::unordered_map<std::string, std::shared_ptr<GssCredential>>> s_gss_init_creds;
137
138 class GssSecContext : boost::noncopyable
139 {
140 public:
141 GssSecContext(std::shared_ptr<GssCredential> cred)
142 {
143 if (!cred->valid()) {
144 throw PDNSException("Invalid credential " + cred->d_nameS);
145 }
146 d_cred = cred;
147 }
148
149 ~GssSecContext()
150 {
151 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
152 if (d_ctx != GSS_C_NO_CONTEXT) {
153 tmp_maj = gss_delete_sec_context(&tmp_min, &d_ctx, GSS_C_NO_BUFFER);
154 }
155 if (d_peer_name != GSS_C_NO_NAME) {
156 tmp_maj = gss_release_name(&tmp_min, &(d_peer_name));
157 }
158 }
159
160 std::shared_ptr<GssCredential> d_cred;
161 GssContextType d_type{GSS_CONTEXT_NONE};
162 gss_ctx_id_t d_ctx{GSS_C_NO_CONTEXT};
163 gss_name_t d_peer_name{GSS_C_NO_NAME};
164 time_t d_expires{time(nullptr) + 60}; // partly initialized wil be cleaned up
165
166 enum
167 {
168 GssStateInitial,
169 GssStateNegotiate,
170 GssStateComplete,
171 GssStateError
172 } d_state{GssStateInitial};
173 }; // GssSecContext
174
175 static LockGuarded<std::unordered_map<DNSName, std::shared_ptr<GssSecContext>>> s_gss_sec_context;
176
177 template <typename T>
178 static void doExpire(T& m, time_t now)
179 {
180 auto lock = m.lock();
181 for (auto i = lock->begin(); i != lock->end();) {
182 if (now > i->second->d_expires) {
183 i = lock->erase(i);
184 }
185 else {
186 ++i;
187 }
188 }
189 }
190
191 static void expire()
192 {
193 static time_t s_last_expired;
194 time_t now = time(nullptr);
195 if (now - s_last_expired < TSIG_GSS_EXPIRE_INTERVAL) {
196 return;
197 }
198 s_last_expired = now;
199 doExpire(s_gss_init_creds, now);
200 doExpire(s_gss_accept_creds, now);
201 doExpire(s_gss_sec_context, now);
202 }
203
204 bool GssContext::supported() { return true; }
205
206 void GssContext::initialize()
207 {
208 d_peerPrincipal = "";
209 d_localPrincipal = "";
210 d_error = GSS_CONTEXT_NO_ERROR;
211 d_type = GSS_CONTEXT_NONE;
212 }
213
214 GssContext::GssContext()
215 {
216 initialize();
217 generateLabel("pdns.tsig.");
218 }
219
220 GssContext::GssContext(const DNSName& label)
221 {
222 initialize();
223 setLabel(label);
224 }
225
226 void GssContext::generateLabel(const std::string& suffix)
227 {
228 std::ostringstream oss;
229 oss << std::hex << time(nullptr) << "." << suffix;
230 setLabel(DNSName(oss.str()));
231 }
232
233 void GssContext::setLabel(const DNSName& label)
234 {
235 d_label = label;
236 auto lock = s_gss_sec_context.lock();
237 auto it = lock->find(d_label);
238 if (it != lock->end()) {
239 d_secctx = it->second;
240 d_type = d_secctx->d_type;
241 }
242 }
243
244 bool GssContext::expired()
245 {
246 return (!d_secctx || (d_secctx->d_expires > -1 && d_secctx->d_expires < time(nullptr)));
247 }
248
249 bool GssContext::valid()
250 {
251 return (d_secctx && !expired() && d_secctx->d_state == GssSecContext::GssStateComplete);
252 }
253
254 bool GssContext::init(const std::string& input, std::string& output)
255 {
256 expire();
257
258 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
259 OM_uint32 maj, min;
260 gss_buffer_desc recv_tok, send_tok, buffer;
261 OM_uint32 flags;
262 OM_uint32 expires;
263
264 if (d_label.empty()) {
265 d_error = GSS_CONTEXT_INVALID;
266 return false;
267 }
268
269 d_type = GSS_CONTEXT_INIT;
270 std::shared_ptr<GssCredential> cred;
271 {
272 auto lock = s_gss_init_creds.lock();
273 auto it = lock->find(d_localPrincipal);
274 if (it == lock->end()) {
275 it = lock->emplace(d_localPrincipal, std::make_shared<GssCredential>(d_localPrincipal, GSS_C_INITIATE)).first;
276 }
277 cred = it->second;
278 }
279
280 // see if we can find a context in non-completed state
281 if (d_secctx) {
282 if (d_secctx->d_state != GssSecContext::GssStateNegotiate) {
283 d_error = GSS_CONTEXT_INVALID;
284 return false;
285 }
286 }
287 else {
288 // make context
289 auto lock = s_gss_sec_context.lock();
290 d_secctx = std::make_shared<GssSecContext>(cred);
291 d_secctx->d_state = GssSecContext::GssStateNegotiate;
292 d_secctx->d_type = d_type;
293 (*lock)[d_label] = d_secctx;
294 }
295
296 recv_tok.length = input.size();
297 recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
298
299 if (!d_peerPrincipal.empty()) {
300 buffer.value = const_cast<void*>(static_cast<const void*>(d_peerPrincipal.c_str()));
301 buffer.length = d_peerPrincipal.size();
302 maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_secctx->d_peer_name));
303 if (maj != GSS_S_COMPLETE) {
304 processError("gss_import_name", maj, min);
305 return false;
306 }
307 }
308
309 maj = gss_init_sec_context(&min, cred->d_cred, &d_secctx->d_ctx, d_secctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, nullptr, &send_tok, &flags, &expires);
310
311 if (send_tok.length > 0) {
312 output.assign(static_cast<char*>(send_tok.value), send_tok.length);
313 tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
314 }
315
316 if (maj == GSS_S_COMPLETE) {
317 // We do not want forever
318 if (expires == GSS_C_INDEFINITE) {
319 expires = 60;
320 }
321 d_secctx->d_expires = time(nullptr) + expires;
322 d_secctx->d_state = GssSecContext::GssStateComplete;
323 return true;
324 }
325 else if (maj != GSS_S_CONTINUE_NEEDED) {
326 processError("gss_init_sec_context", maj, min);
327 }
328
329 return (maj == GSS_S_CONTINUE_NEEDED);
330 }
331
332 bool GssContext::accept(const std::string& input, std::string& output)
333 {
334 expire();
335
336 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
337 OM_uint32 maj, min;
338 gss_buffer_desc recv_tok, send_tok;
339 OM_uint32 flags;
340 OM_uint32 expires;
341
342 if (d_label.empty()) {
343 d_error = GSS_CONTEXT_INVALID;
344 return false;
345 }
346
347 d_type = GSS_CONTEXT_ACCEPT;
348 std::shared_ptr<GssCredential> cred;
349 {
350 auto lock = s_gss_accept_creds.lock();
351 auto it = lock->find(d_localPrincipal);
352 if (it == lock->end()) {
353 it = lock->emplace(d_localPrincipal, std::make_shared<GssCredential>(d_localPrincipal, GSS_C_ACCEPT)).first;
354 }
355 cred = it->second;
356 }
357
358 // see if we can find a context in non-completed state
359 if (d_secctx) {
360 if (d_secctx->d_state != GssSecContext::GssStateNegotiate) {
361 d_error = GSS_CONTEXT_INVALID;
362 return false;
363 }
364 }
365 else {
366 // make context
367 auto lock = s_gss_sec_context.lock();
368 d_secctx = std::make_shared<GssSecContext>(cred);
369 d_secctx->d_state = GssSecContext::GssStateNegotiate;
370 d_secctx->d_type = d_type;
371 (*lock)[d_label] = d_secctx;
372 }
373
374 recv_tok.length = input.size();
375 recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
376
377 maj = gss_accept_sec_context(&min, &d_secctx->d_ctx, cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &d_secctx->d_peer_name, nullptr, &send_tok, &flags, &expires, nullptr);
378
379 if (send_tok.length > 0) {
380 output.assign(static_cast<char*>(send_tok.value), send_tok.length);
381 tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
382 }
383
384 if (maj == GSS_S_COMPLETE) {
385 // We do not want forever
386 if (expires == GSS_C_INDEFINITE) {
387 expires = 60;
388 }
389 d_secctx->d_expires = time(nullptr) + expires;
390 d_secctx->d_state = GssSecContext::GssStateComplete;
391 return true;
392 }
393 else if (maj != GSS_S_CONTINUE_NEEDED) {
394 processError("gss_accept_sec_context", maj, min);
395 }
396 return (maj == GSS_S_CONTINUE_NEEDED);
397 };
398
399 bool GssContext::sign(const std::string& input, std::string& output)
400 {
401 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
402 OM_uint32 maj, min;
403
404 gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
405 gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
406
407 recv_tok.length = input.size();
408 recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
409
410 maj = gss_get_mic(&min, d_secctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok);
411
412 if (send_tok.length > 0) {
413 output.assign(static_cast<char*>(send_tok.value), send_tok.length);
414 tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
415 }
416
417 if (maj != GSS_S_COMPLETE) {
418 processError("gss_get_mic", maj, min);
419 }
420
421 return (maj == GSS_S_COMPLETE);
422 }
423
424 bool GssContext::verify(const std::string& input, const std::string& signature)
425 {
426 OM_uint32 maj, min;
427
428 gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
429 gss_buffer_desc sign_tok = GSS_C_EMPTY_BUFFER;
430
431 recv_tok.length = input.size();
432 recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
433 sign_tok.length = signature.size();
434 sign_tok.value = const_cast<void*>(static_cast<const void*>(signature.c_str()));
435
436 maj = gss_verify_mic(&min, d_secctx->d_ctx, &recv_tok, &sign_tok, nullptr);
437
438 if (maj != GSS_S_COMPLETE) {
439 processError("gss_get_mic", maj, min);
440 }
441
442 return (maj == GSS_S_COMPLETE);
443 }
444
445 bool GssContext::destroy()
446 {
447 if (d_label.empty()) {
448 return false;
449 }
450 auto lock = s_gss_sec_context.lock();
451 return lock->erase(d_label) == 1;
452 }
453
454 void GssContext::setLocalPrincipal(const std::string& name)
455 {
456 d_localPrincipal = name;
457 }
458
459 bool GssContext::getLocalPrincipal(std::string& name)
460 {
461 name = d_localPrincipal;
462 return name.size() > 0;
463 }
464
465 void GssContext::setPeerPrincipal(const std::string& name)
466 {
467 d_peerPrincipal = name;
468 }
469
470 bool GssContext::getPeerPrincipal(std::string& name)
471 {
472 gss_buffer_desc value;
473 OM_uint32 maj, min;
474
475 if (d_secctx->d_peer_name != GSS_C_NO_NAME) {
476 maj = gss_display_name(&min, d_secctx->d_peer_name, &value, nullptr);
477 if (maj == GSS_S_COMPLETE && value.length > 0) {
478 name.assign(static_cast<char*>(value.value), value.length);
479 maj = gss_release_buffer(&min, &value);
480 return true;
481 }
482 else {
483 return false;
484 }
485 }
486 else {
487 return false;
488 }
489 }
490
491 std::tuple<size_t, size_t, size_t> GssContext::getCounts()
492 {
493 return {s_gss_init_creds.lock()->size(), s_gss_accept_creds.lock()->size(), s_gss_sec_context.lock()->size()};
494 }
495
496 void GssContext::processError(const std::string& method, OM_uint32 maj, OM_uint32 min)
497 {
498 OM_uint32 tmp_min;
499 gss_buffer_desc msg;
500 OM_uint32 msg_ctx;
501
502 msg_ctx = 0;
503 while (1) {
504 ostringstream oss;
505 if (gss_display_status(&tmp_min, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg) == GSS_S_COMPLETE) {
506 oss << method << ": " << msg.value;
507 }
508 else {
509 oss << method << ": ?";
510 }
511 if (msg.length != 0) {
512 gss_release_buffer(&tmp_min, &msg);
513 }
514 d_gss_errors.push_back(oss.str());
515 if (!msg_ctx)
516 break;
517 }
518 msg_ctx = 0;
519 while (1) {
520 ostringstream oss;
521 if (gss_display_status(&tmp_min, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg) == GSS_S_COMPLETE) {
522 oss << method << ": " << msg.value;
523 }
524 else {
525 oss << method << ": ?";
526 }
527 if (msg.length != 0) {
528 gss_release_buffer(&tmp_min, &msg);
529 }
530 d_gss_errors.push_back(oss.str());
531 if (!msg_ctx)
532 break;
533 }
534 }
535
536 #endif
537
538 bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac)
539 {
540 string tmp_mac;
541 GssContext gssctx(context);
542 if (!gssctx.valid()) {
543 g_log << Logger::Error << "GSS context '" << context << "' is not valid" << endl;
544 for (const string& error : gssctx.getErrorStrings()) {
545 g_log << Logger::Error << "GSS error: " << error << endl;
546 ;
547 }
548 return false;
549 }
550
551 if (!gssctx.sign(message, tmp_mac)) {
552 g_log << Logger::Error << "Could not sign message using GSS context '" << context << "'" << endl;
553 for (const string& error : gssctx.getErrorStrings()) {
554 g_log << Logger::Error << "GSS error: " << error << endl;
555 ;
556 }
557 return false;
558 }
559 mac = tmp_mac;
560 return true;
561 }
562
563 bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac)
564 {
565 GssContext gssctx(context);
566 if (!gssctx.valid()) {
567 g_log << Logger::Error << "GSS context '" << context << "' is not valid" << endl;
568 for (const string& error : gssctx.getErrorStrings()) {
569 g_log << Logger::Error << "GSS error: " << error << endl;
570 ;
571 }
572 return false;
573 }
574
575 if (!gssctx.verify(message, mac)) {
576 g_log << Logger::Error << "Could not verify message using GSS context '" << context << "'" << endl;
577 for (const string& error : gssctx.getErrorStrings()) {
578 g_log << Logger::Error << "GSS error: " << error << endl;
579 ;
580 }
581 return false;
582 }
583 return true;
584 }