Open-Source Wikis

/

OpenSSL

/

How to contribute

/

Patterns and conventions

openssl/openssl

Patterns and conventions

A handful of patterns recur throughout the codebase. Reading them now will save you time later.

Coding style

  • C99, no compiler-specific extensions in portable code.
  • Formatting: WebKit base via clang-format, configured in .clang-format. The pre-commit hook (.pre-commit-config.yaml) enforces it.
  • Spelling: codespell runs in CI (.codespellrc).
  • Tabs are not used; indentation is four spaces.
  • if (ptr == NULL) is preferred over if (!ptr). Likewise if (n != 0) over if (n).
  • Public symbols live in include/openssl/; internal ones in include/internal/ or include/crypto/.

The official policy: https://openssl-library.org/policies/technical/coding-style/.

Initialization and cleanup

OpenSSL favors T *T_new(...) / T_free(T *) pairs. The free function must accept NULL. The *_new() returns NULL on failure and pushes onto the error stack:

EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (ctx == NULL) {
    /* error already on the stack */
    goto err;
}
/* ...use ctx... */
EVP_MD_CTX_free(ctx);

The classic int ret = 0; … err: cleanup; return ret; pattern is universal.

Refcounting

Many structures (X509, EVP_PKEY, SSL_SESSION, OSSL_PROVIDER, …) are reference-counted via CRYPTO_UP_REF / CRYPTO_DOWN_REF (see include/internal/refcount.h). The convention:

X509 *cert = X509_new();
X509_up_ref(cert);    /* now refcount = 2 */
some_api_that_takes_ownership(cert);
X509_free(cert);      /* releases your reference; the API still has its own */

*_dup returns a deep copy when one is needed.

_ex variants and library contexts

Anything new that allocates or fetches should accept an explicit OSSL_LIB_CTX *libctx and a property query string. The convention is the trailing _ex:

EVP_PKEY_CTX *EVP_PKEY_CTX_new_from_name(OSSL_LIB_CTX *libctx,
                                         const char *name,
                                         const char *propquery);
RSA *RSA_new_ex(OSSL_LIB_CTX *libctx, const char *propq);

NULL for libctx means the default context. For new code, plumb a libctx through your call graph rather than relying on the default. See subsystems/core-and-libctx.

OSSL_PARAM as the universal arg-pack

Wherever you would otherwise need 11 setters, OpenSSL passes an OSSL_PARAM array. Build with OSSL_PARAM_construct_* and a static array, or with OSSL_PARAM_BLD for dynamic shapes:

OSSL_PARAM params[] = {
    OSSL_PARAM_construct_utf8_string("group", "secp256r1", 0),
    OSSL_PARAM_construct_int("use_cofactor_dh", &one),
    OSSL_PARAM_END
};
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_params(ctx, params);

Documented in include/openssl/params.h, include/openssl/param_build.h, crypto/params.c, crypto/param_build.c. See doc/man3/OSSL_PARAM*.pod.

Error reporting

ERR_raise(ERR_LIB_X509, X509_R_BAD_DATE);
ERR_raise_data(ERR_LIB_X509, X509_R_BAD_DATE, "value=%s", str);

Each library has its own ERR_LIB_* constant and per-library reason codes. Add new ones to the relevant *err.h and let make update regenerate the dispatch tables. crypto/err/README.md walks through the workflow.

ASN.1 templating

Encoder/decoders for ASN.1 types are declared with macros in *.h.in and implemented with parallel macros in *.c:

ASN1_SEQUENCE(X509_NAME_ENTRY) = {
    ASN1_SIMPLE(X509_NAME_ENTRY, object, ASN1_OBJECT),
    ASN1_SIMPLE(X509_NAME_ENTRY, value, ASN1_PRINTABLE)
} ASN1_SEQUENCE_END(X509_NAME_ENTRY)

IMPLEMENT_ASN1_FUNCTIONS(X509_NAME_ENTRY)

The implementations live in crypto/asn1/. Generic encode/decode helpers like i2d_X509_NAME_ENTRY, d2i_X509_NAME_ENTRY, X509_NAME_ENTRY_new, X509_NAME_ENTRY_free are emitted automatically. See doc/man3/ASN1_TYPE_get.pod, doc/man3/d2i_X509.pod.

For new code, prefer using OSSL_ENCODER_to_data() / OSSL_DECODER_from_data() (provider-backed); the legacy i2d_* and d2i_* family is kept for backward compat. See features/encoders-decoders.

BIO instead of FILE *

OpenSSL functions almost universally take a BIO * rather than a FILE *. The same call works for files, sockets, in-memory buffers, base64-encoding wrappers, and SSL connections:

BIO *out = BIO_new_fp(stdout, BIO_NOCLOSE);
PEM_write_bio_X509(out, cert);
BIO_free_all(out);

crypto/bio/ defines the abstraction; BIO_method_st is the vtable; new BIO types implement bread, bwrite, ctrl, etc. See doc/man3/BIO_*.pod.

Build files (build.info)

Each directory has a build.info listing what to compile. Example from crypto/asn1/build.info:

LIBS=../../libcrypto
SOURCE[../../libcrypto]= a_strex.c a_int.c ...

When you add a .c file, edit the matching build.info. The top-level Configure reads them all and produces a single Makefile. See reference/build-system.

Documentation policy

Every new public function gets a POD page in the appropriate doc/man[1357]/ directory. The policy:

  • Section 1: command-line tools (openssl-req.pod, etc.).
  • Section 3: API (one .pod per primary function plus =item blocks for related ones).
  • Section 5: configuration files (config.pod, fips_config.pod).
  • Section 7: concepts and protocols (crypto.pod, provider.pod, openssl-quic.pod, ossl-guide-*.pod).

Run make doc-nits to catch missing sections.

Symbol versioning

OpenSSL uses linker version maps. Public symbols listed in util/libcrypto.num and util/libssl.num get an ordinal that fixes the symbol's identity for ABI purposes; they are baked into linker scripts (libcrypto.ld, libssl.ld) by util/mkdef.pl. Adding a new public symbol means:

  1. Declare it in a public header.
  2. Implement it.
  3. Run make update to add it to libcrypto.num / libssl.num.
  4. Commit the generated changes.

OPENSSL_NO_* macros gate features in/out at compile time; they are emitted into include/openssl/opensslconf.h.in from Configurations/.

Pluggable record layer (since 3.2)

The TLS record layer is a pluggable interface, with the default TLS implementation in ssl/record/methods/. New record methods (e.g. the QUIC record layer) declare a OSSL_RECORD_METHOD and register it. See ssl/record/record.h and doc/designs/quic-design/.

Threading

OpenSSL is thread-safe per OSSL_LIB_CTX. The actual threading primitives are in crypto/threads_*.c (one per platform: pthread, Windows, none). Locks (CRYPTO_RWLOCK), once-init (CRYPTO_THREAD_run_once), and read-copy-update (RCU) are exposed via crypto.h. See subsystems/threading.

Memory

Use OPENSSL_malloc/OPENSSL_free (debug-instrumented, sanitizer-friendly) for general allocations and OPENSSL_secure_malloc/OPENSSL_secure_free for keys and other sensitive material. The secure heap is mlock'd and zeroed on free. See crypto/mem.c, crypto/mem_sec.c. See subsystems/memory.

What not to do

  • Don't add new uses of EVP_PKEY_CTX_ctrl or EVP_PKEY_CTX_ctrl_str. Use OSSL_PARAM instead. (The legacy controls still work — crypto/evp/ctrl_params_translate.c is the bridge.)
  • Don't add new uses of ENGINE_*. Write a provider.
  • Don't expose new internal-only symbols in include/openssl/. Use include/internal/ or include/crypto/.
  • Don't bypass the EVP layer to call low-level functions like RSA_public_encrypt. They are deprecated.
  • Don't printf from the library. Use BIO_* or the trace facility.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – OpenSSL wiki | Factory