Open-source project
h2o/picotls avatar
h2o/picotls

picotls: a small TLS 1.3 stack written for QUIC

TLS 1.3 implementation in C (master supports RFC8446 as well as draft-26, -27, -28)

644 stars178 forksCLicense varies

At a glance

What is it?
picotls is a TLS 1.3 protocol stack in C with four crypto engines, session resumption with 0-RTT, direct access to handshake messages for QUIC, and extensions for raw public keys, certificate compression and encrypted client hello. It is the TLS layer under the H2O HTTP server.
Who is it for?
picotls fits a project that needs TLS it can reason about, that runs inside QUIC where the handshake has to be driven message by message, or that cannot afford a full TLS library's dependencies.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A protocol stack, not a TLS library

picotls is a TLS 1.3 protocol stack written in C, and the distinction between a stack and a library is the first thing to understand about it. The stack implements the protocol, the handshake and the record layer, and it exposes those pieces directly, with an API for dealing directly with TLS handshake messages, which the README calls essential for QUIC. It does not decide your cipher suite policy, it does not manage a certificate store for you, and it does not give you a high level client that takes a hostname and does the right thing. The design goal stated in the README is that picotls is fast, tiny and low-latency, with the primary user being the H2O HTTP server, which serves HTTP/1, HTTP/2 and HTTP/3 over QUIC. That is the right frame for the whole project. If you are writing a QUIC stack or an HTTP/3 server, you need the handshake to be a thing you can drive, not a black box that owns the socket.

Four crypto engines and what each one gives you

The stack supports four crypto engines, and the README has a table that maps each binding to its licence, key exchange, certificate and AEAD cipher. The OpenSSL backend uses libcrypto for crypto and X.509 operations, and it is the widest one, with secp256r1, secp384r1, secp521r1 and x25519 for key exchange, RSA, ECDSA across the three curves and ed25519 for certificates, and AES-128-GCM, AES-256-GCM, chacha20-poly1305 plus the AEGIS ciphers. The minicrypto backend uses cifra for most crypto and micro-ecc for secp256r1, and it is the dependency-light one, with the table noting that its licence is CC0 for cifra and 2-clause BSD for micro-ecc. There is a fusion AES-GCM engine optimised for QUIC and other protocols that use short AEAD blocks, and there is libaegis for the AEGIS AEADs. The minicrypto binding has a documented limitation, noted in the table, that it is capable of signing a handshake using the certificate's key but cannot verify a signature sent by the peer, which is a real constraint if you use it for a client.

The extensions that matter for modern deployments

The supported extensions list is short and each one earns its place. RFC 7250 covers raw public keys, which lets a device present a key without a certificate chain, which is a meaningful deployment size saving on constrained hardware. RFC 8879 covers certificate compression, which reduces the bytes on the wire when the same certificate is presented repeatedly. And there is support for Encrypted Client Hello at draft 15, which hides the server name in the Client Hello. There is also support for short-length encryption schemes, listed as Format Preserving Encryption under NIST SP 800-38G, and QUIC Load Balancer CID encryption, which encrypts the connection ID a load balancer hands out so that a client cannot infer which backend it reached. These are not the sort of feature list that exists for marketing, they are the ones a server operator or an embedded device actually needs.

Resumption, 0-RTT and the session cache

The stack supports PSK and PSK-DHE resumption using 0-RTT, and the test command line shows the mechanism. A session file argument enables resumption, and the README notes the file is read-write because the cli server implements a single-entry session cache:

code
% ./cli -s session-file 127.0.0.1 8443

The detail that makes this work is spelled out too, with the cli server sending NewSessionTicket when it first sends application data after receiving ClientFinished. That is a specific point in the protocol, and a client that connects and immediately closes would never get a ticket. Early data is a separate flag, and with the `-e` option the client first waits for user input and then sends CLIENT_HELLO along with the early-data:

code
% ./cli -s session-file -e 127.0.0.1 8443

The 0-RTT path is the one that needs care, since early data is replayable by definition, and the fact that the reference client waits for user input before sending it is a hint that the designers were aware of the problem.

Building it, including the submodule step

The build is CMake, and the first instruction in the README catches most people out. If you cloned picotls from git, you need to initialise the submodules:

code
% git submodule init
% git submodule update

After that the build is three commands:

code
% cmake .
% make
% make check

The top level of the repository shows what a C project of this kind contains, with a `CMakeLists.txt`, a `cmake/` directory, `deps/` for the vendored dependencies, `include/` and `lib/` for the headers and implementation, a `fuzz/` directory, a `t/` directory for the tests, `misc/`, an `appveyor.yml` for Windows builds, a `picotls.xcodeproj/`, a `picotlsvs/` directory for Visual Studio and a `picotls-probes.d` file. A `WindowsPort.md` document is referenced for using picotls with Visual Studio, and the developer documentation is on the project wiki rather than in the repository, which is a reasonable division for a library of this size.

The cli tool is a real test harness

The bundled cli is worth understanding because it is how you try the stack without writing a client. You start the test server, which listens on 127.0.0.1:8443, passing a certificate and a private key:

code
% ./cli -c /path/to/certificate.pem -k /path/to/private-key.pem  127.0.0.1 8443

And then you connect a second instance to it as a client:

code
% ./cli 127.0.0.1 8443

The cli server implements a single-entry session cache, which is a limitation worth knowing, since it means one resumption slot rather than a real cache, and it is enough to exercise the resumption and early-data paths. For a security-sensitive library this tool doubles as a way to read what actually goes over the wire, which is the fastest way to understand a handshake implementation.

Where this is the wrong choice

The limits are clear and mostly follow from the design. First, it is a stack, so the work above the handshake is yours. Certificate verification policy, cipher preference, session cache eviction, OCSP stapling and hostname verification are decisions you have to make and get right, and there is no default that is safe for you, which is a good thing for a library of this kind and a bad thing if you wanted a turnkey solution. Second, the Windows story is a separate document, with a `WindowsPort.md`, an `appveyor.yml` and a `picotlsvs/` directory, so Windows is supported but through a port rather than as a first class build. Third, the licence is not uniform. The software is provided under the MIT licence, and the TLS protocol implementation is MIT, but additional licences apply if you use the minicrypto binding, with CC0 and 2-clause BSD for the underlying libraries, and the OpenSSL binding brings the OpenSSL licence with it, so a distribution that links a particular backend has obligations the MIT statement does not cover. Fourth, there is no release history on the repository, no tags to pin, so a project that depends on picotls is tracking a moving master branch unless it vendors or forks.

Editorial conclusion

picotls fits a project that needs TLS it can reason about, that runs inside QUIC where the handshake has to be driven message by message, or that cannot afford a full TLS library's dependencies. It is the wrong choice if you want a batteries included TLS library, since certificate handling, cipher selection policy and everything above the handshake is largely up to you, and the wrong choice if you need Windows support without extra work, since that is a separate porting document. Before you build it, initialise the submodules, because a git clone without them will not build, and read the licence table rather than assuming MIT covers everything, since the minicrypto binding pulls in CC0 and 2-clause BSD code. The last push was on 2026-10-20 and the repository publishes no GitHub releases.

Frequently asked questions

What crypto backends does picotls support?

Four: an OpenSSL backend using libcrypto for crypto and X.509 operations, a minicrypto backend using cifra and micro-ecc, a fusion AES-GCM engine optimized for QUIC and other protocols with short AEAD blocks, and libaegis for the AEGIS AEADs. The minicrypto binding can sign a handshake but cannot verify a signature sent by the peer.

How do I build picotls from a git clone?

Initialise the submodules first with `git submodule init` and `git submodule update`, then run `cmake .`, `make` and `make check`. The README notes that a clone without the submodules initialised will not build.

Which TLS extensions does picotls support?

RFC 7250 for raw public keys, RFC 8879 for certificate compression, and Encrypted Client Hello at draft 15. It also supports short-length encryption schemes including Format Preserving Encryption under NIST SP 800-38G and QUIC Load Balancer CID encryption.

How does picotls handle session resumption and early data?

It supports PSK and PSK-DHE resumption using 0-RTT. The test client uses a session file argument for resumption, and the cli server implements a single-entry session cache and sends NewSessionTicket when it first sends application data after receiving ClientFinished. With the early data option, the client waits for user input and then sends CLIENT_HELLO along with the early data.

Official sources

  1. h2o/picotls on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/h2o-picotls.svg)](https://hysenlabs.com/projects/h2o-picotls)

Was this useful?