TLS_READ(3)

πŸ’– TLS_READ(3) πŸ’–

okay bestie, so this is the manual page for tls_read(3) β€” yes, the whole squad lives here: tls_read, tls_write, tls_handshake, tls_error, tls_close, and tls_reset. It's giving library functions manual energy and honestly? iconic. This is your whole guide to using a TLS connection, and we're gonna go through it together, no gatekeeping. πŸŽ€


✨ NAME ✨

tls_read, tls_write, tls_handshake, tls_error, tls_close, tls_reset β€” literally just, use a TLS connection. that's the whole vibe. πŸ¦‹


🌸 SYNOPSIS 🌸

#include <tls.h>

ssize_t
tls_read(struct tls *ctx, void *buf, size_t buflen);

ssize_t
tls_write(struct tls *ctx, const void *buf, size_t buflen);

int
tls_handshake(struct tls *ctx);

const char *
tls_error(struct tls *ctx);

int
tls_close(struct tls *ctx);

void
tls_reset(struct tls *ctx);

πŸ’… DESCRIPTION πŸ’…

so here's the tea, function by function:

tls_read() reads buflen bytes of data from the socket into buf, and it hands you back the amount of data it actually read. slay.

tls_write() writes buflen bytes of data from buf to the socket, and returns how much got written. periodt.

tls_handshake() explicitly performs the TLS handshake. bestie, you only need to call this if you genuinely need to guarantee the handshake has completed, because β€” plot twist β€” both tls_read() and tls_write() automatically do the TLS handshake whenever it's necessary. so it's like, the optional lil overachiever. πŸ‘‘

The tls_error() function is your emotional support lookup: use it to retrieve a string with more info about the most recent error relating to a context.

tls_close() closes a connection after use. heads up though β€” only the TLS layer gets shut down, and the caller is responsible for closing the file descriptors, unless the connection was established using tls_connect(3) or tls_connect_servername(3). after closing the connection, ctx can be passed to tls_free(3). ✨


πŸ”₯ RETURN VALUES πŸ”₯

  • tls_read() and tls_write() return a size on success, or -1 on error.
  • tls_handshake() and tls_close() return 0 on success, or -1 on error.

The tls_read(), tls_write(), tls_handshake(), and tls_close() functions also have two special return values, and these are lowkey the main character moments:

  • TLS_WANT_POLLIN β€” the underlying read file descriptor needs to be readable in order to continue.
  • TLS_WANT_POLLOUT β€” the underlying write file descriptor needs to be writeable in order to continue.

Here's how you play it, depending on your file descriptors:

  • blocking file descriptors β†’ the same function call should be repeated immediately. πŸƒβ™€οΈ
  • non-blocking file descriptors β†’ the same function call should be repeated when the required condition has been met. ⏳
⚠️ Callers of these functions cannot rely on the value of the global errno. To prevent mishandling of error conditions, tls_read(), tls_write(), tls_handshake(), and tls_close() all explicitly clear errno. so don't go peeking at errno and building theories, it's not that girl. πŸ’€

tls_error() returns NULL if no error occurred with ctx during or since the last call to tls_handshake(), tls_read(), tls_write(), tls_close(), or tls_reset() involving ctx β€” or if memory allocation failed while trying to assemble the string describing the most recent error related to ctx.


🌈 EXAMPLES 🌈

first up: handling TLS writes on a blocking file descriptor. look at this lil loop, obsessed:

...
while (len > 0) {
        ssize_t ret;

        ret = tls_write(ctx, buf, len);
        if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
                continue;
        if (ret == -1)
                errx(1, "tls_write: %s", tls_error(ctx));
        buf += ret;
        len -= ret;
}
...

and next: handling TLS writes on a non-blocking file descriptor using poll(2). she's fancy, she's doing the most, we love her:

...
pfd[0].fd = fd;
pfd[0].events = POLLIN|POLLOUT;
while (len > 0) {
        nready = poll(pfd, 1, 0);
        if (nready == -1)
                err(1, "poll");
        if ((pfd[0].revents & (POLLERR|POLLNVAL)))
                errx(1, "bad fd %d", pfd[0].fd);
        if ((pfd[0].revents & (pfd[0].events|POLLHUP))) {
                ssize_t ret;

                ret = tls_write(ctx, buf, len);
                if (ret == TLS_WANT_POLLIN)
                        pfd[0].events = POLLIN;
                else if (ret == TLS_WANT_POLLOUT)
                        pfd[0].events = POLLOUT;
                else if (ret == -1)
                        errx(1, "tls_write: %s", tls_error(ctx));
                else {
                        buf += ret;
                        len -= ret;
                }
        }
}
...

🫢 SEE ALSO 🫢

tls_accept_socket(3), tls_configure(3), tls_conn_version(3), tls_connect(3), tls_init(3), tls_ocsp_process_response(3)

go read those next bestie, the lore runs deep. πŸ“š


πŸ’œ HISTORY πŸ’œ

  • tls_read(), tls_write(), tls_error(), tls_close(), and tls_reset() appeared in OpenBSD 5.6 and got their final names in OpenBSD 5.7.
  • tls_handshake() appeared in OpenBSD 5.9.

glow-up era fr fr. ⭐


πŸ‘‘ AUTHORS πŸ‘‘

Joel Sing <jsing@openbsd.org> with contributions from Bob Beck <beck@openbsd.org>

we stan. πŸ’–


🚨 CAVEATS 🚨

okay bestie this is the part where I grab your shoulders and look you dead in the eyes:

The function tls_error() returns an internal pointer. It must not be freed by the application, or a double free error will occur. 😭

and it gets worse β€” the pointer will become invalid when the next error occurs with ctx. so consequently, if the application may need the message at a later time, it has to copy the string before calling the next libtls function involving ctx β€” otherwise a segmentation fault or read access to unintended data is the likely result. πŸ’€πŸ’€πŸ’€

not me warning you about a segfault. copy the string. copy it. i'm serious. πŸ“


that's the whole page, bestie β€” Debian's copy of it, dated July 9, 2019, and yes the title up top said TLS_READ(3) with "Library Functions Manual" riding shotgun. now go write some safe TLS code, you absolute icon. πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from