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