# TLSe is a TLS stack in one C file, built to survive a fork

> TLSe implements TLS 1.0 through 1.3 in a single C file on top of libtomcrypt, and its distinguishing feature is the ability to export a TLS context and import it in a pre-forked worker. That is a narrow, deliberate design, and it comes with the caveats of a library whose release tags stopped in 2018.

**eduardsui/tlse** — Single C file TLS 1.2/1.3 implementation, using tomcrypt as crypto library

- Repository: https://github.com/eduardsui/tlse
- Stars: 685 · Forks: 107
- Language: C
- License: NOASSERTION
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/eduardsui-tlse

## One C file, several protocol versions, one unusual feature

TLSe is described as a single C file TLS 1.3, 1.2, 1.1 and 1.0 implementation, with the weak ciphers removed from TLS 1.0, using libtomcrypt as its crypto library. It also supports DTLS 1.2 and 1.0. Before using `tlse.c` you may want to download and compile tomcrypt, or alternatively use the `libtomcrypt.c` amalgamation that ships in the repository.

The repository layout is tiny and tells the story: `tlse.c` and the optional `tlse.h`, the `libtomcrypt.c` amalgamation, `x509.c`, a `root.pem` and a `tls_root_ca.h` for the trust anchor, a `CMakeLists.txt`, an `examples/` directory and a `LICENSE` file.

The README is also clear about what is missing. It does not implement 0-RTT, and client-side TLS 1.3 support is experimental.

There is one feature that justifies the library existing at all, and the README calls it the main feature: the ability to serialize a TLS context via `tls_export_context` and re-import it with `tls_import_context` in another pre-forked worker process, with the socket descriptor sent via `sendmsg`. Everything else, including the secondary features such as SRTP key exchange, encryption and decryption, DTLS-SRTP and WebRTC `RTCPeerConnection` support without dependencies, reads as secondary to that one capability.

## Compiling it is a single include

The build is deliberately simple. A simple TLS client:


`$ gcc tlshello.c -o tlshello -ltomcrypt -ltommath -DLTM_DESC`  


And a simple TLS server:


`$ gcc tlsserverhello.c -o tlsserverhello -ltomcrypt -ltommath -DLTM_DESC`  


The README adds that for debugging TLS connections the DEBUG flag must be set, as `-DDEBUG`. The `LTM_DESC` define is what tells libtomcrypt to provide the descriptor API, and dropping it produces link errors that look alarming and are not.

If you would rather not link against an installed libtomcrypt, the library may use the `libtomcrypt.c` amalgamation instead, and the client then compiles as:


`$ gcc tlshello.c -o tlshello -DTLS_AMALGAMATION`


with the server as:


`$ gcc tlsserverhello.c -o tlsserverhello -DTLS_AMALGAMATION`


Using it in your own code is one line, because the entire library is a single file that you just include in your source:


`#include "tlse.c"`


`tlse.h` is optional and safe to skip, and the README notes it is only worth including if you add `tlse.c` to your makefile, which is useful when linking against C++.

## The cmake path and how compile definitions are passed

There is a cmake build as well, and the invocation in the README is a single line:


`$ mkdir build && cd build && cmake ../ -DTLSE_COMPILE_DEFINITIONS="TLS_AMALGAMATION" -DBUILD_EXAMPLES=ON && cmake --build . --verbose`


Two switches matter. `TLSE_COMPILE_DEFINITIONS` is how you tell the library which compile time features to switch on, and the README notes it may be given multiple values as semicolon separated values enclosed in a double quoted string. `BUILD_EXAMPLES=ON` builds the example programs, which the README lists as a simple client, a simple server, and two blocking examples that use the libssl style API.

The examples need certificates to run. The README says that after compiling them you should put `fullchain.pem` and `privkey.pem` in a directory called `testcert` in the working directory, and it mentions using Let's Encrypt for certificate generation.

So the build surface is small: a header, an optional amalgamated crypto file, and a set of defines. The cost of that simplicity is that every configuration decision lands in a preprocessor symbol rather than in a configuration API, and you need to know which ones exist and what they default to.

## Exporting a TLS context is what makes it unusual

Most TLS libraries assume one process, one connection, one context. TLSe is built around a different deployment shape, and the README says so directly: the main feature of this implementation is the ability to serialize a TLS context, via `tls_export_context` and re-import it, via `tls_import_context` in another pre-forked worker process, with the socket descriptor possibly sent via `sendmsg`.

Think about what that buys. A server that accepts connections in a parent process and hands them to workers, in the classic pre-fork model, has a problem: the TLS state, the negotiated session keys and the certificate chain live in the parent's memory, and the child that ends up serving the socket does not have them. With a conventional library, the options are to negotiate the TLS session in the child, which means a handshake per child rather than a per connection model, or to pass secrets around by hand.

TLSe's answer is to serialise the whole context and re-import it in the worker. The socket descriptor travels separately, with `sendmsg` as the suggested mechanism, which is the standard way to pass a descriptor over a Unix socket.

This is also the feature that carries the most risk, and it deserves the scrutiny. A serialisation format is an interface between processes, and if a worker can be tricked into importing a context, the boundaries you thought you had are gone. That is not a reason to reject the library, but it is a reason to read the export and import code before you rely on it.

## Ciphers, legacy support and the ROBOT mitigation

The cipher story is detailed, which is unusual and welcome in a TLS implementation.

TLS 1.2 is supported, along with TLS 1.1 and TLS 1.0 when `TLS_LEGACY_SUPPORT` is defined, and it is on by default. The RSA, ECDSA, DHE and ECDHE suites are listed explicitly, including the CBC and GCM variants of AES with SHA and SHA256.

Then there is the RSA key exchange problem. The README says the following ciphers are supported but disabled by default, and that to enable them TLSe must be compiled with `-DNO_TLS_ROBOT_MITIGATION`. ROBOT attack is mitigated by default, but it is recommended to disable RSA encryption to avoid future vulnerabilities. The switch name is unfortunate, since defining `NO_TLS_ROBOT_MITIGATION` is how you turn the mitigation off, but the behaviour is the safe default and the documentation is explicit about the risk.

ChaCha20 and Poly1305 are supported as well, with `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`, `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256` and `TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256`, and those are enabled by default.

From tomcrypt it uses RSA, ECDSA and AES in GCM and CBC modes, SHA1, SHA256, SHA384, SHA512 and HMAC. It implements what is needed for TLS 1.2 and includes a pem and der parser, and it offers a low level interface that is efficient for non-blocking asynchronous sockets alongside a blocking, libssl style interface.

## Client certificates, SNI, ALPN and certificate validation

The extension surface is a set of functions rather than a configuration struct, and each one is named in the README.

Client certificates are supported. To request one you call `tls_request_client_certificate(TLSContext *)` following `tls_accept(TLSContext *)`, so the request happens as part of the accept rather than beforehand.

Server Name Indication is implemented, and the SNI string is read with `tls_sni(TLSContext *)`. ALPN is implemented too, with `tls_add_alpn(struct TLSContext *, const char *)` to offer a protocol and `const char *tls_alpn(struct TLSContext *)` to read the negotiated one, and SCSV is supported alongside them.

Certificate validation is a group of four functions: `tls_certificate_chain_is_valid`, `tls_certificate_chain_is_valid_root`, `tls_certificate_valid_subject` and `tls_certificate_is_valid`, which checks the not before and not after dates. The README attaches one important condition to the first of them: certificates fed to `tls_certificate_chain_is_valid` must be in correct order, where certificate 2 signs certificate 1 and certificate 3 signs certificate 2, and certificate 1, the first, is the certificate used in the key exchange. Get that order wrong and you get a validation failure that looks like a bad certificate rather than a bad list.

The library was written to be used by the author's other projects, the Concept Applications Server and the Concept Native Client, which is worth knowing because it tells you the intended environment is a C++ server application.

## Threading, KTLS and the limits the README admits

Three practical constraints come out of the README.

On threading, if thread safety is needed you need to call `tls_init()` before letting any other threads in, and you must not use the same object from multiple threads without a mutex. Other than that, TLSe and libtomcrypt are thread safe, and you may want to define `LTC_PTHREAD` if you are using libtomcrypt.

On KTLS, TLSe supports the kernel TLS implementation on Linux kernel 4.13 or higher. If `TLS_RX` is not defined, KTLS is send-only, so you may use send and sendfile but not recv, and the negotiation must still be handled by TLSe. You enable it with `-DWITH_KTLS`. The README is refreshingly honest about the friction here, noting that it is not clear which header should be included for the Linux structures and that you may need to check the structures and constants in the kernel documentation yourself.

On the edges, 0-RTT is not implemented, client-side TLS 1.3 is experimental, and the security note says that for DTLS it does not implement a state machine, so using the DTLS implementation with UDP carries a warning whose full text runs past the end of the README copy available here. That note is the part to read in full before you put this library on a UDP socket.

Finally, the version picture. The release tags are v1.0.7 from 2018-01-26, v1.0.6 from 2017-01-03 and v1.0.4 from 2016-08-10, while the repository is not archived and the last push was on 2026-10-21. So the code has moved in the years since the last tag, and there is no tagged release to compare against. The licence is one GitHub cannot categorise, with the terms in the `LICENSE` file at the root.

## Conclusion

Adopt TLSe when you are writing a C or C++ server that needs TLS with no external TLS dependency, or when you need a TLS context to survive a fork into a worker process, and when you are prepared to own the security review yourself. Do not adopt it when a well audited library and a maintained build are available to you, since a bespoke TLS implementation is a liability, and do not rely on its DTLS support. Verify first that the protocol versions and cipher suites you need are enabled rather than merely compiled in, that `tls_init()` is called before any thread starts, and that the version you take is current, since the newest release tag is v1.0.7 from 2018-01-26 while the last push was on 2026-10-21.

## FAQ

### How do I compile TLSe?

For the simple client, `$ gcc tlshello.c -o tlshello -ltomcrypt -ltommath -DLTM_DESC`, and the same form for the server, adding `-DDEBUG` to debug TLS connections. If you use the libtomcrypt.c amalgamation instead of an installed libtomcrypt, compile with `-DTLS_AMALGAMATION` and include the single file with `#include "tlse.c"`.

### What is special about TLSe compared to other TLS libraries?

The README names the main feature: a TLS context can be serialized with tls_export_context and re-imported with tls_import_context in another pre-forked worker process, with the socket descriptor sent via sendmsg. It also supports DTLS 1.2 and 1.0, SRTP, DTLS-SRTP and WebRTC RTCPeerConnection without dependencies.

### Does TLSe support TLS 1.3 and 0-RTT?

It implements TLS 1.3, 1.2, 1.1 and 1.0 without the weak ciphers, and the README states that it does not implement 0-RTT and that client-side TLS 1.3 support is experimental.

### How do I enable the RSA key exchange ciphers in TLSe?

The RSA encryption ciphers are supported but disabled by default, because the ROBOT attack is mitigated by default. To enable them you compile with `-DNO_TLS_ROBOT_MITIGATION`, and the README recommends disabling RSA encryption to avoid future vulnerabilities.

## Sources

- [eduardsui/tlse on GitHub](https://github.com/eduardsui/tlse)
- [Issues](https://github.com/eduardsui/tlse/issues)
- [README](https://github.com/eduardsui/tlse/blob/master/README.md)
- [Releases](https://github.com/eduardsui/tlse/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/eduardsui-tlse
