LEVIATHAN v962456e · 962456eee1

Library

TLS and cryptography

Secure connections wrap the existing socket in place, so TcpStream, HttpClient and HttpServer gain TLS without any API change.

since 0.1.0-alpha.1linux

Description

TLS in Leviathan is wrap-in-place. After a session is armed over a connected (client) or accepted (server) socket, the descriptor stays the same descriptor: sends, receives and closes are routed through the TLS session transparently, and the event loop keeps watching the same descriptor. A TcpStream, an HttpClient or an HttpServer therefore works over TLS with no change to the code that uses it.

TLS is provided by the system OpenSSL library (1.1.1 or newer) behind a narrow internal seam. A runtime built without OpenSSL ships a provider that reports TLS support not built into this runtime from any TLS call; programs that never use TLS are unaffected.

Client. std::tlsConnect(fd, host, alpn, caFile, verifyMode, callback) secures a connected descriptor. It performs the handshake without blocking, and on success calls callback with the descriptor, which you then wrap in a TcpStream. On failure, including a certificate that does not verify, it throws a RuntimeException whose message starts with TLS handshake: and names the reason, host and descriptor. Verification happens before the first byte of protocol is sent.

  • host is the name the certificate must match and the name sent for server selection. An IP-literal host sends no server name and is verified against the certificate's IP addresses.
  • alpn is the application protocol list ("h2,http/1.1"), or "" for none.
  • caFile adds one extra trusted certificate authority file to the system's roots; "" uses only the system roots (and the standard SSL_CERT_FILE and SSL_CERT_DIR environment variables).
  • verifyMode is 0 for full verification (certificate chain and host name), 1 for the chain only, or 2 for encryption without any verification. Mode 2 exists for testing; it is never the default and is easy to find by searching for it.

Server. std::tlsAccept(fd, certPath, keyPath, alpn, deadlineMs, callback) secures an accepted descriptor with a certificate and a private key. A client that fails the handshake or takes longer than deadlineMs is dropped and callback receives -1; the call never throws, so one bad client cannot disturb the accept loop.

Driving an armed descriptor. std::tlsDrive(fd, callback) runs the handshake for a descriptor that is already armed; callback receives the descriptor on success or -1 on failure. The event loop copes with the awkward cases of TLS on its own: a read that has to wait for the descriptor to become writable (and the reverse), and data that the TLS layer has already buffered while the descriptor looks idle.

HTTPS. HttpServer(port, certPath, keyPath) serves HTTPS, and HttpClient has requestTls, getTls, postTls and fetchTls. The client always uses full verification.

The cryptography floor. Two more natives support secure protocols:

  • std::sysRandom(n) returns n random bytes, carried in a string. It is cryptographic-grade (the operating system's secure random source). n <= 0 gives "", and more than one megabyte throws a RuntimeException.
  • std::sysRsaEncrypt(publicKeyPem, bytes, padding) encrypts bytes with an RSA public key, for key transport in authentication handshakes. padding is "oaep" (the default) or "pkcs1". It returns string? and gives None when the key cannot be parsed, the data does not fit the key, or the encryption fails.

Both are part of the native floor (see lang.system-floor), so they are not available in comptime code.

Rules

  • Secure by default. TLS 1.2 is the minimum and TLS 1.3 is enabled; renegotiation and compression are off; certificate verification is on unless verifyMode says otherwise.
  • tlsConnect throws on a failed handshake; tlsAccept drops the connection and reports -1.
  • The descriptor stays the same, so keep using the same TcpStream plumbing.
  • HttpClient cannot turn verification off.
  • The environment variable SSLKEYLOGFILE, when set, makes the runtime write key-log lines for debugging tools. It is off by default.
  • A runtime without OpenSSL reports TLS support not built into this runtime.

Examples

Opening a secure connection with full verification, then speaking plain text over it:

std::connectTimeout("example.com", 443, 3000, (fd) => {
    if (fd < 0) {
        console.writeln("could not connect");
        return;
    }
    std::tlsConnect(fd, "example.com", "", "", 0, (secured) => {
        TcpStream conn = TcpStream(secured);
        conn.onData((chunk) => console.writeln("received ${chunk.length()} bytes"));
        conn << "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
    });
});
not run — needs the network

Notes

Not implemented: client certificates (mutual TLS), session resumption, certificate reloading without a restart, a cipher policy setting, OCSP, native TLS providers for Windows and macOS, HTTP/2, and authenticated-encryption APIs over byte blocks.

See also

  • TcpStream — A connected network socket that reads and writes text.
  • HttpClient — A simple HTTP client that sends one request per connection and delivers the response to a callback.
  • HttpServer — A web server that listens on a port and answers each request with a handler function.