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