@hackage s2n-tls-ffi0.1.0.0

Low-level FFI bindings to the s2n-tls library

s2n-tls-ffi

Low-level FFI bindings to the s2n-tls library.

This package provides raw FFI bindings which higher-level packages can build upon.

Using a Linked s2n-tls Library

Call withS2nTlsFfi with Linked to use the linked-in s2n-tls library.

import S2nTls.Ffi

main :: IO ()
main = withS2nTlsFfi Linked $ \ffi -> do
    -- use ffi...

Using a Dynamically Loaded s2n-tls library

Call withS2nTlsFfi with Dynamic path to load s2n-tls at runtime. This enables runtime selection of different library builds - for example, switching between FIPS and non-FIPS crypto backends without recompiling your application.

import S2nTls.Ffi

main :: IO ()
main = do
    let lib = if needFips then Dynamic "libs2n-fips.so" else Dynamic "libs2n.so"
    withS2nTlsFfi lib $ \ffi -> do
        -- use ffi...

Error Handling via C Wrappers

s2n-tls stores error information in thread-local storage (TLS). When a function fails, the error code and debug message are available via s2n_errno_location() and s2n_strerror_debug(). However, Haskell's FFI provides no guarantee that a subsequent call will execute on the same OS thread, making direct access to thread-local error state unreliable.

To solve this, most s2n functions are called through thin C wrappers that:

  1. Call the underlying s2n function
  2. If it fails, immediately read the error code and debug string from TLS
  3. Copy this information into an output struct with an owned 256-byte buffer
  4. Return to Haskell with the error information safely captured

This ensures error information is captured in the same C stack frame before control returns to Haskell. The S2nTlsFfi record exposes these wrapped functions, which return Either S2nError result for functions that can fail.

Note that s2n_send and s2n_recv have special behavior: if the error was S2N_ERR_T_BLOCKED, debug info is not captured because this is a frequent, expected case that doesn't need debug overhead.

Direct Error Functions

The s2n_strerror and s2n_strerror_name functions are not subject to thread-local storage limitations - they take an error code as input and return static strings. These can be called directly without wrappers if you need to format error messages yourself.

Missing Symbol Behavior

Symbol loading is forgiving - missing symbols don't cause failure at load time. This allows the bindings to work with different versions of s2n-tls that may not export all functions.

  • The missingSymbols field of S2nTlsFfi lists symbol names that couldn't be loaded
  • Calling a function for a missing symbol throws a MissingSymbol exception
  • Check missingSymbols at startup if your application requires specific functions

Tests

The memory-safety test suite invokes all FFI functions to detect segfaults and memory errors. It does not verify correct behavior - only that calling each function doesn't crash. Each function name is printed before invocation, so if a crash occurs you can identify the culprit.

Run with:

cabal test memory-safety
  • s2n-tls - High-level Haskell bindings built on this package
  • warp-s2n-tls - TLS support for Warp using s2n-tls

License

Apache-2.0