]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/gss_context.cc
Merge pull request #7628 from tcely/patch-3
[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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <map>
26 #include <string>
27 #include "namespaces.hh"
28 #include "dns.hh"
29 #include "dnsparser.hh"
30 #include "dnspacket.hh"
31 #include "dnsrecords.hh"
32 #include "logger.hh"
33 #include "lock.hh"
34 #include "arguments.hh"
35
36 #include <boost/shared_ptr.hpp>
37 #include <boost/make_shared.hpp>
38 #include "gss_context.hh"
39
40 #ifndef ENABLE_GSS_TSIG
41
42 bool GssContext::supported() { return false; }
43 GssContext::GssContext() : d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
44 GssContext::GssContext(const DNSName& label) : d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
45 void GssContext::setLocalPrincipal(const std::string& name) {}
46 bool GssContext::getLocalPrincipal(std::string& name) { return false; }
47 void GssContext::setPeerPrincipal(const std::string& name) {}
48 bool GssContext::getPeerPrincipal(std::string& name) { return false; }
49 void GssContext::generateLabel(const std::string& suffix) {}
50 void GssContext::setLabel(const DNSName& label) {}
51 bool GssContext::init(const std::string &input, std::string& output) { return false; }
52 bool GssContext::accept(const std::string &input, std::string& output) { return false; }
53 bool GssContext::destroy() { return false; }
54 bool GssContext::expired() { return false; }
55 bool GssContext::valid() { return false; }
56 bool GssContext::sign(const std::string &input, std::string& output) { return false; }
57 bool GssContext::verify(const std::string &input, const std::string &signature) { return false; }
58 GssContextError GssContext::getError() { return GSS_CONTEXT_UNSUPPORTED; }
59
60 #else
61
62 class GssCredential : boost::noncopyable {
63 public:
64 GssCredential(const std::string& name, const gss_cred_usage_t usage) :
65 d_valid(false), d_nameS(name), d_name(GSS_C_NO_NAME), d_cred(GSS_C_NO_CREDENTIAL), d_usage(usage) {
66 gss_buffer_desc buffer;
67
68 if (name.empty() == false) {
69 buffer.length = name.size();
70 buffer.value = (void*)name.c_str();
71 d_maj = gss_import_name(&d_min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
72 if (d_maj != GSS_S_COMPLETE) {
73 d_valid = false;
74 return;
75 }
76 }
77
78 renew();
79 };
80
81 ~GssCredential() {
82 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
83 if (d_cred != GSS_C_NO_CREDENTIAL)
84 tmp_maj = gss_release_cred(&tmp_min, &d_cred);
85 if (d_name != GSS_C_NO_NAME)
86 tmp_maj = gss_release_name(&tmp_min, &d_name);
87 };
88
89 bool expired() const {
90 if (d_expires == -1) return false;
91 return time((time_t*)NULL)>d_expires;
92 }
93
94 bool renew() {
95 OM_uint32 time_rec, tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
96 d_maj = gss_acquire_cred(&d_min, d_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, d_usage, &d_cred, NULL, &time_rec);
97
98 if (d_maj != GSS_S_COMPLETE) {
99 d_valid = false;
100 tmp_maj = gss_release_name(&tmp_min, &d_name);
101 d_name = GSS_C_NO_NAME;
102 return false;
103 }
104
105 d_valid = true;
106
107 if (time_rec > GSS_C_INDEFINITE) {
108 d_expires = time((time_t*)NULL)+time_rec;
109 } else {
110 d_expires = -1;
111 }
112
113 return true;
114 }
115
116 bool valid() {
117 return d_valid && !expired();
118 }
119
120 OM_uint32 d_maj,d_min;
121
122 bool d_valid;
123 int64_t d_expires;
124 std::string d_nameS;
125 gss_name_t d_name;
126 gss_cred_id_t d_cred;
127 gss_cred_usage_t d_usage;
128 };
129
130 std::map<std::string, boost::shared_ptr<GssCredential> > s_gss_accept_creds;
131 std::map<std::string, boost::shared_ptr<GssCredential> > s_gss_init_creds;
132
133 class GssSecContext : boost::noncopyable {
134 public:
135 GssSecContext(boost::shared_ptr<GssCredential> cred) {
136 if (cred->valid() == false) throw PDNSException("Invalid credential " + cred->d_nameS);
137 d_cred = cred;
138 d_state = GssStateInitial;
139 d_ctx = GSS_C_NO_CONTEXT;
140 d_expires = 0;
141 d_maj = d_min = 0;
142 d_peer_name = GSS_C_NO_NAME;
143 d_type = GSS_CONTEXT_NONE;
144 }
145
146 ~GssSecContext() {
147 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
148 if (d_ctx != GSS_C_NO_CONTEXT) {
149 tmp_maj = gss_delete_sec_context(&tmp_min, &d_ctx, GSS_C_NO_BUFFER);
150 }
151 if (d_peer_name != GSS_C_NO_NAME) {
152 tmp_maj = gss_release_name(&tmp_min, &(d_peer_name));
153 }
154 }
155
156 GssContextType d_type;
157 gss_ctx_id_t d_ctx;
158 gss_name_t d_peer_name;
159 int64_t d_expires;
160 boost::shared_ptr<GssCredential> d_cred;
161 OM_uint32 d_maj,d_min;
162
163 enum {
164 GssStateInitial,
165 GssStateNegotiate,
166 GssStateComplete,
167 GssStateError
168 } d_state;
169
170 };
171
172 std::map<DNSName, boost::shared_ptr<GssSecContext> > s_gss_sec_context;
173
174 bool GssContext::supported() { return true; }
175
176 void GssContext::initialize() {
177 d_peerPrincipal = "";
178 d_localPrincipal = "";
179 d_error = GSS_CONTEXT_NO_ERROR;
180 d_type = GSS_CONTEXT_NONE;
181 }
182
183 GssContext::GssContext() {
184 initialize();
185 generateLabel("pdns.tsig.");
186 }
187
188 GssContext::GssContext(const DNSName& label) {
189 initialize();
190 setLabel(label);
191 }
192
193 void GssContext::generateLabel(const std::string& suffix) {
194 std::ostringstream oss;
195 oss << std::hex << time((time_t*)NULL) << "." << suffix;
196 setLabel(DNSName(oss.str()));
197 }
198
199 void GssContext::setLabel(const DNSName& label) {
200 d_label = label;
201 if (s_gss_sec_context.find(d_label) != s_gss_sec_context.end()) {
202 d_ctx = s_gss_sec_context[d_label];
203 d_type = d_ctx->d_type;
204 }
205 }
206
207 bool GssContext::expired() {
208 return (!d_ctx || (d_ctx->d_expires > -1 && d_ctx->d_expires < time((time_t*)NULL)));
209 }
210
211 bool GssContext::valid() {
212 return (d_ctx && !expired() && d_ctx->d_state == GssSecContext::GssStateComplete);
213 }
214
215 bool GssContext::init(const std::string &input, std::string& output) {
216 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
217 OM_uint32 maj,min;
218 gss_buffer_desc recv_tok, send_tok, buffer;
219 OM_uint32 flags;
220 OM_uint32 expires;
221
222 boost::shared_ptr<GssCredential> cred;
223 if (d_label.empty()) {
224 d_error = GSS_CONTEXT_INVALID;
225 return false;
226 }
227
228 d_type = GSS_CONTEXT_INIT;
229
230 if (s_gss_init_creds.find(d_localPrincipal) != s_gss_init_creds.end()) {
231 cred = s_gss_init_creds[d_localPrincipal];
232 } else {
233 s_gss_init_creds[d_localPrincipal] = boost::make_shared<GssCredential>(d_localPrincipal, GSS_C_INITIATE);
234 cred = s_gss_init_creds[d_localPrincipal];
235 }
236
237 // see if we can find a context in non-completed state
238 if (d_ctx) {
239 if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
240 d_error = GSS_CONTEXT_INVALID;
241 return false;
242 }
243 } else {
244 // make context
245 s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred);
246 s_gss_sec_context[d_label]->d_type = d_type;
247 d_ctx = s_gss_sec_context[d_label];
248 d_ctx->d_state = GssSecContext::GssStateNegotiate;
249 }
250
251 recv_tok.length = input.size();
252 recv_tok.value = (void*)input.c_str();
253
254 if (d_peerPrincipal.empty() == false) {
255 buffer.value = (void*)d_peerPrincipal.c_str();
256 buffer.length = d_peerPrincipal.size();
257 maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_ctx->d_peer_name));
258 if (maj != GSS_S_COMPLETE) {
259 processError("gss_import_name", maj, min);
260 return false;
261 }
262 }
263
264 maj = gss_init_sec_context(&min, cred->d_cred, &(d_ctx->d_ctx), d_ctx->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, NULL, &send_tok, &flags, &expires);
265
266 if (send_tok.length>0) {
267 output.assign((const char*)send_tok.value, send_tok.length);
268 tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
269 }
270
271 if (maj == GSS_S_COMPLETE) {
272 if (expires > GSS_C_INDEFINITE) {
273 d_ctx->d_expires = time((time_t*)NULL) + expires;
274 } else {
275 d_ctx->d_expires = -1;
276 }
277 d_ctx->d_state = GssSecContext::GssStateComplete;
278 return true;
279 } else if (maj != GSS_S_CONTINUE_NEEDED) {
280 processError("gss_init_sec_context", maj,min);
281 }
282
283 return (maj == GSS_S_CONTINUE_NEEDED);
284 }
285
286 bool GssContext::accept(const std::string &input, std::string& output) {
287 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
288 OM_uint32 maj,min;
289 gss_buffer_desc recv_tok, send_tok;
290 OM_uint32 flags;
291 OM_uint32 expires;
292
293 boost::shared_ptr<GssCredential> cred;
294 if (d_label.empty()) {
295 d_error = GSS_CONTEXT_INVALID;
296 return false;
297 }
298
299 d_type = GSS_CONTEXT_ACCEPT;
300
301 if (s_gss_accept_creds.find(d_localPrincipal) != s_gss_accept_creds.end()) {
302 cred = s_gss_accept_creds[d_localPrincipal];
303 } else {
304 s_gss_accept_creds[d_localPrincipal] = boost::make_shared<GssCredential>(d_localPrincipal, GSS_C_ACCEPT);
305 cred = s_gss_accept_creds[d_localPrincipal];
306 }
307
308 // see if we can find a context in non-completed state
309 if (d_ctx) {
310 if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
311 d_error = GSS_CONTEXT_INVALID;
312 return false;
313 }
314 } else {
315 // make context
316 s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred);
317 s_gss_sec_context[d_label]->d_type = d_type;
318 d_ctx = s_gss_sec_context[d_label];
319 d_ctx->d_state = GssSecContext::GssStateNegotiate;
320 }
321
322 recv_tok.length = input.size();
323 recv_tok.value = (void*)input.c_str();
324
325 maj = gss_accept_sec_context(&min, &(d_ctx->d_ctx), cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &(d_ctx->d_peer_name), NULL, &send_tok, &flags, &expires, NULL);
326
327 if (send_tok.length>0) {
328 output.assign((const char*)send_tok.value, send_tok.length);
329 tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
330 }
331
332 if (maj == GSS_S_COMPLETE) {
333 if (expires > GSS_C_INDEFINITE) {
334 d_ctx->d_expires = time((time_t*)NULL) + expires;
335 } else {
336 d_ctx->d_expires = -1;
337 }
338 d_ctx->d_state = GssSecContext::GssStateComplete;
339 return true;
340 } else if (maj != GSS_S_CONTINUE_NEEDED) {
341 processError("gss_accept_sec_context", maj,min);
342 }
343 return (maj == GSS_S_CONTINUE_NEEDED);
344 };
345
346 bool GssContext::sign(const std::string& input, std::string& output) {
347 OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
348 OM_uint32 maj,min;
349
350 gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
351 gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
352
353 recv_tok.length = input.size();
354 recv_tok.value = (void*)input.c_str();
355
356 maj = gss_get_mic(&min, d_ctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok);
357
358 if (send_tok.length>0) {
359 output.assign((const char*)send_tok.value, send_tok.length);
360 tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
361 }
362
363 if (maj != GSS_S_COMPLETE) {
364 processError("gss_get_mic", maj,min);
365 }
366
367 return (maj == GSS_S_COMPLETE);
368 }
369
370 bool GssContext::verify(const std::string& input, const std::string& signature) {
371 OM_uint32 maj,min;
372
373 gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
374 gss_buffer_desc sign_tok = GSS_C_EMPTY_BUFFER;
375
376 recv_tok.length = input.size();
377 recv_tok.value = (void*)input.c_str();
378 sign_tok.length = signature.size();
379 sign_tok.value = (void*)signature.c_str();
380
381 maj = gss_verify_mic(&min, d_ctx->d_ctx, &recv_tok, &sign_tok, NULL);
382
383 if (maj != GSS_S_COMPLETE) {
384 processError("gss_get_mic", maj,min);
385 }
386
387 return (maj == GSS_S_COMPLETE);
388 }
389
390 bool GssContext::destroy() {
391 return false;
392 }
393
394 void GssContext::setLocalPrincipal(const std::string& name) {
395 d_localPrincipal = name;
396 }
397
398 bool GssContext::getLocalPrincipal(std::string& name) {
399 name = d_localPrincipal;
400 return name.size()>0;
401 }
402
403 void GssContext::setPeerPrincipal(const std::string& name) {
404 d_peerPrincipal = name;
405 }
406
407 bool GssContext::getPeerPrincipal(std::string& name) {
408 gss_buffer_desc value;
409 OM_uint32 maj,min;
410
411 if (d_ctx->d_peer_name != GSS_C_NO_NAME) {
412 maj = gss_display_name(&min, d_ctx->d_peer_name, &value, NULL);
413 if (maj == GSS_S_COMPLETE && value.length > 0) {
414 name.assign((const char*)value.value, value.length);
415 maj = gss_release_buffer(&min, &value);
416 return true;
417 } else {
418 return false;
419 }
420 } else {
421 return false;
422 }
423 }
424
425 void GssContext::processError(const std::string& method, OM_uint32 maj, OM_uint32 min) {
426 OM_uint32 tmp_min;
427 gss_buffer_desc msg;
428 OM_uint32 msg_ctx;
429
430 msg_ctx = 0;
431 while (1) {
432 ostringstream oss;
433 gss_display_status(&tmp_min, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
434 oss << method << ": " << (char*)msg.value;
435 d_gss_errors.push_back(oss.str());
436 if (!msg_ctx) break;
437 }
438 msg_ctx = 0;
439 while (1) {
440 ostringstream oss;
441 gss_display_status(&tmp_min, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
442 oss << method << ": " << (char*)msg.value;
443 d_gss_errors.push_back(oss.str());
444 if (!msg_ctx) break;
445 }
446 }
447
448 #endif
449
450 bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac) {
451 string tmp_mac;
452 GssContext gssctx(context);
453 if (!gssctx.valid()) {
454 g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl;
455 for(const string& error : gssctx.getErrorStrings()) {
456 g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
457 }
458 return false;
459 }
460
461 if (!gssctx.sign(message, tmp_mac)) {
462 g_log<<Logger::Error<<"Could not sign message using GSS context '"<<context<<"'"<<endl;
463 for(const string& error : gssctx.getErrorStrings()) {
464 g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
465 }
466 return false;
467 }
468 mac = tmp_mac;
469 return true;
470 }
471
472 bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac) {
473 GssContext gssctx(context);
474 if (!gssctx.valid()) {
475 g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl;
476 for(const string& error : gssctx.getErrorStrings()) {
477 g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
478 }
479 return false;
480 }
481
482 if (!gssctx.verify(message, mac)) {
483 g_log<<Logger::Error<<"Could not verify message using GSS context '"<<context<<"'"<<endl;
484 for(const string& error : gssctx.getErrorStrings()) {
485 g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
486 }
487 return false;
488 }
489 return true;
490 }