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
;
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
) {
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 // put the x509 credentials to the current session
95 gnutls_credentials_set(session
, GNUTLS_CRD_CERTIFICATE
, xcred
);
96 sslInitialized
= true;
99 // this is required for new TLS (fuck)
100 // call this before connecting
101 public void sslhostname (const(char)[] hname
) @trusted {
102 import std
.internal
.cstring
: tempCString
;
103 int res
= gnutls_server_name_set(session
, GNUTLS_NAME_DNS
, hname
.tempCString
, hname
.length
);
105 import std
.string
: fromStringz
;
106 import std
.conv
: to
;
107 string errstr
= gnutls_strerror(res
).fromStringz
.idup
;
108 //gnutls_deinit(session);
109 //gnutls_certificate_free_credentials(xcred);
110 throw new Exception("TLS Error ("~errstr
~"): returned with "~res
.to
!string
);
114 public void sslHandshake () {
115 // lob the socket handle off to gnutls
116 gnutls_transport_set_ptr(session
, cast(gnutls_transport_ptr_t
)handle
);
117 // perform the TLS handshake
119 auto ret = gnutls_handshake(session
);
120 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
122 import std
.string
: fromStringz
;
123 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz
.idup
);
129 override @property void blocking (bool byes
) @trusted {
130 super.blocking(byes
);
134 override void connect (Address to
) @trusted {
136 if (!manualHandshake
) sslHandshake();
139 // close the encrypted connection
140 override void close () @trusted {
141 scope(exit
) sslInitialized
= false;
142 if (sslInitialized
) {
143 //{ import core.stdc.stdio : printf; printf("deiniting\n"); }
144 gnutls_bye(session
, GNUTLS_SHUT_RDWR
);
145 gnutls_deinit(session
);
146 gnutls_certificate_free_credentials(xcred
);
151 override ptrdiff_t
send (const(void)[] buf
, SocketFlags flags
) @trusted {
152 if (buf
.length
== 0) return 0;
154 auto res
= gnutls_record_send(session
, buf
.ptr
, buf
.length
);
155 if (res
>= 0 ||
!isblocking
) return res
;
156 if (res
== GNUTLS_E_INTERRUPTED || res
== GNUTLS_E_AGAIN
) continue;
157 //if (gnutls_error_is_fatal(res)) return res;
162 override ptrdiff_t
send (const(void)[] buf
) {
163 import core
.sys
.posix
.sys
.socket
;
164 static if (is(typeof(MSG_NOSIGNAL
))) {
165 return send(buf
, cast(SocketFlags
)MSG_NOSIGNAL
);
167 return send(buf
, SocketFlags
.NOSIGNAL
);
171 override ptrdiff_t
receive (void[] buf
, SocketFlags flags
) @trusted {
172 if (buf
.length
== 0) return 0;
174 auto res
= gnutls_record_recv(session
, buf
.ptr
, buf
.length
);
175 if (res
>= 0 ||
!isblocking
) return res
;
176 if (res
== GNUTLS_E_INTERRUPTED || res
== GNUTLS_E_AGAIN
) continue;
177 //if (gnutls_error_is_fatal(res)) return res;
182 override ptrdiff_t
receive (void[] buf
) { return receive(buf
, SocketFlags
.NONE
); }
184 this (AddressFamily af
, SocketType type
=SocketType
.STREAM
, string certbasename
=null) {
185 sslInit(certbasename
);
189 this (socket_t sock
, AddressFamily af
, string certbasename
=null) {
190 sslInit(certbasename
);
196 // ///////////////////////////////////////////////////////////////////////// //
197 // this can be used as both client and server socket
198 // don't forget to set certificate file (and key file, if you have both) for server!
199 // `connect()` will do client mode, `accept()` will do server mode (and will return `SSLSocket` instance)
200 class SSLSocket
: Socket
{
201 gnutls_certificate_credentials_t xcred
;
202 gnutls_session_t session
;
203 private bool sslInitialized
= false;
204 bool manualHandshake
= false; // for non-blocking sockets this should be `true`
205 bool isblocking
= false;
206 private bool thisIsServer
= false;
208 private string certfilez
; // "cert.pem"
209 private string keyfilez
; // "key.pem"
211 // both key and cert can be in one file
212 void setKeyCertFile (const(char)[] certname
, const(char)[] keyname
=null) {
213 if (certname
.length
== 0) { certname
= keyname
; keyname
= null; }
214 if (certname
.length
== 0) {
215 certfilez
= keyfilez
= "";
217 auto buf
= new char[](certname
.length
+1);
219 buf
[0..certname
.length
] = certname
;
220 certfilez
= cast(string
)buf
;
221 if (keyname
.length
!= 0) {
222 buf
= new char[](keyname
.length
+1);
224 buf
[0..keyname
.length
] = keyname
;
226 keyfilez
= cast(string
)buf
;
230 // take care of pre-connection TLS stuff
231 //FIXME: possible memory leak on exception? (sholdn't be, as `close()` will free the things)
232 private void sslInit () {
233 if (sslInitialized
) return;
234 sslInitialized
= true;
237 gnutls_certificate_allocate_credentials(&xcred
);
239 // sets the trusted certificate authority file (no need for us, as we aren't checking any certificate)
240 //gnutls_certificate_set_x509_trust_file(xcred, CAFILE, GNUTLS_X509_FMT_PEM);
244 if (certfilez
.length
< 1) throw new SocketException("TLS Error: certificate file not set");
245 if (keyfilez
.length
< 1) throw new SocketException("TLS Error: key file not set");
246 auto res
= gnutls_certificate_set_x509_key_file(xcred
, certfilez
.ptr
, keyfilez
.ptr
, GNUTLS_X509_FMT_PEM
);
248 import std
.conv
: to
;
249 throw new SocketException("TLS Error: returned with "~res
.to
!string
);
251 gnutls_init(&session
, GNUTLS_SERVER
);
252 gnutls_certificate_server_set_request(session
, GNUTLS_CERT_IGNORE
);
253 gnutls_handshake_set_timeout(session
, /*GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT*/2300);
256 // initialize TLS session
257 gnutls_init(&session
, GNUTLS_CLIENT
);
260 // use default priorities
263 auto ret = gnutls_priority_set_direct(session, "PERFORMANCE", &err);
265 import std.string : fromStringz;
266 import std.conv : to;
267 if (ret == GNUTLS_E_INVALID_REQUEST) throw new SocketException("Syntax error at: "~err.fromStringz.idup);
268 throw new SocketException("TLS Error: returned with "~ret.to!string);
271 auto ret = gnutls_set_default_priority(session
);
273 import std
.string
: fromStringz
;
274 import std
.conv
: to
;
275 //if (ret == GNUTLS_E_INVALID_REQUEST) throw new Exception("Syntax error at: "~err.fromStringz.idup);
276 string errstr
= gnutls_strerror(ret).fromStringz
.idup
;
277 gnutls_deinit(session
);
278 gnutls_certificate_free_credentials(xcred
);
279 throw new Exception("TLS Error ("~errstr
~"): returned with "~ret.to
!string
);
281 gnutls_session_enable_compatibility_mode(session
);
283 // put the x509 credentials to the current session
284 gnutls_credentials_set(session
, GNUTLS_CRD_CERTIFICATE
, xcred
);
287 // this is required for new TLS (fuck)
288 // call this before connecting
289 public void sslhostname (const(char)[] hname
) @trusted {
290 import std
.internal
.cstring
: tempCString
;
291 int res
= gnutls_server_name_set(session
, GNUTLS_NAME_DNS
, hname
.tempCString
, hname
.length
);
293 import std
.string
: fromStringz
;
294 import std
.conv
: to
;
295 string errstr
= gnutls_strerror(res
).fromStringz
.idup
;
296 //gnutls_deinit(session);
297 //gnutls_certificate_free_credentials(xcred);
298 throw new Exception("TLS Error ("~errstr
~"): returned with "~res
.to
!string
);
302 public void sslHandshake () {
304 // lob the socket handle off to gnutls
305 gnutls_transport_set_ptr(session
, cast(gnutls_transport_ptr_t
)handle
);
306 // perform the TLS handshake
308 auto ret = gnutls_handshake(session
);
309 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
311 import std
.string
: fromStringz
;
312 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz
.idup
);
318 override @property void blocking (bool byes
) @trusted {
319 super.blocking(byes
);
320 manualHandshake
= !byes
;
324 override void connect (Address to
) @trusted {
325 if (sslInitialized
&& thisIsServer
) throw new SocketException("wtf?!");
326 thisIsServer
= false;
329 if (!manualHandshake
) sslHandshake();
332 protected override Socket
accepting () pure nothrow {
333 return new SSLSocket();
336 override Socket
accept () @trusted {
337 auto sk
= super.accept();
338 if (auto ssk
= cast(SSLSocket
)sk
) {
339 ssk
.keyfilez
= keyfilez
;
340 ssk
.certfilez
= certfilez
;
341 ssk
.manualHandshake
= manualHandshake
;
342 ssk
.thisIsServer
= true;
344 if (!ssk
.manualHandshake
) ssk
.sslHandshake();
346 throw new SocketAcceptException("failed to create ssl socket");
351 // close the encrypted connection
352 override void close () @trusted {
353 scope(exit
) sslInitialized
= false;
354 if (sslInitialized
) {
355 //{ import core.stdc.stdio : printf; printf("deiniting\n"); }
356 gnutls_bye(session
, GNUTLS_SHUT_RDWR
);
357 gnutls_deinit(session
);
358 gnutls_certificate_free_credentials(xcred
);
363 override ptrdiff_t
send (const(void)[] buf
, SocketFlags flags
) @trusted {
364 if (session
is null ||
!sslInitialized
) throw new SocketException("not initialized");
365 //return gnutls_record_send(session, buf.ptr, buf.length);
366 if (buf
.length
== 0) return 0;
368 auto res
= gnutls_record_send(session
, buf
.ptr
, buf
.length
);
369 if (res
>= 0 ||
!isblocking
) return res
;
370 if (res
== GNUTLS_E_INTERRUPTED || res
== GNUTLS_E_AGAIN
) continue;
371 //if (gnutls_error_is_fatal(res)) return res;
376 override ptrdiff_t
send (const(void)[] buf
) {
377 import core
.sys
.posix
.sys
.socket
;
378 static if (is(typeof(MSG_NOSIGNAL
))) {
379 return send(buf
, cast(SocketFlags
)MSG_NOSIGNAL
);
381 return send(buf
, SocketFlags
.NOSIGNAL
);
385 override ptrdiff_t
receive (void[] buf
, SocketFlags flags
) @trusted {
386 if (session
is null ||
!sslInitialized
) throw new SocketException("not initialized");
387 //return gnutls_record_recv(session, buf.ptr, buf.length);
388 if (buf
.length
== 0) return 0;
390 auto res
= gnutls_record_recv(session
, buf
.ptr
, buf
.length
);
391 if (res
>= 0 ||
!isblocking
) return res
;
392 if (res
== GNUTLS_E_INTERRUPTED || res
== GNUTLS_E_AGAIN
) continue;
393 //if (gnutls_error_is_fatal(res)) return res;
398 override ptrdiff_t
receive (void[] buf
) { return receive(buf
, SocketFlags
.NONE
); }
400 private this () pure nothrow @safe {}
402 this (AddressFamily af
, SocketType type
=SocketType
.STREAM
) {
406 this (socket_t sock
, AddressFamily af
) {