Anope IRC Services  Version 2.0
m_ssl_gnutls.cpp
Go to the documentation of this file.
1 /*
2  * (C) 2014 Attila Molnar <attilamolnar@hush.com>
3  * (C) 2014 Anope Team
4  * Contact us at team@anope.org
5  *
6  * Please read COPYING and README for further details.
7  */
8 
9 /* RequiredLibraries: gnutls */
10 /* RequiredWindowsLibraries: libgnutls-28 */
11 
12 #include "module.h"
13 #include "modules/ssl.h"
14 
15 #include <errno.h>
16 #include <gnutls/gnutls.h>
17 #include <gnutls/x509.h>
18 
20 static GnuTLSModule *me;
21 
22 namespace GnuTLS { class X509CertCredentials; }
23 
24 class MySSLService : public SSLService
25 {
26  public:
27  MySSLService(Module *o, const Anope::string &n);
28 
32  void Init(Socket *s) anope_override;
33 };
34 
35 class SSLSocketIO : public SocketIO
36 {
37  public:
38  gnutls_session_t sess;
40 
43  SSLSocketIO();
44 
51  int Recv(Socket *s, char *buf, size_t sz) anope_override;
52 
58  int Send(Socket *s, const char *buf, size_t sz) anope_override;
59 
65 
71 
77  void Connect(ConnectionSocket *s, const Anope::string &target, int port) anope_override;
78 
84 
87  void Destroy() anope_override;
88 };
89 
90 namespace GnuTLS
91 {
92  class Init
93  {
94  public:
95  Init() { gnutls_global_init(); }
96  ~Init() { gnutls_global_deinit(); }
97  };
98 
101  class Datum
102  {
103  gnutls_datum_t datum;
104 
105  public:
106  Datum(const Anope::string &dat)
107  {
108  datum.data = reinterpret_cast<unsigned char *>(const_cast<char *>(dat.data()));
109  datum.size = static_cast<unsigned int>(dat.length());
110  }
111 
112  const gnutls_datum_t *get() const { return &datum; }
113  };
114 
115  class DHParams
116  {
117  gnutls_dh_params_t dh_params;
118 
119  public:
120  DHParams() : dh_params(NULL) { }
121 
122  void Import(const Anope::string &dhstr)
123  {
124  if (dh_params != NULL)
125  {
126  gnutls_dh_params_deinit(dh_params);
127  dh_params = NULL;
128  }
129 
130  int ret = gnutls_dh_params_init(&dh_params);
131  if (ret < 0)
132  throw ConfigException("Unable to initialize DH parameters");
133 
134  ret = gnutls_dh_params_import_pkcs3(dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM);
135  if (ret < 0)
136  {
137  gnutls_dh_params_deinit(dh_params);
138  dh_params = NULL;
139  throw ConfigException("Unable to import DH parameters");
140  }
141  }
142 
144  {
145  if (dh_params)
146  gnutls_dh_params_deinit(dh_params);
147  }
148 
149  gnutls_dh_params_t get() const { return dh_params; }
150  };
151 
152  class X509Key
153  {
156  class RAIIKey
157  {
158  public:
159  gnutls_x509_privkey_t key;
160 
162  {
163  int ret = gnutls_x509_privkey_init(&key);
164  if (ret < 0)
165  throw ConfigException("gnutls_x509_privkey_init() failed");
166  }
167 
169  {
170  gnutls_x509_privkey_deinit(key);
171  }
172  } key;
173 
174  public:
176  X509Key(const Anope::string &keystr)
177  {
178  int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM);
179  if (ret < 0)
180  throw ConfigException("Error loading private key: " + Anope::string(gnutls_strerror(ret)));
181  }
182 
183  gnutls_x509_privkey_t& get() { return key.key; }
184  };
185 
187  {
188  std::vector<gnutls_x509_crt_t> certs;
189 
190  public:
192  X509CertList(const Anope::string &certstr)
193  {
194  unsigned int certcount = 3;
195  certs.resize(certcount);
196  Datum datum(certstr);
197 
198  int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
199  if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
200  {
201  // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs,
202  // try again with a bigger buffer
203  certs.resize(certcount);
204  ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
205  }
206 
207  if (ret < 0)
208  throw ConfigException("Unable to load certificates" + Anope::string(gnutls_strerror(ret)));
209 
210  // Resize the vector to the actual number of certs because we rely on its size being correct
211  // when deallocating the certs
212  certs.resize(certcount);
213  }
214 
216  {
217  for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
218  gnutls_x509_crt_deinit(*i);
219  }
220 
221  gnutls_x509_crt_t* raw() { return &certs[0]; }
222  unsigned int size() const { return certs.size(); }
223  };
224 
226  {
227  unsigned int refcount;
228  gnutls_certificate_credentials_t cred;
230 
231  static Anope::string LoadFile(const Anope::string &filename)
232  {
233  std::ifstream ifs(filename.c_str());
234  const Anope::string ret((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
235  return ret;
236  }
237 
238  #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
239  static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st);
240  #else
241  static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st);
242  #endif
243 
244  public:
247 
248  X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile)
249  : refcount(0), certs(LoadFile(certfile)), key(LoadFile(keyfile))
250  {
251  if (gnutls_certificate_allocate_credentials(&cred) < 0)
252  throw ConfigException("Cannot allocate certificate credentials");
253 
254  int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get());
255  if (ret < 0)
256  {
257  gnutls_certificate_free_credentials(cred);
258  throw ConfigException("Unable to set cert/key pair");
259  }
260 
261  #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
262  gnutls_certificate_client_set_retrieve_function(cred, cert_callback);
263  #else
264  gnutls_certificate_set_retrieve_function(cred, cert_callback);
265  #endif
266  }
267 
269  {
270  gnutls_certificate_free_credentials(cred);
271  }
272 
273  void SetupSession(gnutls_session_t sess)
274  {
275  gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
276  gnutls_set_default_priority(sess);
277  }
278 
279  void SetDH(const Anope::string &dhfile)
280  {
281  const Anope::string dhdata = LoadFile(dhfile);
282  dh.Import(dhdata);
283  gnutls_certificate_set_dh_params(cred, dh.get());
284  }
285 
286  bool HasDH() const
287  {
288  return (dh.get() != NULL);
289  }
290 
291  void incrref() { refcount++; }
292  void decrref() { if (!--refcount) delete this; }
293  };
294 }
295 
296 class GnuTLSModule : public Module
297 {
299 
300  public:
303 
304  GnuTLSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), cred(NULL), service(this, "ssl")
305  {
306  me = this;
307  this->SetPermanent(true);
308  }
309 
311  {
312  for (std::map<int, Socket *>::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;)
313  {
314  Socket *s = it->second;
315  ++it;
316 
317  if (dynamic_cast<SSLSocketIO *>(s->io))
318  delete s;
319  }
320 
321  if (cred)
322  cred->decrref();
323  }
324 
325  static void CheckFile(const Anope::string &filename)
326  {
327  if (!Anope::IsFile(filename.c_str()))
328  {
329  Log() << "File does not exist: " << filename;
330  throw ConfigException("Error loading certificate/private key");
331  }
332  }
333 
334  void OnReload(Configuration::Conf *conf) anope_override
335  {
336  Configuration::Block *config = conf->GetModule(this);
337 
338  const Anope::string certfile = config->Get<const Anope::string>("cert", "data/anope.crt");
339  const Anope::string keyfile = config->Get<const Anope::string>("key", "data/anope.key");
340  const Anope::string dhfile = config->Get<const Anope::string>("dh", "data/dhparams.pem");
341 
342  CheckFile(certfile);
343  CheckFile(keyfile);
344 
345  GnuTLS::X509CertCredentials *newcred = new GnuTLS::X509CertCredentials(certfile, keyfile);
346 
347  // DH params is not mandatory
348  if (Anope::IsFile(dhfile.c_str()))
349  {
350  try
351  {
352  newcred->SetDH(dhfile);
353  }
354  catch (...)
355  {
356  delete newcred;
357  throw;
358  }
359  Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile;
360  }
361 
362  if (cred)
363  cred->decrref();
364  cred = newcred;
365  cred->incrref();
366 
367  Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded certificate " << certfile << " and private key " << keyfile;
368  }
369 
370  void OnPreServerConnect() anope_override
371  {
372  Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink);
373 
374  if (config->Get<bool>("ssl"))
375  {
376  this->service.Init(UplinkSock);
377  }
378  }
379 };
380 
382 {
383 }
384 
386 {
387  if (s->io != &NormalSocketIO)
388  throw CoreException("Socket initializing SSL twice");
389 
390  s->io = new SSLSocketIO();
391 }
392 
393 int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz)
394 {
395  int ret = gnutls_record_recv(this->sess, buf, sz);
396 
397  if (ret > 0)
398  TotalRead += ret;
399  else if (ret < 0)
400  {
401  switch (ret)
402  {
403  case GNUTLS_E_AGAIN:
404  case GNUTLS_E_INTERRUPTED:
406  break;
407  default:
408  if (s == UplinkSock)
409  {
410  // Log and fake an errno because this is a fatal error on the uplink socket
411  Log() << "SSL error: " << gnutls_strerror(ret);
412  }
413  SocketEngine::SetLastError(ECONNRESET);
414  }
415  }
416 
417  return ret;
418 }
419 
420 int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz)
421 {
422  int ret = gnutls_record_send(this->sess, buf, sz);
423 
424  if (ret > 0)
425  TotalWritten += ret;
426  else
427  {
428  switch (ret)
429  {
430  case 0:
431  case GNUTLS_E_AGAIN:
432  case GNUTLS_E_INTERRUPTED:
434  break;
435  default:
436  if (s == UplinkSock)
437  {
438  // Log and fake an errno because this is a fatal error on the uplink socket
439  Log() << "SSL error: " << gnutls_strerror(ret);
440  }
441  SocketEngine::SetLastError(ECONNRESET);
442  }
443  }
444 
445  return ret;
446 }
447 
449 {
450  if (s->io == &NormalSocketIO)
451  throw SocketException("Attempting to accept on uninitialized socket with SSL");
452 
453  sockaddrs conaddr;
454 
455  socklen_t size = sizeof(conaddr);
456  int newsock = accept(s->GetFD(), &conaddr.sa, &size);
457 
458 #ifndef INVALID_SOCKET
459  const int INVALID_SOCKET = -1;
460 #endif
461 
462  if (newsock < 0 || newsock == INVALID_SOCKET)
463  throw SocketException("Unable to accept connection: " + Anope::LastError());
464 
465  ClientSocket *newsocket = s->OnAccept(newsock, conaddr);
466  me->service.Init(newsocket);
468 
469  if (gnutls_init(&io->sess, GNUTLS_SERVER) != GNUTLS_E_SUCCESS)
470  throw SocketException("Unable to initialize SSL socket");
471 
472  me->cred->SetupSession(io->sess);
473  gnutls_transport_set_ptr(io->sess, reinterpret_cast<gnutls_transport_ptr_t>(newsock));
474 
475  newsocket->flags[SF_ACCEPTING] = true;
476  this->FinishAccept(newsocket);
477 
478  return newsocket;
479 }
480 
482 {
483  if (cs->io == &NormalSocketIO)
484  throw SocketException("Attempting to finish connect uninitialized socket with SSL");
485  else if (cs->flags[SF_ACCEPTED])
486  return SF_ACCEPTED;
487  else if (!cs->flags[SF_ACCEPTING])
488  throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?");
489 
491 
492  int ret = gnutls_handshake(io->sess);
493  if (ret < 0)
494  {
495  if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
496  {
497  // gnutls_handshake() wants to read or write again;
498  // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write.
499  if (gnutls_record_get_direction(io->sess) == 0)
500  {
501  SocketEngine::Change(cs, false, SF_WRITABLE);
503  }
504  else
505  {
507  SocketEngine::Change(cs, false, SF_READABLE);
508  }
509  return SF_ACCEPTING;
510  }
511  else
512  {
513  cs->OnError(Anope::string(gnutls_strerror(ret)));
514  cs->flags[SF_DEAD] = true;
515  cs->flags[SF_ACCEPTING] = false;
516  return SF_DEAD;
517  }
518  }
519  else
520  {
521  cs->flags[SF_ACCEPTED] = true;
522  cs->flags[SF_ACCEPTING] = false;
523  SocketEngine::Change(cs, false, SF_WRITABLE);
525  cs->OnAccept();
526  return SF_ACCEPTED;
527  }
528 }
529 
530 void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port)
531 {
532  if (s->io == &NormalSocketIO)
533  throw SocketException("Attempting to connect uninitialized socket with SSL");
534 
535  s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false;
536 
537  s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port);
538  int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size());
539  if (c == -1)
540  {
541  if (Anope::LastErrorCode() != EINPROGRESS)
542  {
544  s->flags[SF_DEAD] = true;
545  return;
546  }
547  else
548  {
550  s->flags[SF_CONNECTING] = true;
551  return;
552  }
553  }
554  else
555  {
556  s->flags[SF_CONNECTING] = true;
557  this->FinishConnect(s);
558  }
559 }
560 
562 {
563  if (s->io == &NormalSocketIO)
564  throw SocketException("Attempting to finish connect uninitialized socket with SSL");
565  else if (s->flags[SF_CONNECTED])
566  return SF_CONNECTED;
567  else if (!s->flags[SF_CONNECTING])
568  throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?");
569 
571 
572  if (io->sess == NULL)
573  {
574  if (gnutls_init(&io->sess, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS)
575  throw SocketException("Unable to initialize SSL socket");
576  me->cred->SetupSession(io->sess);
577  gnutls_transport_set_ptr(io->sess, reinterpret_cast<gnutls_transport_ptr_t>(s->GetFD()));
578  }
579 
580  int ret = gnutls_handshake(io->sess);
581  if (ret < 0)
582  {
583  if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
584  {
585  // gnutls_handshake() wants to read or write again;
586  // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write.
587  if (gnutls_record_get_direction(io->sess) == 0)
588  {
591  }
592  else
593  {
596  }
597 
598  return SF_CONNECTING;
599  }
600  else
601  {
602  s->OnError(Anope::string(gnutls_strerror(ret)));
603  s->flags[SF_CONNECTING] = false;
604  s->flags[SF_DEAD] = true;
605  return SF_DEAD;
606  }
607  }
608  else
609  {
610  s->flags[SF_CONNECTING] = false;
611  s->flags[SF_CONNECTED] = true;
614  s->OnConnect();
615  return SF_CONNECTED;
616  }
617 }
618 
620 {
621  if (this->sess)
622  {
623  gnutls_bye(this->sess, GNUTLS_SHUT_WR);
624  gnutls_deinit(this->sess);
625  }
626 
627  mycreds->decrref();
628 
629  delete this;
630 }
631 
632 SSLSocketIO::SSLSocketIO() : sess(NULL), mycreds(me->cred)
633 {
634  mycreds->incrref();
635 }
636 
637 #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12))
638 int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st)
639 {
640  st->type = GNUTLS_CRT_X509;
641 #else
642 int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st)
643 {
644  st->cert_type = GNUTLS_CRT_X509;
645  st->key_type = GNUTLS_PRIVKEY_X509;
646 #endif
647  st->ncerts = me->cred->certs.size();
648  st->cert.x509 = me->cred->certs.raw();
649  st->key.x509 = me->cred->key.get();
650  st->deinit_all = 0;
651 
652  return 0;
653 }
654 
int Send(Socket *s, const char *buf, size_t sz) anope_override
void SetupSession(gnutls_session_t sess)
void pton(int type, const Anope::string &address, int pport=0)
Definition: sockets.cpp:121
std::bitset< SF_SIZE > flags
Definition: sockets.h:203
void Import(const Anope::string &dhstr)
size_t size() const
Definition: sockets.cpp:43
SocketIO * io
Definition: sockets.h:209
virtual ClientSocket * OnAccept(int fd, const sockaddrs &addr)=0
SocketFlag
Definition: sockets.h:124
gnutls_x509_crt_t * raw()
sockaddr sa
Definition: sockets.h:30
CoreExport SocketIO NormalSocketIO
Definition: sockets.cpp:29
gnutls_certificate_credentials_t cred
virtual void OnConnect()
static void CheckFile(const Anope::string &filename)
GnuTLS::X509CertCredentials * cred
CoreExport const string LastError()
Definition: misc.cpp:606
CoreExport bool IsFile(const Anope::string &file)
Definition: misc.cpp:266
bool IsIPv6() const
Definition: sockets.cpp:479
void OnReload(Configuration::Conf *conf) anope_override
const char * data() const
Definition: anope.h:118
static std::map< int, Socket * > Sockets
Definition: socketengine.h:24
X509CertList(const Anope::string &certstr)
void OnPreServerConnect() anope_override
gnutls_datum_t datum
CoreExport int LastErrorCode()
Definition: misc.cpp:597
static void Change(Socket *s, bool set, SocketFlag flag)
virtual void OnError(const Anope::string &error)
gnutls_x509_privkey_t key
gnutls_session_t sess
GnuTLS::Init libinit
void Connect(ConnectionSocket *s, const Anope::string &target, int port) anope_override
int GetFD() const
Definition: sockets.cpp:474
X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile)
GnuTLSModule(const Anope::string &modname, const Anope::string &creator)
const gnutls_datum_t * get() const
void SetDH(const Anope::string &dhfile)
size_type length() const
Definition: anope.h:131
static GnuTLSModule * me
sockaddrs conaddr
Definition: sockets.h:391
Definition: Config.cs:26
std::vector< gnutls_x509_crt_t > certs
GnuTLS::X509CertCredentials * mycreds
gnutls_dh_params_t get() const
gnutls_x509_privkey_t & get()
unsigned int size() const
int Recv(Socket *s, char *buf, size_t sz) anope_override
MySSLService(Module *o, const Anope::string &n)
ClientSocket * Accept(ListenSocket *s) anope_override
static void SetLastError(int)
Definition: sockets.cpp:557
void Init(Socket *s) anope_override
#define anope_override
Definition: services.h:56
#define MODULE_INIT(x)
Definition: modules.h:45
virtual void OnError(const Anope::string &error)
static Anope::string LoadFile(const Anope::string &filename)
CoreExport int CurrentUplink
Definition: main.cpp:43
CoreExport uint32_t TotalWritten
Definition: sockets.cpp:27
SocketFlag FinishAccept(ClientSocket *cs) anope_override
CoreExport uint32_t TotalRead
Definition: sockets.cpp:26
T anope_dynamic_static_cast(O ptr)
Definition: anope.h:774
void Destroy() anope_override
SocketFlag FinishConnect(ConnectionSocket *s) anope_override
virtual void OnAccept()
MySSLService service
const char * c_str() const
Definition: anope.h:117
Definition: logger.h:53
T Get(const Anope::string &tag)
Definition: config.h:44
gnutls_dh_params_t dh_params
X509Key(const Anope::string &keystr)
static int cert_callback(gnutls_session_t sess, const gnutls_datum_t *req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t *sign_algos, int sign_algos_length, gnutls_retr_st *st)
#define accept
Definition: socket.h:28
Definition: ssl.h:2
Definition: modules.h:163
Datum(const Anope::string &dat)