some updates
[iv.d.git] / sslsocket.d
blob2da0b673f1c9464a0ec7dca5a5bd3faf3d413e5a
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // loosely based on opticron and Adam D. Ruppe work
18 module iv.sslsocket /*is aliced*/;
20 import iv.alice;
21 public import std.socket;
22 import iv.gnutls;
25 // ///////////////////////////////////////////////////////////////////////// //
26 shared static this () { gnutls_global_init(); }
27 shared static ~this () { gnutls_global_deinit(); }
30 // ///////////////////////////////////////////////////////////////////////// //
31 /// deprecated!
32 class SSLClientSocket : Socket {
33 gnutls_certificate_credentials_t xcred;
34 gnutls_session_t session;
35 private bool sslInitialized = false;
36 bool manualHandshake = false; // for non-blocking sockets this should be `true`
37 bool isblocking = false;
38 string certBaseName;
40 // take care of pre-connection TLS stuff
41 //FIXME: possible memory leak on exception? (sholdn't be, as `close()` will free the things)
42 private void sslInit (string acertbasename, const(char)[] hostname=null) {
43 if (sslInitialized) return;
45 // x509 stuff
46 gnutls_certificate_allocate_credentials(&xcred);
48 // sets the trusted certificate authority file (no need for us, as we aren't checking any certificate)
49 //gnutls_certificate_set_x509_trust_file(xcred, CAFILE, GNUTLS_X509_FMT_PEM);
50 if (acertbasename.length) {
51 certBaseName = acertbasename;
52 import std.internal.cstring : tempCString;
53 string cfname = certBaseName~".cer";
54 string kfname = certBaseName~".key";
55 int ret = gnutls_certificate_set_x509_key_file(xcred, cfname.tempCString, kfname.tempCString, GNUTLS_X509_FMT_PEM);
56 if (ret < 0) {
57 import std.string : fromStringz;
58 import std.conv : to;
59 string errstr = gnutls_strerror(ret).fromStringz.idup;
60 gnutls_certificate_free_credentials(xcred);
61 throw new Exception("TLS Error ("~errstr~"): cannot load certificate, err="~ret.to!string);
65 // initialize TLS session
66 gnutls_init(&session, GNUTLS_CLIENT|(certBaseName.length ? GNUTLS_FORCE_CLIENT_CERT : 0));
68 // use default priorities
70 const(char)* err;
71 auto ret = gnutls_priority_set_direct(session, "PERFORMANCE", &err);
72 if (ret < 0) {
73 import std.string : fromStringz;
74 import std.conv : to;
75 if (ret == GNUTLS_E_INVALID_REQUEST) throw new Exception("Syntax error at: "~err.fromStringz.idup);
76 string errstr = gnutls_strerror(ret).fromStringz.idup;
77 gnutls_deinit(session);
78 gnutls_certificate_free_credentials(xcred);
79 throw new Exception("TLS Error ("~errstr~"): returned with "~ret.to!string);
82 auto ret = gnutls_set_default_priority(session);
83 if (ret < 0) {
84 import std.string : fromStringz;
85 import std.conv : to;
86 //if (ret == GNUTLS_E_INVALID_REQUEST) throw new Exception("Syntax error at: "~err.fromStringz.idup);
87 string errstr = gnutls_strerror(ret).fromStringz.idup;
88 gnutls_deinit(session);
89 gnutls_certificate_free_credentials(xcred);
90 throw new Exception("TLS Error ("~errstr~"): returned with "~ret.to!string);
92 gnutls_session_enable_compatibility_mode(session);
94 if (hostname.length) {
95 version(none) {
96 import core.stdc.stdio : stderr, fprintf;
97 fprintf(stderr, "TLS: setting host name to '%.*s'\n", cast(uint)hostname.length, hostname.ptr);
99 int xres = gnutls_server_name_set(session, GNUTLS_NAME_DNS, hostname.ptr, cast(uint)hostname.length);
100 if (xres < 0) {
101 import std.string : fromStringz;
102 import std.conv : to;
103 string errstr = gnutls_strerror(xres).fromStringz.idup;
104 gnutls_deinit(session);
105 gnutls_certificate_free_credentials(xcred);
106 throw new Exception("TLS Error ("~errstr~"): returned with "~xres.to!string);
110 // put the x509 credentials to the current session
111 gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
112 sslInitialized = true;
115 // this is required for new TLS (fuck)
116 // call this before connecting
117 public void sslhostname (const(char)[] hname) @trusted {
118 if (hname.length == 0) return;
119 //import std.internal.cstring : tempCString;
120 int res = gnutls_server_name_set(session, GNUTLS_NAME_DNS, hname.ptr/*tempCString*/, hname.length);
121 if (res < 0) {
122 import std.string : fromStringz;
123 import std.conv : to;
124 string errstr = gnutls_strerror(res).fromStringz.idup;
125 //gnutls_deinit(session);
126 //gnutls_certificate_free_credentials(xcred);
127 throw new Exception("TLS Error ("~errstr~"): returned with "~res.to!string);
131 public void sslHandshake () {
132 if (!sslInitialized) throw new Exception("trying to handshake on uninitialised SSL session");
133 // lob the socket handle off to gnutls
134 gnutls_transport_set_ptr(session, cast(gnutls_transport_ptr_t)handle);
135 gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
136 // perform the TLS handshake
137 for (;;) {
138 auto ret = gnutls_handshake(session);
139 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
140 if (ret < 0) {
141 import std.string : fromStringz;
142 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz.idup);
144 break;
148 public string getSessionInfo () {
149 if (!sslInitialized) return null;
150 char* desc = gnutls_session_get_desc(session);
151 if (desc is null) return null;
152 import core.stdc.string : strlen;
153 usize len = strlen(desc);
154 string res;
155 if (len != 0) res = desc[0..len].idup;
156 gnutls_free(desc);
157 return res;
160 override @property void blocking (bool byes) @trusted {
161 super.blocking(byes);
162 isblocking = byes;
165 override void connect (Address to) @trusted {
166 super.connect(to);
167 if (!manualHandshake) sslHandshake();
170 // close the encrypted connection
171 override void close () @trusted {
172 if (sslInitialized) {
173 sslInitialized = false;
174 //{ import core.stdc.stdio : printf; printf("deiniting\n"); }
175 //!!!gnutls_bye(session, GNUTLS_SHUT_RDWR);
176 gnutls_deinit(session);
177 gnutls_certificate_free_credentials(xcred);
179 super.close();
182 override ptrdiff_t send (const(void)[] buf, SocketFlags flags) @trusted {
183 if (buf.length == 0) return 0;
184 for (;;) {
185 auto res = gnutls_record_send(session, buf.ptr, buf.length);
186 if (res >= 0 || !isblocking) return res;
187 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
188 //if (gnutls_error_is_fatal(res)) return res;
189 return res;
193 override ptrdiff_t send (const(void)[] buf) {
194 import core.sys.posix.sys.socket;
195 static if (is(typeof(MSG_NOSIGNAL))) {
196 return send(buf, cast(SocketFlags)MSG_NOSIGNAL);
197 } else {
198 return send(buf, SocketFlags.NOSIGNAL);
202 override ptrdiff_t receive (void[] buf, SocketFlags flags) @trusted {
203 if (buf.length == 0) return 0;
204 for (;;) {
205 auto res = gnutls_record_recv(session, buf.ptr, buf.length);
206 if (res >= 0 || !isblocking) return res;
207 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
208 //if (gnutls_error_is_fatal(res)) return res;
209 return res;
213 override ptrdiff_t receive (void[] buf) { return receive(buf, SocketFlags.NONE); }
215 this (AddressFamily af, const(char)[] hostname, SocketType type=SocketType.STREAM, string certbasename=null) {
216 sslInit(certbasename, hostname);
217 super(af, type);
220 this (AddressFamily af, SocketType type=SocketType.STREAM, string certbasename=null) {
221 sslInit(certbasename);
222 super(af, type);
225 this (socket_t sock, AddressFamily af, string certbasename=null) {
226 sslInit(certbasename);
227 super(sock, af);
232 // ///////////////////////////////////////////////////////////////////////// //
233 // this can be used as both client and server socket
234 // don't forget to set certificate file (and key file, if you have both) for server!
235 // `connect()` will do client mode, `accept()` will do server mode (and will return `SSLSocket` instance)
236 class SSLSocket : Socket {
237 gnutls_certificate_credentials_t xcred;
238 gnutls_session_t session;
239 private bool sslInitialized = false;
240 bool manualHandshake = false; // for non-blocking sockets this should be `true`
241 bool isblocking = false;
242 private bool thisIsServer = false;
243 // server
244 private string certfilez; // "cert.pem"
245 private string keyfilez; // "key.pem"
247 // both key and cert can be in one file
248 void setKeyCertFile (const(char)[] certname, const(char)[] keyname=null) {
249 if (certname.length == 0) { certname = keyname; keyname = null; }
250 if (certname.length == 0) {
251 certfilez = keyfilez = "";
252 } else {
253 auto buf = new char[](certname.length+1);
254 buf[] = 0;
255 buf[0..certname.length] = certname;
256 certfilez = cast(string)buf;
257 if (keyname.length != 0) {
258 buf = new char[](keyname.length+1);
259 buf[] = 0;
260 buf[0..keyname.length] = keyname;
262 keyfilez = cast(string)buf;
266 // take care of pre-connection TLS stuff
267 //FIXME: possible memory leak on exception? (sholdn't be, as `close()` will free the things)
268 private void sslInit () {
269 if (sslInitialized) return;
270 sslInitialized = true;
272 // x509 stuff
273 gnutls_certificate_allocate_credentials(&xcred);
275 // sets the trusted certificate authority file (no need for us, as we aren't checking any certificate)
276 //gnutls_certificate_set_x509_trust_file(xcred, CAFILE, GNUTLS_X509_FMT_PEM);
278 if (thisIsServer) {
279 // server
280 if (certfilez.length < 1) throw new SocketException("TLS Error: certificate file not set");
281 if (keyfilez.length < 1) throw new SocketException("TLS Error: key file not set");
282 auto res = gnutls_certificate_set_x509_key_file(xcred, certfilez.ptr, keyfilez.ptr, GNUTLS_X509_FMT_PEM);
283 if (res < 0) {
284 import std.conv : to;
285 throw new SocketException("TLS Error: returned with "~res.to!string);
287 gnutls_init(&session, GNUTLS_SERVER);
288 gnutls_certificate_server_set_request(session, GNUTLS_CERT_IGNORE);
289 gnutls_handshake_set_timeout(session, /*GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT*/2300);
290 } else {
291 // client
292 // initialize TLS session
293 gnutls_init(&session, GNUTLS_CLIENT);
296 // use default priorities
298 const(char)* err;
299 auto ret = gnutls_priority_set_direct(session, "PERFORMANCE", &err);
300 if (ret < 0) {
301 import std.string : fromStringz;
302 import std.conv : to;
303 if (ret == GNUTLS_E_INVALID_REQUEST) throw new SocketException("Syntax error at: "~err.fromStringz.idup);
304 throw new SocketException("TLS Error: returned with "~ret.to!string);
307 auto ret = gnutls_set_default_priority(session);
308 if (ret < 0) {
309 import std.string : fromStringz;
310 import std.conv : to;
311 //if (ret == GNUTLS_E_INVALID_REQUEST) throw new Exception("Syntax error at: "~err.fromStringz.idup);
312 string errstr = gnutls_strerror(ret).fromStringz.idup;
313 gnutls_deinit(session);
314 gnutls_certificate_free_credentials(xcred);
315 throw new Exception("TLS Error ("~errstr~"): returned with "~ret.to!string);
317 gnutls_session_enable_compatibility_mode(session);
319 // put the x509 credentials to the current session
320 gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
323 // this is required for new TLS (fuck)
324 // call this before connecting
325 public void sslhostname (const(char)[] hname) @trusted {
326 import std.internal.cstring : tempCString;
327 int res = gnutls_server_name_set(session, GNUTLS_NAME_DNS, hname.tempCString, hname.length);
328 if (res < 0) {
329 import std.string : fromStringz;
330 import std.conv : to;
331 string errstr = gnutls_strerror(res).fromStringz.idup;
332 //gnutls_deinit(session);
333 //gnutls_certificate_free_credentials(xcred);
334 throw new Exception("TLS Error ("~errstr~"): returned with "~res.to!string);
338 public void sslHandshake () {
339 sslInit();
340 // lob the socket handle off to gnutls
341 gnutls_transport_set_ptr(session, cast(gnutls_transport_ptr_t)handle);
342 // perform the TLS handshake
343 for (;;) {
344 auto ret = gnutls_handshake(session);
345 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
346 if (ret < 0) {
347 import std.string : fromStringz;
348 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz.idup);
350 break;
354 override @property void blocking (bool byes) @trusted {
355 super.blocking(byes);
356 manualHandshake = !byes;
357 isblocking = byes;
360 override void connect (Address to) @trusted {
361 if (sslInitialized && thisIsServer) throw new SocketException("wtf?!");
362 thisIsServer = false;
363 sslInit();
364 super.connect(to);
365 if (!manualHandshake) sslHandshake();
368 protected override Socket accepting () pure nothrow {
369 return new SSLSocket();
372 override Socket accept () @trusted {
373 auto sk = super.accept();
374 if (auto ssk = cast(SSLSocket)sk) {
375 ssk.keyfilez = keyfilez;
376 ssk.certfilez = certfilez;
377 ssk.manualHandshake = manualHandshake;
378 ssk.thisIsServer = true;
379 ssk.sslInit();
380 if (!ssk.manualHandshake) ssk.sslHandshake();
381 } else {
382 throw new SocketAcceptException("failed to create ssl socket");
384 return sk;
387 // close the encrypted connection
388 override void close () @trusted {
389 scope(exit) sslInitialized = false;
390 if (sslInitialized) {
391 //{ import core.stdc.stdio : printf; printf("deiniting\n"); }
392 gnutls_bye(session, GNUTLS_SHUT_RDWR);
393 gnutls_deinit(session);
394 gnutls_certificate_free_credentials(xcred);
396 super.close();
399 override ptrdiff_t send (const(void)[] buf, SocketFlags flags) @trusted {
400 if (session is null || !sslInitialized) throw new SocketException("not initialized");
401 //return gnutls_record_send(session, buf.ptr, buf.length);
402 if (buf.length == 0) return 0;
403 for (;;) {
404 auto res = gnutls_record_send(session, buf.ptr, buf.length);
405 if (res >= 0 || !isblocking) return res;
406 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
407 //if (gnutls_error_is_fatal(res)) return res;
408 return res;
412 override ptrdiff_t send (const(void)[] buf) {
413 import core.sys.posix.sys.socket;
414 static if (is(typeof(MSG_NOSIGNAL))) {
415 return send(buf, cast(SocketFlags)MSG_NOSIGNAL);
416 } else {
417 return send(buf, SocketFlags.NOSIGNAL);
421 override ptrdiff_t receive (void[] buf, SocketFlags flags) @trusted {
422 if (session is null || !sslInitialized) throw new SocketException("not initialized");
423 //return gnutls_record_recv(session, buf.ptr, buf.length);
424 if (buf.length == 0) return 0;
425 for (;;) {
426 auto res = gnutls_record_recv(session, buf.ptr, buf.length);
427 if (res >= 0 || !isblocking) return res;
428 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
429 //if (gnutls_error_is_fatal(res)) return res;
430 return res;
434 override ptrdiff_t receive (void[] buf) { return receive(buf, SocketFlags.NONE); }
436 private this () pure nothrow @safe {}
438 this (AddressFamily af, SocketType type=SocketType.STREAM) {
439 super(af, type);
442 this (socket_t sock, AddressFamily af) {
443 super(sock, af);