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.
23 #include "gss_context.hh"
26 #ifndef ENABLE_GSS_TSIG
28 std::tuple
<size_t, size_t, size_t> GssContext::getCounts() { return std::tuple
<size_t, size_t, size_t>(0, 0, 0); }
29 bool GssContext::supported() { return false; }
30 GssContext::GssContext() :
31 d_error(GSS_CONTEXT_UNSUPPORTED
), d_type(GSS_CONTEXT_NONE
) {}
32 GssContext::GssContext(const DNSName
& /* label */) :
33 d_error(GSS_CONTEXT_UNSUPPORTED
), d_type(GSS_CONTEXT_NONE
) {}
34 void GssContext::setLocalPrincipal(const std::string
& /* name */) {}
35 bool GssContext::getLocalPrincipal(std::string
& /* name */) { return false; }
36 void GssContext::setPeerPrincipal(const std::string
& /* name */) {}
37 bool GssContext::getPeerPrincipal(std::string
& /* name */) { return false; }
38 void GssContext::generateLabel(const std::string
& /* suffix */) {}
39 void GssContext::setLabel(const DNSName
& /* label */) {}
40 bool GssContext::init(const std::string
& /* input */, std::string
& /* output */) { return false; }
41 bool GssContext::accept(const std::string
& /* input */, std::string
& /* output */) { return false; }
42 bool GssContext::destroy() { return false; }
43 bool GssContext::expired() { return false; }
44 bool GssContext::valid() { return false; }
45 bool GssContext::sign(const std::string
& /* input */, std::string
& /* output */) { return false; }
46 bool GssContext::verify(const std::string
& /* input */, const std::string
& /* signature */) { return false; }
47 GssContextError
GssContext::getError() { return GSS_CONTEXT_UNSUPPORTED
; }
51 #include <unordered_map>
55 #define TSIG_GSS_EXPIRE_INTERVAL 60
57 class GssCredential
: boost::noncopyable
60 GssCredential(const std::string
& name
, const gss_cred_usage_t usage
) :
61 d_nameS(name
), d_usage(usage
)
63 gss_buffer_desc buffer
;
66 buffer
.length
= name
.size();
67 buffer
.value
= const_cast<void*>(static_cast<const void*>(name
.c_str()));
69 auto maj
= gss_import_name(&min
, &buffer
, (gss_OID
)GSS_KRB5_NT_PRINCIPAL_NAME
, &d_name
);
70 if (maj
!= GSS_S_COMPLETE
) {
71 d_name
= GSS_C_NO_NAME
;
82 OM_uint32 tmp_min
__attribute__((unused
));
83 if (d_cred
!= GSS_C_NO_CREDENTIAL
) {
84 (void)gss_release_cred(&tmp_min
, &d_cred
);
86 if (d_name
!= GSS_C_NO_NAME
) {
87 (void)gss_release_name(&tmp_min
, &d_name
);
93 if (d_expires
== -1) {
96 return time(nullptr) > d_expires
;
101 OM_uint32 time_rec
, tmp_maj
, tmp_min
__attribute__((unused
));
102 tmp_maj
= gss_acquire_cred(&tmp_min
, d_name
, GSS_C_INDEFINITE
, GSS_C_NO_OID_SET
, d_usage
, &d_cred
, nullptr, &time_rec
);
104 if (tmp_maj
!= GSS_S_COMPLETE
) {
106 (void)gss_release_name(&tmp_min
, &d_name
);
107 d_name
= GSS_C_NO_NAME
;
113 // We do not want forever, but a good time
114 if (time_rec
== GSS_C_INDEFINITE
) {
115 time_rec
= 24 * 60 * 60;
117 d_expires
= time(nullptr) + time_rec
;
124 return d_valid
&& !expired();
128 gss_cred_usage_t d_usage
;
129 gss_name_t d_name
{GSS_C_NO_NAME
};
130 gss_cred_id_t d_cred
{GSS_C_NO_CREDENTIAL
};
131 time_t d_expires
{time(nullptr) + 60}; // partly initialized will be cleaned up
135 static LockGuarded
<std::unordered_map
<std::string
, std::shared_ptr
<GssCredential
>>> s_gss_accept_creds
;
136 static LockGuarded
<std::unordered_map
<std::string
, std::shared_ptr
<GssCredential
>>> s_gss_init_creds
;
138 class GssSecContext
: boost::noncopyable
141 GssSecContext(std::shared_ptr
<GssCredential
> cred
)
143 if (!cred
->valid()) {
144 throw PDNSException("Invalid credential " + cred
->d_nameS
);
151 OM_uint32 tmp_maj
__attribute__((unused
)), tmp_min
__attribute__((unused
));
152 if (d_ctx
!= GSS_C_NO_CONTEXT
) {
153 tmp_maj
= gss_delete_sec_context(&tmp_min
, &d_ctx
, GSS_C_NO_BUFFER
);
155 if (d_peer_name
!= GSS_C_NO_NAME
) {
156 tmp_maj
= gss_release_name(&tmp_min
, &(d_peer_name
));
160 std::shared_ptr
<GssCredential
> d_cred
;
161 GssContextType d_type
{GSS_CONTEXT_NONE
};
162 gss_ctx_id_t d_ctx
{GSS_C_NO_CONTEXT
};
163 gss_name_t d_peer_name
{GSS_C_NO_NAME
};
164 time_t d_expires
{time(nullptr) + 60}; // partly initialized wil be cleaned up
172 } d_state
{GssStateInitial
};
175 static LockGuarded
<std::unordered_map
<DNSName
, std::shared_ptr
<GssSecContext
>>> s_gss_sec_context
;
177 template <typename T
>
178 static void doExpire(T
& m
, time_t now
)
180 auto lock
= m
.lock();
181 for (auto i
= lock
->begin(); i
!= lock
->end();) {
182 if (now
> i
->second
->d_expires
) {
193 static time_t s_last_expired
;
194 time_t now
= time(nullptr);
195 if (now
- s_last_expired
< TSIG_GSS_EXPIRE_INTERVAL
) {
198 s_last_expired
= now
;
199 doExpire(s_gss_init_creds
, now
);
200 doExpire(s_gss_accept_creds
, now
);
201 doExpire(s_gss_sec_context
, now
);
204 bool GssContext::supported() { return true; }
206 void GssContext::initialize()
208 d_peerPrincipal
= "";
209 d_localPrincipal
= "";
210 d_error
= GSS_CONTEXT_NO_ERROR
;
211 d_type
= GSS_CONTEXT_NONE
;
214 GssContext::GssContext()
217 generateLabel("pdns.tsig.");
220 GssContext::GssContext(const DNSName
& label
)
226 void GssContext::generateLabel(const std::string
& suffix
)
228 std::ostringstream oss
;
229 oss
<< std::hex
<< time(nullptr) << "." << suffix
;
230 setLabel(DNSName(oss
.str()));
233 void GssContext::setLabel(const DNSName
& label
)
236 auto lock
= s_gss_sec_context
.lock();
237 auto it
= lock
->find(d_label
);
238 if (it
!= lock
->end()) {
239 d_secctx
= it
->second
;
240 d_type
= d_secctx
->d_type
;
244 bool GssContext::expired()
246 return (!d_secctx
|| (d_secctx
->d_expires
> -1 && d_secctx
->d_expires
< time(nullptr)));
249 bool GssContext::valid()
251 return (d_secctx
&& !expired() && d_secctx
->d_state
== GssSecContext::GssStateComplete
);
254 bool GssContext::init(const std::string
& input
, std::string
& output
)
258 OM_uint32 tmp_maj
__attribute__((unused
)), tmp_min
__attribute__((unused
));
260 gss_buffer_desc recv_tok
, send_tok
, buffer
;
264 if (d_label
.empty()) {
265 d_error
= GSS_CONTEXT_INVALID
;
269 d_type
= GSS_CONTEXT_INIT
;
270 std::shared_ptr
<GssCredential
> cred
;
272 auto lock
= s_gss_init_creds
.lock();
273 auto it
= lock
->find(d_localPrincipal
);
274 if (it
== lock
->end()) {
275 it
= lock
->emplace(d_localPrincipal
, std::make_shared
<GssCredential
>(d_localPrincipal
, GSS_C_INITIATE
)).first
;
280 // see if we can find a context in non-completed state
282 if (d_secctx
->d_state
!= GssSecContext::GssStateNegotiate
) {
283 d_error
= GSS_CONTEXT_INVALID
;
289 auto lock
= s_gss_sec_context
.lock();
290 d_secctx
= std::make_shared
<GssSecContext
>(cred
);
291 d_secctx
->d_state
= GssSecContext::GssStateNegotiate
;
292 d_secctx
->d_type
= d_type
;
293 (*lock
)[d_label
] = d_secctx
;
296 recv_tok
.length
= input
.size();
297 recv_tok
.value
= const_cast<void*>(static_cast<const void*>(input
.c_str()));
299 if (!d_peerPrincipal
.empty()) {
300 buffer
.value
= const_cast<void*>(static_cast<const void*>(d_peerPrincipal
.c_str()));
301 buffer
.length
= d_peerPrincipal
.size();
302 maj
= gss_import_name(&min
, &buffer
, (gss_OID
)GSS_KRB5_NT_PRINCIPAL_NAME
, &(d_secctx
->d_peer_name
));
303 if (maj
!= GSS_S_COMPLETE
) {
304 processError("gss_import_name", maj
, min
);
309 maj
= gss_init_sec_context(&min
, cred
->d_cred
, &d_secctx
->d_ctx
, d_secctx
->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
, nullptr, &send_tok
, &flags
, &expires
);
311 if (send_tok
.length
> 0) {
312 output
.assign(static_cast<char*>(send_tok
.value
), send_tok
.length
);
313 tmp_maj
= gss_release_buffer(&tmp_min
, &send_tok
);
316 if (maj
== GSS_S_COMPLETE
) {
317 // We do not want forever
318 if (expires
== GSS_C_INDEFINITE
) {
321 d_secctx
->d_expires
= time(nullptr) + expires
;
322 d_secctx
->d_state
= GssSecContext::GssStateComplete
;
325 else if (maj
!= GSS_S_CONTINUE_NEEDED
) {
326 processError("gss_init_sec_context", maj
, min
);
329 return (maj
== GSS_S_CONTINUE_NEEDED
);
332 bool GssContext::accept(const std::string
& input
, std::string
& output
)
336 OM_uint32 tmp_maj
__attribute__((unused
)), tmp_min
__attribute__((unused
));
338 gss_buffer_desc recv_tok
, send_tok
;
342 if (d_label
.empty()) {
343 d_error
= GSS_CONTEXT_INVALID
;
347 d_type
= GSS_CONTEXT_ACCEPT
;
348 std::shared_ptr
<GssCredential
> cred
;
350 auto lock
= s_gss_accept_creds
.lock();
351 auto it
= lock
->find(d_localPrincipal
);
352 if (it
== lock
->end()) {
353 it
= lock
->emplace(d_localPrincipal
, std::make_shared
<GssCredential
>(d_localPrincipal
, GSS_C_ACCEPT
)).first
;
358 // see if we can find a context in non-completed state
360 if (d_secctx
->d_state
!= GssSecContext::GssStateNegotiate
) {
361 d_error
= GSS_CONTEXT_INVALID
;
367 auto lock
= s_gss_sec_context
.lock();
368 d_secctx
= std::make_shared
<GssSecContext
>(cred
);
369 d_secctx
->d_state
= GssSecContext::GssStateNegotiate
;
370 d_secctx
->d_type
= d_type
;
371 (*lock
)[d_label
] = d_secctx
;
374 recv_tok
.length
= input
.size();
375 recv_tok
.value
= const_cast<void*>(static_cast<const void*>(input
.c_str()));
377 maj
= gss_accept_sec_context(&min
, &d_secctx
->d_ctx
, cred
->d_cred
, &recv_tok
, GSS_C_NO_CHANNEL_BINDINGS
, &d_secctx
->d_peer_name
, nullptr, &send_tok
, &flags
, &expires
, nullptr);
379 if (send_tok
.length
> 0) {
380 output
.assign(static_cast<char*>(send_tok
.value
), send_tok
.length
);
381 tmp_maj
= gss_release_buffer(&tmp_min
, &send_tok
);
384 if (maj
== GSS_S_COMPLETE
) {
385 // We do not want forever
386 if (expires
== GSS_C_INDEFINITE
) {
389 d_secctx
->d_expires
= time(nullptr) + expires
;
390 d_secctx
->d_state
= GssSecContext::GssStateComplete
;
393 else if (maj
!= GSS_S_CONTINUE_NEEDED
) {
394 processError("gss_accept_sec_context", maj
, min
);
396 return (maj
== GSS_S_CONTINUE_NEEDED
);
399 bool GssContext::sign(const std::string
& input
, std::string
& output
)
401 OM_uint32 tmp_maj
__attribute__((unused
)), tmp_min
__attribute__((unused
));
404 gss_buffer_desc recv_tok
= GSS_C_EMPTY_BUFFER
;
405 gss_buffer_desc send_tok
= GSS_C_EMPTY_BUFFER
;
407 recv_tok
.length
= input
.size();
408 recv_tok
.value
= const_cast<void*>(static_cast<const void*>(input
.c_str()));
410 maj
= gss_get_mic(&min
, d_secctx
->d_ctx
, GSS_C_QOP_DEFAULT
, &recv_tok
, &send_tok
);
412 if (send_tok
.length
> 0) {
413 output
.assign(static_cast<char*>(send_tok
.value
), send_tok
.length
);
414 tmp_maj
= gss_release_buffer(&tmp_min
, &send_tok
);
417 if (maj
!= GSS_S_COMPLETE
) {
418 processError("gss_get_mic", maj
, min
);
421 return (maj
== GSS_S_COMPLETE
);
424 bool GssContext::verify(const std::string
& input
, const std::string
& signature
)
428 gss_buffer_desc recv_tok
= GSS_C_EMPTY_BUFFER
;
429 gss_buffer_desc sign_tok
= GSS_C_EMPTY_BUFFER
;
431 recv_tok
.length
= input
.size();
432 recv_tok
.value
= const_cast<void*>(static_cast<const void*>(input
.c_str()));
433 sign_tok
.length
= signature
.size();
434 sign_tok
.value
= const_cast<void*>(static_cast<const void*>(signature
.c_str()));
436 maj
= gss_verify_mic(&min
, d_secctx
->d_ctx
, &recv_tok
, &sign_tok
, nullptr);
438 if (maj
!= GSS_S_COMPLETE
) {
439 processError("gss_get_mic", maj
, min
);
442 return (maj
== GSS_S_COMPLETE
);
445 bool GssContext::destroy()
447 if (d_label
.empty()) {
450 auto lock
= s_gss_sec_context
.lock();
451 return lock
->erase(d_label
) == 1;
454 void GssContext::setLocalPrincipal(const std::string
& name
)
456 d_localPrincipal
= name
;
459 bool GssContext::getLocalPrincipal(std::string
& name
)
461 name
= d_localPrincipal
;
462 return name
.size() > 0;
465 void GssContext::setPeerPrincipal(const std::string
& name
)
467 d_peerPrincipal
= name
;
470 bool GssContext::getPeerPrincipal(std::string
& name
)
472 gss_buffer_desc value
;
475 if (d_secctx
->d_peer_name
!= GSS_C_NO_NAME
) {
476 maj
= gss_display_name(&min
, d_secctx
->d_peer_name
, &value
, nullptr);
477 if (maj
== GSS_S_COMPLETE
&& value
.length
> 0) {
478 name
.assign(static_cast<char*>(value
.value
), value
.length
);
479 maj
= gss_release_buffer(&min
, &value
);
491 std::tuple
<size_t, size_t, size_t> GssContext::getCounts()
493 return {s_gss_init_creds
.lock()->size(), s_gss_accept_creds
.lock()->size(), s_gss_sec_context
.lock()->size()};
496 void GssContext::processError(const std::string
& method
, OM_uint32 maj
, OM_uint32 min
)
505 if (gss_display_status(&tmp_min
, maj
, GSS_C_GSS_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &msg
) == GSS_S_COMPLETE
) {
506 oss
<< method
<< ": " << msg
.value
;
509 oss
<< method
<< ": ?";
511 if (msg
.length
!= 0) {
512 gss_release_buffer(&tmp_min
, &msg
);
514 d_gss_errors
.push_back(oss
.str());
521 if (gss_display_status(&tmp_min
, min
, GSS_C_MECH_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &msg
) == GSS_S_COMPLETE
) {
522 oss
<< method
<< ": " << msg
.value
;
525 oss
<< method
<< ": ?";
527 if (msg
.length
!= 0) {
528 gss_release_buffer(&tmp_min
, &msg
);
530 d_gss_errors
.push_back(oss
.str());
538 bool gss_add_signature(const DNSName
& context
, const std::string
& message
, std::string
& mac
)
541 GssContext
gssctx(context
);
542 if (!gssctx
.valid()) {
543 g_log
<< Logger::Error
<< "GSS context '" << context
<< "' is not valid" << endl
;
544 for (const string
& error
: gssctx
.getErrorStrings()) {
545 g_log
<< Logger::Error
<< "GSS error: " << error
<< endl
;
551 if (!gssctx
.sign(message
, tmp_mac
)) {
552 g_log
<< Logger::Error
<< "Could not sign message using GSS context '" << context
<< "'" << endl
;
553 for (const string
& error
: gssctx
.getErrorStrings()) {
554 g_log
<< Logger::Error
<< "GSS error: " << error
<< endl
;
563 bool gss_verify_signature(const DNSName
& context
, const std::string
& message
, const std::string
& mac
)
565 GssContext
gssctx(context
);
566 if (!gssctx
.valid()) {
567 g_log
<< Logger::Error
<< "GSS context '" << context
<< "' is not valid" << endl
;
568 for (const string
& error
: gssctx
.getErrorStrings()) {
569 g_log
<< Logger::Error
<< "GSS error: " << error
<< endl
;
575 if (!gssctx
.verify(message
, mac
)) {
576 g_log
<< Logger::Error
<< "Could not verify message using GSS context '" << context
<< "'" << endl
;
577 for (const string
& error
: gssctx
.getErrorStrings()) {
578 g_log
<< Logger::Error
<< "GSS error: " << error
<< endl
;