]>
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; } | |
a1ed1fa3 | 43 | GssContext::GssContext() { d_error = GSS_CONTEXT_UNSUPPORTED; d_type = GSS_CONTEXT_NONE; } |
1635f12b | 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: | |
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: | |
1635f12b | 140 | GssSecContext(boost::shared_ptr<GssCredential> cred) { |
b53bf23e AT |
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 | ||
1635f12b | 177 | std::map<DNSName, boost::shared_ptr<GssSecContext> > s_gss_sec_context; |
b53bf23e AT |
178 | |
179 | bool GssContext::supported() { return true; } | |
180 | ||
181 | void GssContext::initialize() { | |
b53bf23e AT |
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(); | |
725e814a | 190 | generateLabel("pdns.tsig."); |
b53bf23e AT |
191 | } |
192 | ||
1635f12b | 193 | GssContext::GssContext(const DNSName& label) { |
b53bf23e | 194 | initialize(); |
1635f12b | 195 | setLabel(label); |
b53bf23e AT |
196 | } |
197 | ||
198 | void GssContext::generateLabel(const std::string& suffix) { | |
199 | std::ostringstream oss; | |
200 | oss << std::hex << time((time_t*)NULL) << "." << suffix; | |
725e814a | 201 | setLabel(DNSName(oss.str())); |
b53bf23e AT |
202 | } |
203 | ||
1635f12b | 204 | void GssContext::setLabel(const DNSName& label) { |
b53bf23e AT |
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 | |
1635f12b | 250 | s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred); |
b53bf23e AT |
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 | |
1635f12b | 321 | s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred); |
b53bf23e AT |
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 | ||
21a3792f | 455 | bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac) { |
b53bf23e | 456 | string tmp_mac; |
1635f12b | 457 | GssContext gssctx(context); |
b53bf23e | 458 | if (!gssctx.valid()) { |
e6a9dde5 | 459 | g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl; |
ef7cd021 | 460 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 461 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
462 | } |
463 | return false; | |
464 | } | |
465 | ||
466 | if (!gssctx.sign(message, tmp_mac)) { | |
e6a9dde5 | 467 | g_log<<Logger::Error<<"Could not sign message using GSS context '"<<context<<"'"<<endl; |
ef7cd021 | 468 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 469 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
470 | } |
471 | return false; | |
472 | } | |
473 | mac = tmp_mac; | |
474 | return true; | |
475 | } | |
476 | ||
21a3792f | 477 | bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac) { |
1635f12b | 478 | GssContext gssctx(context); |
b53bf23e | 479 | if (!gssctx.valid()) { |
e6a9dde5 | 480 | g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl; |
ef7cd021 | 481 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 482 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
483 | } |
484 | return false; | |
485 | } | |
486 | ||
487 | if (!gssctx.verify(message, mac)) { | |
e6a9dde5 | 488 | g_log<<Logger::Error<<"Could not verify message using GSS context '"<<context<<"'"<<endl; |
ef7cd021 | 489 | for(const string& error : gssctx.getErrorStrings()) { |
e6a9dde5 | 490 | g_log<<Logger::Error<<"GSS error: "<<error<<endl;; |
b53bf23e AT |
491 | } |
492 | return false; | |
493 | } | |
494 | return true; | |
495 | } |