8. chat echo (clear-then-TLS)
import std.socket : InternetAddress, Socket, TcpSocket, SocketOptionLevel, SocketOption; import std.exception : enforce, ifThrown; import std.stdio : writefln, writeln, stderr; import std.string : toStringz, fromStringz, representation; import std.format : format; import deimos.openssl.ssl; import deimos.openssl.err : ERR_print_errors_fp; static import cio = core.stdc.stdio; import hostname : hostnamez; import sslclient : SSLClient; void main() { ssl_ctx_st* ctx = SSL_CTX_new(TLS_server_method); enforce(ctx, "SSL_CTX_new() failed"); scope (exit) SSL_CTX_free(ctx); if (!SSL_CTX_use_certificate_file(ctx, "ssl.crt", SSL_FILETYPE_PEM) || !SSL_CTX_use_PrivateKey_file(ctx, "ssl.key", SSL_FILETYPE_PEM)) { ERR_print_errors_fp(cio.stderr); enforce(false, "SSL_CTX_use_certificate_file() failed"); } enum port = 4444; scope listener = new TcpSocket; listener.blocking = true; listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, 1); listener.bind(new InternetAddress(port)); listener.listen(10); writefln!"Listening on %d."(port); ubyte[1024] buf; ptrdiff_t len; while (true) { writeln; auto client = SSLClient(listener.accept); writefln!"Received connection from %s."(client.sock.remoteAddress.toString); while (0 < (len = client.recv(buf[]))) { if (buf[0 .. len] == "STARTTLS\n") { if (!client.accept(ctx).ifThrown(0)) { writeln("Failed to secure socket."); ERR_print_errors_fp(cio.stdout); break; } client.send(format!"SSL/TLS using %s\n"(SSL_get_cipher(client.ssl) .fromStringz).representation); } else { client.send(buf[0 .. len]); } } } assert(0); }
sslclient.d
import std.socket : Socket; import std.exception : enforce; import std.string : toStringz, fromStringz, representation; import deimos.openssl.ssl : ssl_st, ssl_ctx_st, SSL_new, SSL_set_tlsext_host_name, SSL_set_fd, SSL_accept, SSL_connect, SSL_read, SSL_write, SSL_shutdown, SSL_free; import hostname : hostnamez; struct SSLClient { Socket sock; ssl_st* ssl; int recv(ubyte[] buf) @trusted { assert(buf.ptr !is null); assert(buf.length < int.max); if (ssl) return SSL_read(ssl, cast(void*) buf.ptr, cast(int) buf.length); else return cast(int) sock.receive(buf); } int send(const ubyte[] buf) @trusted { assert(buf.ptr !is null); assert(buf.length < int.max); if (ssl) return SSL_write(ssl, cast(void*) buf.ptr, cast(int) buf.length); else return cast(int) sock.send(buf); } private void ssl_prelude(ssl_ctx_st* ctx) { assert(ssl is null); enforce(ssl = SSL_new(ctx), "SSL_new() failed"); enforce(SSL_set_tlsext_host_name(ssl, cast(char*) hostnamez), "SSL_set_tlsext_host_name failed"); enforce(SSL_set_fd(ssl, sock.handle), "SSL_set_fd() failed"); } bool accept(ssl_ctx_st* ctx) { ssl_prelude(ctx); enforce(SSL_accept(ssl) == 1, "SSL_accept() failed"); return true; } bool connect(ssl_ctx_st* ctx) { ssl_prelude(ctx); enforce(SSL_connect(ssl) == 1, "SSL_connect() failed"); return true; } ~this() { if (ssl) { SSL_shutdown(ssl); SSL_free(ssl); } sock.close; } }
This server is the first to require a proper dub setup, with multiple configurations to reuse an sslclient.d
library that wraps std.socket.Socket
with an SSL state, which has I/O functions that either use raw socket I/O or SSL I/O depending on the SSL state. You should check out the dub directory with fossil:
$ fossil clone https://d.minimaltype.com/ (a bunch of fossil output) $ cd d/net/8 $ make ssl.pem (a bunch of openssl output) $ dub build --config=server Performing "debug" build using /usr/bin/dmd for x86_64. net8 ~master: building configuration "server"... Linking... $ dub build --config=client Performing "debug" build using /usr/bin/dmd for x86_64. net8 ~master: building configuration "client"... Linking...
Server interaction
Listening on 4444. Received connection from 127.0.0.1:57268
Client interaction
this is cleartext this is cleartext STARTTLS subject: /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd issuer: /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd Accepting self-signed certificate. SSL/TLS using TLS_AES_256_GCM_SHA384 ^^ three lines from the client, then the last line from the server ^^ three lines from the client, then the last line from the server this is ciphertext this is ciphertext hi hi ^CThe 'STARTTLS' line is input to the client that causes it and the server to negotiate an encrypted connection.
strace of the client's "hi" exchange
strace: Process 178463 attached read(0, "hi\n", 1024) = 3 write(3, "\27\3\3\0\24\377x\325/\315\31f\3615\240!\23\230cv\365\240\232\3161", 25) = 25 read(3, "\27\3\3\0\24", 5) = 5 read(3, "^\261U\v\247\354\24\231\327~\302\210\\\322u\251-\300\337=", 20) = 20 write(1, "hi\n", 3) = 3 read(0, ^Cstrace: Process 178463 detachedshowing cleartext to the terminal, and encrypted communications over the socket.