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.