]>
Commit | Line | Data |
---|---|---|
12471842 PL |
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 | */ | |
b53bf23e AT |
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" | |
fa8fd4d2 | 35 | |
b53bf23e AT |
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; } | |
c613b06f CHB |
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) {} | |
b53bf23e AT |
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) {} | |
1635f12b | 50 | void GssContext::setLabel(const DNSName& label) {} |
b53bf23e AT |
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: | |
c613b06f CHB |
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) { | |
b53bf23e | 66 | gss_buffer_desc buffer; |
b53bf23e AT |
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: | |
1635f12b | 135 | GssSecContext(boost::shared_ptr<GssCredential> cred) { |
b53bf23e AT |
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 | ||
1635f12b | 172 | std::map<DNSName, boost::shared_ptr<GssSecContext> > s_gss_sec_context; |
b53bf23e AT |
173 | |
174 | bool GssContext::supported() { return true; } | |
175 | ||
176 | void GssContext::initialize() { | |
b53bf23e AT |
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(); | |
725e814a | 185 | generateLabel("pdns.tsig."); |
b53bf23e AT |
186 | } |
187 | ||
1635f12b | 188 | GssContext::GssContext(const DNSName& label) { |
b53bf23e | 189 | initialize(); |
1635f12b | 190 | setLabel(label); |
b53bf23e AT |
191 | } |
192 | ||
193 | void GssContext::generateLabel(const std::string& suffix) { | |
194 | std::ostringstream oss; | |
195 | oss << std::hex << time((time_t*)NULL) << "." << suffix; | |
725e814a | 196 | setLabel(DNSName(oss.str())); |
b53bf23e AT |
197 | } |
198 | ||
1635f12b | 199 | void GssContext::setLabel(const DNSName& label) { |
b53bf23e AT |
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 | |
1635f12b | 245 | s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred); |
b53bf23e AT |
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 | |
1635f12b | 316 | s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred); |
b53bf23e AT |
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 | ||
21a3792f | 450 | bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac) { |
b53bf23e | 451 | string tmp_mac; |
1635f12b | 452 | GssContext gssctx(context); |
b53bf23e | 453 | if (!gssctx.valid()) { |
e6a9dde5 | 454 | g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl; |
ef7cd021 | 455 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 456 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
457 | } |
458 | return false; | |
459 | } | |
460 | ||
461 | if (!gssctx.sign(message, tmp_mac)) { | |
e6a9dde5 | 462 | g_log<<Logger::Error<<"Could not sign message using GSS context '"<<context<<"'"<<endl; |
ef7cd021 | 463 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 464 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
465 | } |
466 | return false; | |
467 | } | |
468 | mac = tmp_mac; | |
469 | return true; | |
470 | } | |
471 | ||
21a3792f | 472 | bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac) { |
1635f12b | 473 | GssContext gssctx(context); |
b53bf23e | 474 | if (!gssctx.valid()) { |
e6a9dde5 | 475 | g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl; |
ef7cd021 | 476 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 477 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
478 | } |
479 | return false; | |
480 | } | |
481 | ||
482 | if (!gssctx.verify(message, mac)) { | |
e6a9dde5 | 483 | g_log<<Logger::Error<<"Could not verify message using GSS context '"<<context<<"'"<<endl; |
ef7cd021 | 484 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 485 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
486 | } |
487 | return false; | |
488 | } | |
489 | return true; | |
490 | } |