7. hello (TLS)

This server is the first in the series to have some related files. See the fossil repo for, especially, a Makefile to generate the required crypto.

#! /usr/bin/env dub
/+ dub.sdl:
    dependency "openssl" version="~>2.0.3+1.1.0h"
    dependency "hostname" version="~>0.1.2"
    libs "ssl"
+/
import std.socket : InternetAddress, Socket, TcpSocket, SocketOptionLevel, SocketOption;
import std.exception : enforce;
import std.stdio : writefln, writeln, stderr;
import std.string : toStringz, fromStringz;
import deimos.openssl.ssl;
import deimos.openssl.err : ERR_print_errors_fp;
static import cio = core.stdc.stdio;
import hostname : hostnamez;

enum port = 4444;

void send(ssl_st* ssl, scope string msg) nothrow {
    SSL_write(ssl, cast(void*) msg.ptr, cast(int) msg.length);
}

int main() {
    auto 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)) {
        stderr.writeln("SSL_CTX_use_certificate_file() failed");
        ERR_print_errors_fp(cio.stderr);
        return 1;
    }

    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);

    while (true) {
        writeln;
        auto client = listener.accept;
        scope (exit)
            client.close;
        writefln!"Received connection from %s."(client.remoteAddress.toString);

        auto ssl = SSL_new(ctx);
        enforce(ssl, "SSL_new() failed");
        scope (exit) {
            SSL_shutdown(ssl);
            SSL_free(ssl);
        }

        enforce(SSL_set_tlsext_host_name(ssl, cast(char*) hostnamez),
                "SSL_set_tlsext_host_name failed");

        SSL_set_fd(ssl, client.handle);

        if (SSL_accept(ssl) <= 0) {
            stderr.writeln("SSL_accept() failed");
            ERR_print_errors_fp(cio.stderr);
            continue;
        }

        writefln!"SSL/TLS using %s"(SSL_get_cipher(ssl).fromStringz);

        ssl.send("Hello, world!\n");
    }
    assert(0);
}

Server interaction

Listening on 4444.

Received connection from 127.0.0.1:49434.
SSL/TLS using TLS_AES_256_GCM_SHA384

Client interaction

$ ncat --recv-only --ssl localhost 4444
Hello, world!

$ ./client.d 
SSL/TLS using TLS_AES_256_GCM_SHA384
Hello, world!
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.

is there a convenient way to get a binary out of this script?

dub can build from these directly:
$ dub build --compiler=gdc -brelease --single hello.d
Performing "release" build using gdc for x86_64.
hello ~master: building configuration "application"...

what is the 'hostname' import about?

That's just a wrapper for gethostname(), as OpenSSL wants to set a hostname.

when does the crypto kick in?

At the SSL_accept (in a server), or the SSL_connect (in a client). After these calls, further networking should use SSL_send and SSL_read.

what's with that static import?

This code uses D's stdio, but also needs to pass a FILE* to an OpenSSL function for libc stdio. The static import helps to make it clear that I'm passing libc's stderr where that's passed.

this is super confusing, is there a book I can read about it?

I found that Hands-On Network Programming with C had a helpful section on OpenSSL.