login
RepoCritics — Review. Share. Archive. Every open-source repo.

mirleft/ocaml-tls

Wiki: mirleft/ocaml-tls

Source: https://github.com/mirleft/ocaml-tls

Last synced 2026-07-18 · 1226 words · Edit wiki on GitHub →

mirleft/ocaml-tls

> TLS 1.0–1.3 implemented purely in OCaml — a memory-safe protocol engine with > no I/O, born from the "Not-Quite-So-Broken TLS" research project.

GitHub repo · API docs · License: BSD-2-Clause

Overview

ocaml-tls is a from-scratch implementation of Transport Layer Security in OCaml, started in 2014 by David Kaloper-Meršinjak and Hannes Mehnert and described in a Usenix Security 2015 paper[^1]. The motivating thesis — written in the shadow of Heartbleed — was that a TLS stack in a memory-safe, expressive language, structured as a pure function from (state, bytes) to (state, bytes), would avoid both the buffer-overflow class of C bugs and the state-machine confusion bugs (SMACK, EarlyCCS) that plagued OpenSSL-era implementations. The authors backed the claim with the Bitcoin Piñata (2015): a public MirageOS unikernel holding bitcoin whose private key could be taken by anyone who broke the stack. It was not broken[^2].

The defining design decision is that the core (Tls.Engine, opam package tls) performs no I/O and no mutation. You feed it received bytes and a state value; it returns a new state, bytes to write, and data for the application. Effectful adapters — tls-lwt, tls-mirage, tls-eio, tls-async, tls-miou-unix, and a plain-Unix layer — wire that engine to an actual scheduler and socket. This makes the protocol logic testable and composable (e.g. STARTTLS layering in SMTP) at the cost of a less familiar API than "wrap this socket".

At ~321 stars it is niche by raw numbers, but the number understates its footprint: it is the TLS stack of the entire MirageOS unikernel ecosystem and is maintained today by the Robur cooperative. Maintenance is demonstrably active — two CVEs reported in 2026 (missing keyUsage/extendedKeyUsage validation, CVE-2026-45388/45389) were fixed and released in v2.1.0 within the disclosure cycle[^3], and the last push was 2026-06-29.

Getting Started

opam install tls tls-lwt ca-certs
(* client.ml — HTTPS GET over tls-lwt (tls >= 1.0, result-based Config) *)
let tls_config =
  let authenticator =
    match Ca_certs.authenticator () with
    | Ok a -> a | Error (`Msg m) -> failwith m
  in
  match Tls.Config.client ~authenticator () with
  | Ok cfg -> cfg | Error (`Msg m) -> failwith m

let () =
  Lwt_main.run
    (let open Lwt.Infix in
     Tls_lwt.connect tls_config ("mirage.io", 443) >>= fun (ic, oc) ->
     Lwt_io.write oc "GET / HTTP/1.1\r\nHost: mirage.io\r\n\r\n" >>= fun () ->
     Lwt_io.read ~count:512 ic >>= Lwt_io.print)

Ca_certs loads the operating system's trust anchors; without it you must construct an X509.Authenticator.t yourself — there is no implicit default.

Architecture / How It Works

The repository ships one engine and several thin effectful shells:

  • tls (core) — handshake state machines for TLS 1.0/1.1/1.2 (client and

server) and TLS 1.3, record layer framing, and configuration validation. Tls.Engine.handle_tls : state -> string -> ... is the single entry point: a value-passing step function. Illegal state transitions are unrepresentable or rejected explicitly rather than discovered in an interop matrix — the Usenix paper's central claim[^1].

  • Crypto is delegated to mirage-crypto (AES-GCM/CCM,

ChaCha20-Poly1305, ECDH/ECDSA/EdDSA via mirage-crypto-ec, which vendors code generated by fiat-crypto)[^4]. Certificate parsing and chain validation live in the sibling ocaml-x509 library. ocaml-tls is therefore a protocol engine, not a crypto library — its safety story is only as good as those dependencies.

  • Effectful layers (tls-lwt, tls-mirage, tls-eio, tls-async,

tls-miou-unix, tls.unix) each own a read/write loop and expose the idioms of their scheduler. They are small; the protocol logic is never duplicated.

Two consequences of the pure core are worth understanding. First, composability: anything that shuttles bytes can host a TLS session (SMTP STARTTLS via colombe/sendmail, MirageOS Mirage_flow.S stacking). Second, defaults are opinionated and move: v0.13.0 (2021) dropped static-RSA and CBC ciphersuites and SHA1 signatures from the default config, leaving (EC)DHE + AEAD only[^3] — old peers may need explicit config to interoperate.

Production Notes

  • Performance is adequate, not competitive with C. The v1.0.0 rewrite

from Cstruct to bytes/string improved bandwidth up to 3× and handshakes ~2× (measured on an i7-5600U)[^3], and v2.0.1 cut allocation in send_application_data. There is no kTLS offload and no assembly fast path beyond what mirage-crypto's C stubs (AES-NI, PCLMULQDQ) provide. For a TLS-terminating proxy at high throughput, this is the wrong tool.

  • Secrets live on a GC-managed heap. OCaml's moving collector copies

values; reliable zeroization of key material is not achievable the way it is in C or Rust. Memory-safety cuts one attack class while making another mitigation unavailable — know which you care about.

  • API churn across majors is real. 1.0.0 (2024) changed error types,

made Tls.Config.{client,server} return result instead of raising, and removed Cstruct from the interface; 0.16.0 renamed tls.lwt to the separate tls-lwt opam package. Pinned tutorials from the 0.x era will not compile.

  • Old bugs do surface. v2.0.4 (2026) fixed an exception on empty

change-cipher-spec input present since 0.1.0, and v2.1.0's CVEs were missing certificate-extension validation, not memory errors[^3] — a reminder that language safety does not equal protocol completeness. The project does run interop suites (bettertls support landed in v2.1.1).

When to Use / When Not

Use when:

  • You are building MirageOS unikernels — it is the native and only real TLS

option there.

  • You are writing OCaml services and want TLS without C bindings in your

trust base (capnp-rpc, albatross, miragevpn all do this).

  • You need to compose TLS inside another protocol (STARTTLS, custom tunnels)

and benefit from an I/O-free engine, or want a readable reference implementation of the TLS state machines.

Avoid when:

  • Raw throughput or handshake rate is the bottleneck — OpenSSL-class stacks

with kTLS and assembly crypto are markedly faster.

  • You need FIPS certification or formally audited crypto primitives.
  • You are not in the OCaml ecosystem, or depend on long-tail TLS features

(exotic extensions, PKCS#11 HSM integration) a small team has not prioritized.

Alternatives

  • savonet/ocaml-ssl — OpenSSL bindings for OCaml; use instead when you need

maximum interop/performance and accept C in the trust base.

  • rustls/rustls — memory-safe TLS in Rust; use instead outside OCaml when

the "safe-language TLS" argument matters, with far wider deployment.

  • openssl/openssl — the C default; use instead when certification, hardware

offload, or ubiquity outweigh memory-safety concerns.

  • haskell-tls/hs-tls — the closest analogue: a pure functional-language TLS

stack, for Haskell codebases.

History

| Version | Date | Notes | |---------|------|-------| | 0.1.0 | 2014-07-08 | Initial beta release[^3]. | | 0.12.0 | 2020-05-12 | TLS 1.3 support[^3]. | | 0.13.0 | 2021-04-14 | AEAD-only defaults; ECDSA/EdDSA via mirage-crypto-ec. | | 0.15.4 | 2022-09-27 | tls-eio package added. | | 0.16.0 | 2023-02-14 | tls.lwt split into the tls-lwt opam package. | | 1.0.0 | 2024-08-21 | Cstruct removed; result-based config; up to 3× bandwidth; tls-miou-unix[^3]. | | 2.0.0 | 2025-02-05 | Dune variants (mirage-ptime) replace PCLOCK functors. | | 2.0.3 | 2025-09-26 | Plain-Unix implementation (tls.unix). | | 2.1.0 | 2026-05-20 | keyUsage/extendedKeyUsage validation; CVE-2026-45388/45389 fixed[^3]. | | 2.1.1 | 2026-06-29 | TLS 1.3 KeyUpdate state fix; IP checking in certificates. |

References

[^1]: Kaloper-Meršinjak, Mehnert, Madhavapeddy, Sewell, "Not-Quite-So-Broken TLS: Lessons in Re-Engineering a Security Protocol Specification and Implementation" — Usenix Security 2015. https://www.usenix.org/conference/usenixsecurity15/technical-sessions/presentation/kaloper-mersinjak [^2]: MirageOS blog, "Announcing the Bitcoin Piñata" — 2015-02. https://mirage.io/blog/announcing-bitcoin-pinata [^3]: ocaml-tls CHANGES.md (release notes and dates). https://github.com/mirleft/ocaml-tls/blob/main/CHANGES.md [^4]: mirage-crypto — cryptographic primitives used by ocaml-tls. https://github.com/mirage/mirage-crypto

Tags

ocaml, tls, cryptography, security, networking, protocol-implementation, mirageos, unikernel, functional-programming, memory-safety