2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
27 #include "namespaces.hh"
29 #include "dnsparser.hh"
30 #include "dnspacket.hh"
31 #include "dnsrecords.hh"
34 #include "arguments.hh"
36 #include <boost/shared_ptr.hpp>
37 #include <boost/make_shared.hpp>
38 #include "gss_context.hh"
40 #ifndef ENABLE_GSS_TSIG
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
; }
62 class GssCredential
: boost::noncopyable
{
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
;
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
) {
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
);
89 bool expired() const {
90 if (d_expires
== -1) return false;
91 return time((time_t*)NULL
)>d_expires
;
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
);
98 if (d_maj
!= GSS_S_COMPLETE
) {
100 tmp_maj
= gss_release_name(&tmp_min
, &d_name
);
101 d_name
= GSS_C_NO_NAME
;
107 if (time_rec
> GSS_C_INDEFINITE
) {
108 d_expires
= time((time_t*)NULL
)+time_rec
;
117 return d_valid
&& !expired();
120 OM_uint32 d_maj
,d_min
;
126 gss_cred_id_t d_cred
;
127 gss_cred_usage_t d_usage
;
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
;
133 class GssSecContext
: boost::noncopyable
{
135 GssSecContext(boost::shared_ptr
<GssCredential
> cred
) {
136 if (cred
->valid() == false) throw PDNSException("Invalid credential " + cred
->d_nameS
);
138 d_state
= GssStateInitial
;
139 d_ctx
= GSS_C_NO_CONTEXT
;
142 d_peer_name
= GSS_C_NO_NAME
;
143 d_type
= GSS_CONTEXT_NONE
;
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
);
151 if (d_peer_name
!= GSS_C_NO_NAME
) {
152 tmp_maj
= gss_release_name(&tmp_min
, &(d_peer_name
));
156 GssContextType d_type
;
158 gss_name_t d_peer_name
;
160 boost::shared_ptr
<GssCredential
> d_cred
;
161 OM_uint32 d_maj
,d_min
;
172 std::map
<DNSName
, boost::shared_ptr
<GssSecContext
> > s_gss_sec_context
;
174 bool GssContext::supported() { return true; }
176 void GssContext::initialize() {
177 d_peerPrincipal
= "";
178 d_localPrincipal
= "";
179 d_error
= GSS_CONTEXT_NO_ERROR
;
180 d_type
= GSS_CONTEXT_NONE
;
183 GssContext::GssContext() {
185 generateLabel("pdns.tsig.");
188 GssContext::GssContext(const DNSName
& label
) {
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()));
199 void GssContext::setLabel(const DNSName
& 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
;
207 bool GssContext::expired() {
208 return (!d_ctx
|| (d_ctx
->d_expires
> -1 && d_ctx
->d_expires
< time((time_t*)NULL
)));
211 bool GssContext::valid() {
212 return (d_ctx
&& !expired() && d_ctx
->d_state
== GssSecContext::GssStateComplete
);
215 bool GssContext::init(const std::string
&input
, std::string
& output
) {
216 OM_uint32 tmp_maj
__attribute__((unused
)), tmp_min
__attribute__((unused
));
218 gss_buffer_desc recv_tok
, send_tok
, buffer
;
222 boost::shared_ptr
<GssCredential
> cred
;
223 if (d_label
.empty()) {
224 d_error
= GSS_CONTEXT_INVALID
;
228 d_type
= GSS_CONTEXT_INIT
;
230 if (s_gss_init_creds
.find(d_localPrincipal
) != s_gss_init_creds
.end()) {
231 cred
= s_gss_init_creds
[d_localPrincipal
];
233 s_gss_init_creds
[d_localPrincipal
] = boost::make_shared
<GssCredential
>(d_localPrincipal
, GSS_C_INITIATE
);
234 cred
= s_gss_init_creds
[d_localPrincipal
];
237 // see if we can find a context in non-completed state
239 if (d_ctx
->d_state
!= GssSecContext::GssStateNegotiate
) {
240 d_error
= GSS_CONTEXT_INVALID
;
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
;
251 recv_tok
.length
= input
.size();
252 recv_tok
.value
= (void*)input
.c_str();
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
);
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
);
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
);
271 if (maj
== GSS_S_COMPLETE
) {
272 if (expires
> GSS_C_INDEFINITE
) {
273 d_ctx
->d_expires
= time((time_t*)NULL
) + expires
;
275 d_ctx
->d_expires
= -1;
277 d_ctx
->d_state
= GssSecContext::GssStateComplete
;
279 } else if (maj
!= GSS_S_CONTINUE_NEEDED
) {
280 processError("gss_init_sec_context", maj
,min
);
283 return (maj
== GSS_S_CONTINUE_NEEDED
);
286 bool GssContext::accept(const std::string
&input
, std::string
& output
) {
287 OM_uint32 tmp_maj
__attribute__((unused
)), tmp_min
__attribute__((unused
));
289 gss_buffer_desc recv_tok
, send_tok
;
293 boost::shared_ptr
<GssCredential
> cred
;
294 if (d_label
.empty()) {
295 d_error
= GSS_CONTEXT_INVALID
;
299 d_type
= GSS_CONTEXT_ACCEPT
;
301 if (s_gss_accept_creds
.find(d_localPrincipal
) != s_gss_accept_creds
.end()) {
302 cred
= s_gss_accept_creds
[d_localPrincipal
];
304 s_gss_accept_creds
[d_localPrincipal
] = boost::make_shared
<GssCredential
>(d_localPrincipal
, GSS_C_ACCEPT
);
305 cred
= s_gss_accept_creds
[d_localPrincipal
];
308 // see if we can find a context in non-completed state
310 if (d_ctx
->d_state
!= GssSecContext::GssStateNegotiate
) {
311 d_error
= GSS_CONTEXT_INVALID
;
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
;
322 recv_tok
.length
= input
.size();
323 recv_tok
.value
= (void*)input
.c_str();
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
);
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
);
332 if (maj
== GSS_S_COMPLETE
) {
333 if (expires
> GSS_C_INDEFINITE
) {
334 d_ctx
->d_expires
= time((time_t*)NULL
) + expires
;
336 d_ctx
->d_expires
= -1;
338 d_ctx
->d_state
= GssSecContext::GssStateComplete
;
340 } else if (maj
!= GSS_S_CONTINUE_NEEDED
) {
341 processError("gss_accept_sec_context", maj
,min
);
343 return (maj
== GSS_S_CONTINUE_NEEDED
);
346 bool GssContext::sign(const std::string
& input
, std::string
& output
) {
347 OM_uint32 tmp_maj
__attribute__((unused
)), tmp_min
__attribute__((unused
));
350 gss_buffer_desc recv_tok
= GSS_C_EMPTY_BUFFER
;
351 gss_buffer_desc send_tok
= GSS_C_EMPTY_BUFFER
;
353 recv_tok
.length
= input
.size();
354 recv_tok
.value
= (void*)input
.c_str();
356 maj
= gss_get_mic(&min
, d_ctx
->d_ctx
, GSS_C_QOP_DEFAULT
, &recv_tok
, &send_tok
);
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
);
363 if (maj
!= GSS_S_COMPLETE
) {
364 processError("gss_get_mic", maj
,min
);
367 return (maj
== GSS_S_COMPLETE
);
370 bool GssContext::verify(const std::string
& input
, const std::string
& signature
) {
373 gss_buffer_desc recv_tok
= GSS_C_EMPTY_BUFFER
;
374 gss_buffer_desc sign_tok
= GSS_C_EMPTY_BUFFER
;
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();
381 maj
= gss_verify_mic(&min
, d_ctx
->d_ctx
, &recv_tok
, &sign_tok
, NULL
);
383 if (maj
!= GSS_S_COMPLETE
) {
384 processError("gss_get_mic", maj
,min
);
387 return (maj
== GSS_S_COMPLETE
);
390 bool GssContext::destroy() {
394 void GssContext::setLocalPrincipal(const std::string
& name
) {
395 d_localPrincipal
= name
;
398 bool GssContext::getLocalPrincipal(std::string
& name
) {
399 name
= d_localPrincipal
;
400 return name
.size()>0;
403 void GssContext::setPeerPrincipal(const std::string
& name
) {
404 d_peerPrincipal
= name
;
407 bool GssContext::getPeerPrincipal(std::string
& name
) {
408 gss_buffer_desc value
;
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
);
425 void GssContext::processError(const std::string
& method
, OM_uint32 maj
, OM_uint32 min
) {
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());
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());
450 bool gss_add_signature(const DNSName
& context
, const std::string
& message
, std::string
& 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
;;
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
;;
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
;;
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
;;