s6-networking: the s6-tlsd-io program
Keywords: s6-networking s6-tlsd-io tlsd tls ssl ucspi tcp inet network tcp/ip server

s6-networking
Software
skarnet.org

# The `s6-tlsd-io` program

`s6-tlsd-io` is a program that establishes a TLS or SSL server connection over an existing TCP connection, then communicates with an existing local program over already established pipes. It is the only server-side program in s6-networking that performs cryptography.

s6-networking does not include cryptographic software. All the crypto used in `s6-tlsd-io` is provided by the chosen SSL backend: BearSSL or LibreSSL, depending on the options given when configuring s6-networking.

## Interface

         s6-tlsd-io [ -S | -s ] [ -J | -j ] [ -Y | -y ] [ -v verbosity ] [ -K kimeout ] [ -k snilevel ] [ -d notif ] [ -- ] fdr fdw

- s6-tlsd-io expects to have an open connection it can talk to on its standard input and output. It also expects to read cleartext data from file descriptor fdr and write cleartext data to file descriptor fdw.
- It expects a TLS client on the other side of the network connection to initiate a TLS handshake, and it answers it.
- Then it acts as a full duplex tunnel, decrypting and transmitting data from stdin to fdw, and encrypting and transmitting data from fdr to stdout.
- When it cannot transmit any more data from/to the local application because connections have closed, `s6-tlsd-io` exits.

## Exit codes

- 0: the connection terminated normally.
- 96: error while configuring the TLS context - for instance, invalid private key or server certificate files.
- 97: error while setting up the TLS server engine.
- 98: TLS error while running the engine.
- 100: wrong usage.
- 111: system call failed.

## Protocol version and parameters

During the TLS/SSL handshake, `s6-tlsd-io` tries the versions of the protocol that is supported by default by the backend, with the default algorithms and cipher suites; the backend normally ensures that the most secure combination is tried first, with slow degradation until the client and the server agree.

- For BearSSL, this means use of the br_ssl_server_init_full_rsa() or br_ssl_server_init_full_ec() function. The supported protocol versions are described here.
- For LibreSSL, this means use of the tls_config_set_protocols(TLS_PROTOCOLS_DEFAULT) call.

As a server, `s6-tlsd-io` is conservative in its choice of protocols. It only supports TLS versions 1.2 and higher as supported by the backend, to avoid downgrade attacks.

## Environment variables

`s6-tlsd-io` expects to have the following environment variables set:

- `KEYFILE`: a path to the file containing the server's private key, DER- or PEM-encoded.
- `CERTFILE`: a path to the file containing the server's certificate chain, DER- or PEM-encoded. If PEM-encoded, the file can actually contain a chain of certificates.

If one of those variables is unset, `s6-tlsd-io` will refuse to run.

Alternatively, if snilevel is nonzero, the private key for the server named x should be held in a file whose name is contained in the `KEYFILE:x` environment variable, and the corresponding certificate chain file should be named in the `CERTFILE:x` environment variable. If snilevel is 2 or more, the `KEYFILE` and `CERTFILE` variables will be entirely ignored.

You can wildcard the first level of a SNI domain: you can point to a valid certificate for `foo.example.com` for all values of foo via a variable called `CERTFILE:*.example.com` (and have the corresponding `KEYFILE:*.example.com`). Only the first level can be wildcarded, and this does not work for top-level domains (you cannot hold a certificate for `*.com`). Note: if you are using a shell to handle your environment variables, be careful to properly quote them so that it does not attempt to expand the asterisks.

If you are using client certificates, `s6-tlsd-io` also requires either one of the following variables to be set:

- `CADIR`: a directory where trust anchors (i.e. root or intermediate CA certificates) can be found, one per file, DER- or PEM-encoded.
- `CAFILE`: a file containing the whole set of trust anchors, PEM-encoded.

If `s6-tlsd-io` is run as root, it can also read two more environment variables, `TLS_UID` and `TLS_GID`, which contain a numeric uid and a numeric gid; `s6-tlsd-io` then drops its root privileges to this uid/gid after reading its private key file. This ensures that the engine, including the handshake, is run with as little privilege as possible.

## SSL close handling

If the local application initiates the end of the session by sending EOF to fdr, there are two ways for the TLS layer to handle it.

- It can send a `close_notify` alert, and wait for an acknowledgement from the peer, at which point the connection is closed. The advantage of this setup is that it is secure even when the application protocol is not auto-terminated, i.e. when it does not know when its data stops. Old protocols such as HTTP-0.9 are in this case. The drawback of this setup is that it breaks full-duplex: once a peer has sent the `close_notify`, it must discard all the incoming records that are not a `close_notify` from the other peer. So if a client sends EOF while it is still receiving data from the server, the connection closes immediately and the data can be truncated.
- It can simply transmit the EOF, shutting down half the TCP connection, and wait for the EOF back. The advantage of this setup is that it maintains full-duplex: a client can send EOF after its initial request, and still receive a complete answer from the server. The drawback is that it is insecure when the application protocol is not auto-terminated.

Nowadays (2020), most protocols are auto-terminated, so it is not dangerous anymore to use EOF tranmission, and that is the default for `s6-tlsd-io`. Nevertheless, by using the `-S` option, you can force it to use the `close_notify` method if your application requires it to be secure.

## `s6-tlsd-io` options

- `-v verbosity` : Be more or less verbose. Default for verbosity is 1. 0 is quiet, 2 is verbose, more than 2 is debug output. This option currently has no effect.
- `-S` : send a `close_notify` alert and break the connection when receiving a local EOF.
- `-s` : transmit EOF by half-closing the TCP connection without using `close_notify`. This is the default.
- `-J` : treat EOF from the peer without a prior close_notify as an error: print a fatal error message and exit 98.
- `-j` : treat EOF from the peer without a prior close_notify as a normal exit condition. This is the default.
- `-Y` : Request a client certificate. The certificate is optional: if the client gives none, the connection proceeds.
- `-y` : Request a client certificate. The certificate is mandatory: if the client gives none, the handshake fails. The default, with neither the `-Y` nor the `-y` option, is not to request a client certificate at all.
- `-K kimeout` : if the handshake takes more than kimeout milliseconds to complete, close the connection. The default is 0, which means infinite timeout (never kill the connection).
- `-k snilevel` : support alternative certificate chains for SNI. If snilevel is nonzero, private key file names are read from every environment variable of the form `KEYFILE:x`, where x is a server name that the client may require, and a corresponding certificate chain for the name x should exist in the file named after the contents of the `CERTFILE:x` environment variable. If snilevel is 2 or more, only those files are read, and the generic `KEYFILE` and `CERTFILE` variables are ignored. If snilevel is 0, or if the option is not given, which is the default, `KEYFILE` and `CERTFILE` are the only private key / certificate chain pair that are loaded, no other environment variable is read for keypairs.
- `-d notif` : handshake notification. notif must be a file descriptor open for writing. When the TLS handshake has completed, some data (terminated by two null characters) will be sent to file descriptor notif. The data contains information about the TLS parameters of the connection; its exact contents are left unspecified, but there's at least an `SSL_PROTOCOL=protocol` string and an `SSL_CIPHER=cipher` string, both null-terminated. Sending this data serves a dual purpose: telling the notif reader that the handshake has completed, and providing it with some basic information about the connection. If this option is not given, no such notification is performed.