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*/;
21 public import std
.socket
;
25 // ///////////////////////////////////////////////////////////////////////// //
26 shared static this () { gnutls_global_init(); }
27 shared static ~this () { gnutls_global_deinit(); }
30 // ///////////////////////////////////////////////////////////////////////// //
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;
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;
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
);
57 import std
.string
: fromStringz
;
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
71 auto ret = gnutls_priority_set_direct(session, "PERFORMANCE", &err);
73 import std.string : fromStringz;
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
);
84 import std
.string
: fromStringz
;
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
) {
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
);
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
);
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
138 auto ret = gnutls_handshake(session
);
139 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
141 import std
.string
: fromStringz
;
142 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz
.idup
);
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
);
155 if (len
!= 0) res
= desc
[0..len
].idup
;
160 override @property void blocking (bool byes
) @trusted {
161 super.blocking(byes
);
165 override void connect (Address to
) @trusted {
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
);
182 override ptrdiff_t
send (const(void)[] buf
, SocketFlags flags
) @trusted {
183 if (buf
.length
== 0) return 0;
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;
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
);
198 return send(buf
, SocketFlags
.NOSIGNAL
);
202 override ptrdiff_t
receive (void[] buf
, SocketFlags flags
) @trusted {
203 if (buf
.length
== 0) return 0;
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;
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
);
220 this (AddressFamily af
, SocketType type
=SocketType
.STREAM
, string certbasename
=null) {
221 sslInit(certbasename
);
225 this (socket_t sock
, AddressFamily af
, string certbasename
=null) {
226 sslInit(certbasename
);
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;
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
= "";
253 auto buf
= new char[](certname
.length
+1);
255 buf
[0..certname
.length
] = certname
;
256 certfilez
= cast(string
)buf
;
257 if (keyname
.length
!= 0) {
258 buf
= new char[](keyname
.length
+1);
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;
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);
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
);
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);
292 // initialize TLS session
293 gnutls_init(&session
, GNUTLS_CLIENT
);
296 // use default priorities
299 auto ret = gnutls_priority_set_direct(session, "PERFORMANCE", &err);
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
);
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
);
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 () {
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
344 auto ret = gnutls_handshake(session
);
345 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
347 import std
.string
: fromStringz
;
348 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz
.idup
);
354 override @property void blocking (bool byes
) @trusted {
355 super.blocking(byes
);
356 manualHandshake
= !byes
;
360 override void connect (Address to
) @trusted {
361 if (sslInitialized
&& thisIsServer
) throw new SocketException("wtf?!");
362 thisIsServer
= false;
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;
380 if (!ssk
.manualHandshake
) ssk
.sslHandshake();
382 throw new SocketAcceptException("failed to create ssl socket");
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
);
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;
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;
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
);
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;
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;
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
) {
442 this (socket_t sock
, AddressFamily af
) {