Cryptography

AXL’s cryptographic primitives are spread across a few headers by dependency, not gathered behind one libcrypto-style include. This page is the map.

Two tiers:

  • Standalone primitives have no external dependency and are available in every build — hashing, HMAC, randomness, and Authenticode image verification.

  • mbedTLS-backed primitives require an AXL_TLS=1 build (which links the vendored mbedTLS): TLS and public-key signature verification. Without AXL_TLS=1 they fail closed (see axl_pk_available() / axl_tls_available()).

<axl/axl-crypto.h> is the home for mbedTLS-backed primitives beyond TLS; it grows on demand as consumers need them (today: public-key signature verification).

Primitives at a glance

Primitive

Header

Backend

Reference

Hashing & checksums (MD5, SHA-1, SHA-256, CRC-32, Adler-32)

<axl/axl-digest.h>

standalone

AxlData — Data Structures

HMAC (RFC 2104)

<axl/axl-hmac.h>

standalone

AxlData — Data Structures

Cryptographic random bytes (EFI_RNG_PROTOCOL)

<axl/axl-rng.h>

standalone

AxlRng — cryptographic random bytes

Deterministic PRNG (GRand-style)

<axl/axl-rand.h>

standalone

AxlRand — deterministic pseudo-random numbers

Authenticode image-signature verification

<axl/axl-image-verify.h>

standalone

AxlImage — executable-image lifecycle

TLS 1.2 server / client

<axl/axl-tls.h>

needs AXL_TLS=1

AxlTls — TLS Support

Public-key signature verification (ECDSA-P256)

<axl/axl-crypto.h>

needs AXL_TLS=1

below

Public-key signature verification

axl_pk_verify checks a detached signature over a message against a public key the consumer ships — the building block for signed firmware updates, signed config blobs, or Secure-Boot-style image checks. It is pure verification: there is no signing side (the private key is held offline by the vendor and never ships).

AXL_PK_ECDSA_P256 (ECDSA over NIST P-256 with SHA-256) is supported: the public key is a DER SubjectPublicKeyInfo, the signature is a DER ECDSA signature, and the message is hashed with SHA-256 internally. AXL_PK_ED25519 is reserved but unsupported by the current mbedTLS build (which does not enable PSA crypto) and returns AXL_ERR.

Any non-AXL_OK result means “not verified, untrusted” — fail closed. Use axl_pk_available() to distinguish “verification not compiled in” from “signature invalid”.

Defines

AXL_AEAD_NONCE_LEN

Required nonce length, all algorithms.

AXL_AEAD_TAG_LEN

Authentication tag length.

AXL_CIPHER_CTR_IV_LEN

AES-CTR initial counter block size.

Typedefs

typedef struct AxlPkKey AxlPkKey

An opaque public-key key pair (or public-only key).

Holds a private key (from axl_pk_keygen / axl_pk_key_load_private) or a public key only (from axl_pk_key_load_public). Free with axl_pk_key_free().

typedef struct AxlEcdh AxlEcdh

An ephemeral ECDH key pair.

Created by axl_ecdh_new() (which generates the key pair), used to export the public key and compute the shared secret, then freed with axl_ecdh_free().

typedef struct AxlCipher AxlCipher

An AES-CTR keystream context.

Holds the running counter and keystream position so successive axl_cipher_ctr_xcrypt() calls continue one keystream. Free with axl_cipher_free().

Enums

enum AxlPkAlg

Public-key signature algorithm selector.

axl-crypto.h:

Generic public-key signature verification. A detached-signature verifier over the mbedTLS that AXL links when built with AXL_TLS=1.

This is a low-level cryptographic primitive — the building block for verifying signed firmware updates, signed configuration blobs, or Secure-Boot-style image checks against a public key the consumer ships. There is no signing side: signing is done offline by the vendor and the private key never ships in the binary.

Optional — like axl-tls.h

, the real implementation requires AXL_TLS=1 at build time. Without it, axl_pk_available() returns false and axl_pk_verify() returns AXL_ERR (fail-closed). Use axl_pk_available() to distinguish “verification not compiled in” from “signature

invalid”.

// pubkey: DER SubjectPublicKeyInfo baked into the image at build time.
// sig:    detached DER ECDSA signature over msg (ECDSA-with-SHA-256).
if (axl_pk_verify(AXL_PK_ECDSA_P256,
                  pubkey, pubkey_len,
                  msg, msg_len,
                  sig, sig_len) == AXL_OK) {
    // signature is valid — msg is authentic
}

The numeric values are part of the contract (a consumer may persist the choice alongside a signed blob); new algorithms only extend the range.

Values:

enumerator AXL_PK_ED25519

Ed25519 — reserved; NOT supported by the current mbedTLS build (requires PSA crypto, which AXL does not enable). axl_pk_verify() returns AXL_ERR for this algorithm. Because this is value 0, a zero-initialized selector fails closed (nothing verifies) — always set AXL_PK_ECDSA_P256 explicitly.

enumerator AXL_PK_ECDSA_P256

ECDSA over NIST P-256 (prime256v1) with SHA-256.

enumerator AXL_PK_RSA

RSA with PKCS#1 v1.5 over SHA-256 (rsa-sha2-256). Keygen produces a 3072-bit key. Supported by the key-handle API (axl_pk_keygen / _sign / _verify); NOT by the raw-bytes axl_pk_verify() below.

enumerator AXL_PK_ECDSA_P384

ECDSA over NIST P-384 (secp384r1) with SHA-384 (the hash follows the curve). Like P-256 it is supported by the key-handle API (axl_pk_keygen / _sign / _verify); NOT by the raw-bytes axl_pk_verify(). Its AXL_PK_SIG_RAW signature is r||s = 96 bytes (48-byte order each).

enum AxlPkSigFormat

Detached ECDSA signature byte layout.

Values:

enumerator AXL_PK_SIG_DER

ASN.1 DER: SEQUENCE { INTEGER r, INTEGER s } (OpenSSL / X.509 form). The format the raw-bytes axl_pk_verify() expects.

enumerator AXL_PK_SIG_RAW

Fixed-width r || s, each big-endian and left-padded to the curve order size (64 bytes for P-256, 96 for P-384). The form SSH, JWS, and COSE use. Ignored for RSA (its signature has a single encoding).

enum AxlAeadAlg

AEAD algorithm selector.

Values:

enumerator AXL_AEAD_AES_128_GCM

AES-128-GCM (16-byte key).

enumerator AXL_AEAD_AES_256_GCM

AES-256-GCM (32-byte key).

enumerator AXL_AEAD_CHACHA20_POLY1305

ChaCha20-Poly1305 (32-byte key).

enum AxlEcdhAlg

ECDH curve selector.

Values:

enumerator AXL_ECDH_P256

NIST P-256. Public key is the uncompressed SEC1 point 0x04||X||Y (65 bytes); shared secret is the X coordinate (32 bytes).

enumerator AXL_ECDH_X25519

X25519 (Curve25519). Public key and shared secret are 32 bytes each (RFC 7748).

enum AxlCipherAlg

AES-CTR key size selector.

Values:

enumerator AXL_CIPHER_AES_128_CTR

AES-128-CTR (16-byte key).

enumerator AXL_CIPHER_AES_256_CTR

AES-256-CTR (32-byte key).

Functions

bool axl_pk_available(void)

Whether public-key signature verification was compiled in.

True only when AXL was built with AXL_TLS=1 (which links mbedTLS). When false, axl_pk_verify() always returns AXL_ERR regardless of input — callers that must fail closed on a missing crypto backend can branch on this to log the distinction.

Returns:

true if axl_pk_verify() can verify signatures.

int axl_pk_verify(AxlPkAlg alg, const uint8_t *pubkey, size_t pubkey_len, const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len)

Verify a detached signature over a message with a public key.

Pure verification — there is no signing side in this API.

Encodings (for AXL_PK_ECDSA_P256):

  • pubkey is a DER-encoded SubjectPublicKeyInfo for a P-256 key (the output of openssl ec -pubout -outform DER). PEM is not accepted — convert to DER first.

  • sig is a DER-encoded ECDSA signature: SEQUENCE { INTEGER r, INTEGER s } (the output of openssl dgst -sha256 -sign). It must contain exactly the signature, with no trailing bytes.

  • msg is the raw message bytes; this function computes SHA-256(msg) internally (ECDSA-with-SHA-256). msg may be NULL only when msg_len is 0.

AXL_PK_ED25519 is reserved and unsupported by this build; it returns AXL_ERR.

Operates only on public inputs; there is no secret to leak. Any parse or verify failure is reported uniformly as AXL_ERR — a caller must treat every non-AXL_OK result as “not verified, untrusted” and fail closed. Do NOT branch security decisions on the reason for failure; the only distinction the API offers is axl_pk_available(), which separates “verification not compiled in” from “invalid”.

Parameters:
  • alg – signature algorithm

  • pubkey – public key bytes (encoding per alg)

  • pubkey_len – public key length in bytes (> 0)

  • msg – message bytes (NULL iff msg_len == 0)

  • msg_len – message length in bytes

  • sig – detached signature bytes

  • sig_len – signature length in bytes (> 0)

Returns:

AXL_OK if the signature is valid for msg under pubkey; AXL_ERR otherwise — invalid signature, malformed key/signature, NULL/zero-length key or signature, unsupported algorithm, or verification not compiled in (see axl_pk_available()).

AxlPkKey *axl_pk_keygen(AxlPkAlg alg)

Generate a new key pair.

AXL_PK_ECDSA_P256 generates a NIST P-256 key; AXL_PK_ECDSA_P384 a NIST P-384 key; AXL_PK_RSA generates a 3072-bit RSA key (slower — seconds on some firmware, but a one-time cost for a persisted host key). AXL_PK_ED25519 is unsupported and returns NULL.

Parameters:
  • alg – key algorithm to generate

Returns:

a new private key handle (caller frees with axl_pk_key_free), or NULL on failure / unsupported algorithm / TLS not compiled in.

AxlPkKey *axl_pk_key_load_private(const uint8_t *der, size_t len)

Load a private key from its PKCS#8 DER encoding.

der is an unencrypted PKCS#8 PrivateKeyInfo (the output of openssl pkcs8 -topk8 -nocrypt -outform DER, and of axl_pk_key_get_private_der()).

Parameters:
  • der – PKCS#8 DER private key

  • len – length in bytes (> 0)

Returns:

a new key handle, or NULL on malformed input / unsupported key type / TLS not compiled in.

AxlPkKey *axl_pk_key_load_public(const uint8_t *der, size_t len)

Load a public key from its SubjectPublicKeyInfo DER encoding.

Same encoding the raw-bytes axl_pk_verify() accepts. The resulting handle can verify but not sign.

Parameters:
  • der – SubjectPublicKeyInfo DER public key

  • len – length in bytes (> 0)

Returns:

a new key handle, or NULL on malformed input / unsupported key type / TLS not compiled in.

int axl_pk_key_get_private_der(const AxlPkKey *key, uint8_t *out, size_t *len)

Serialize a key’s private half as PKCS#8 DER.

Output-buffer protocol (shared by axl_pk_key_get_public_der and axl_pk_key_sign):

  • out == NULL: a size query — writes the exact required size to *len and returns AXL_OK.

  • out != NULL: *len is the buffer capacity on entry. On success *len is set to the bytes written. If the buffer is too small (including *len == 0), returns AXL_ERR, sets *len to the required size, and leaves out untouched — so the caller can re-call with a buffer of that size (the same *len may be passed straight through from a prior size query).

Fails if the handle has no private key.

Parameters:
  • key – key handle (must hold a private key)

  • out – output buffer, or NULL to size-query

  • len – [in/out] buffer capacity / bytes written

Returns:

AXL_OK on success; AXL_ERR on a public-only key, a too-small buffer, bad args, or TLS not compiled in.

int axl_pk_key_get_public_der(const AxlPkKey *key, uint8_t *out, size_t *len)

Serialize a key’s public half as SubjectPublicKeyInfo DER.

Output-buffer protocol as in axl_pk_key_get_private_der().

Parameters:
  • key – key handle

  • out – output buffer, or NULL to size-query

  • len – [in/out] buffer capacity / bytes written

Returns:

AXL_OK on success; AXL_ERR on a too-small buffer, bad args, or TLS not compiled in.

AxlPkAlg axl_pk_key_alg(const AxlPkKey *key)

Report a key handle’s algorithm.

Parameters:
  • key – key handle

Returns:

the AxlPkAlg of key (AXL_PK_ECDSA_P256, AXL_PK_ECDSA_P384, or AXL_PK_RSA). Returns AXL_PK_ED25519 (the reserved zero value) for a NULL handle.

int axl_pk_key_sign(const AxlPkKey *key, const uint8_t *msg, size_t msg_len, AxlPkSigFormat fmt, uint8_t *sig, size_t *sig_len)

Sign a message with a private key.

Computes SHA-256(msg) and signs it. For ECDSA, fmt selects the signature byte layout (AXL_PK_SIG_RAW for SSH/JWS/COSE, AXL_PK_SIG_DER for X.509-style); for RSA fmt is ignored.

Uses the output-buffer protocol of axl_pk_key_get_private_der() on sig / sig_len (pass sig == NULL to size-query). Note ECDSA DER signatures are variable-length (r/s leading-zero trimming), so a size query reports a safe upper bound; AXL_PK_SIG_RAW is fixed-width (64 bytes for P-256).

Parameters:
  • key – signing key (must hold a private key)

  • msg – message bytes (NULL iff msg_len == 0)

  • msg_len – message length in bytes

  • fmt – ECDSA signature layout (ignored for RSA)

  • sig – output buffer, or NULL to size-query

  • sig_len – [in/out] buffer capacity / bytes written

Returns:

AXL_OK on success; AXL_ERR on a public-only key, a too-small buffer, bad args, or TLS not compiled in.

int axl_pk_key_verify(const AxlPkKey *key, const uint8_t *msg, size_t msg_len, AxlPkSigFormat fmt, const uint8_t *sig, size_t sig_len)

Verify a detached signature with a key handle.

The handle-based peer of the raw-bytes axl_pk_verify(): verifies a signature over SHA-256(msg) using key (private or public). For ECDSA, fmt must match how the signature was produced, and for AXL_PK_SIG_RAW sig_len must be exactly the curve’s r||s size (64 bytes for P-256). For RSA, fmt is ignored.

Any non-AXL_OK result means “not verified, untrusted” — fail closed.

Parameters:
  • key – verifying key

  • msg – message bytes (NULL iff msg_len == 0)

  • msg_len – message length in bytes

  • fmt – ECDSA signature layout (ignored for RSA)

  • sig – detached signature bytes

  • sig_len – signature length in bytes (> 0)

Returns:

AXL_OK if the signature is valid; AXL_ERR otherwise.

void axl_pk_key_free(AxlPkKey *key)

Free a key handle. NULL-safe. Zeroizes private key material.

Parameters:
  • key – key handle to free

int axl_aead_seal(AxlAeadAlg alg, const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t nonce_len, const uint8_t *aad, size_t aad_len, const uint8_t *plaintext, size_t pt_len, uint8_t *ciphertext, uint8_t *tag, size_t tag_len)

Encrypt and authenticate a message (AEAD seal).

Encrypts plaintext and computes an authentication tag over the ciphertext and aad. The ciphertext is the same length as the plaintext; the tag is returned separately. ciphertext may alias plaintext for in-place encryption. plaintext / aad may be NULL only when their length is 0.

key_len must match alg (16 for AES-128, 32 for AES-256 and ChaCha20-Poly1305). nonce_len must be AXL_AEAD_NONCE_LEN and tag_len AXL_AEAD_TAG_LEN. The caller MUST NOT reuse a (key, nonce) pair across messages.

Parameters:
  • alg – AEAD algorithm

  • key – key (length per alg)

  • key_len – key length in bytes

  • nonce – nonce (AXL_AEAD_NONCE_LEN bytes)

  • nonce_len – nonce length (must be AXL_AEAD_NONCE_LEN)

  • aad – associated data (NULL iff aad_len == 0)

  • aad_len – associated-data length

  • plaintext – plaintext (NULL iff pt_len == 0)

  • pt_len – plaintext length

  • ciphertext – [out] ciphertext, pt_len bytes (may alias plaintext)

  • tag – [out] tag (AXL_AEAD_TAG_LEN bytes)

  • tag_len – tag buffer length (must be AXL_AEAD_TAG_LEN)

Returns:

AXL_OK on success; AXL_ERR on bad args, a key/nonce/tag length mismatch, or TLS not compiled in.

int axl_aead_open(AxlAeadAlg alg, const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t nonce_len, const uint8_t *aad, size_t aad_len, const uint8_t *ciphertext, size_t ct_len, const uint8_t *tag, size_t tag_len, uint8_t *plaintext)

Verify and decrypt a message (AEAD open).

Checks the tag over ciphertext and aad and, only if it is valid, writes the plaintext. On any authentication failure returns AXL_ERR and writes no plaintext (fail closed) — a caller must treat AXL_ERR as “not authentic, discard”. plaintext may alias ciphertext.

Length requirements match axl_aead_seal().

Parameters:
  • alg – AEAD algorithm

  • key – key (length per alg)

  • key_len – key length in bytes

  • nonce – nonce (AXL_AEAD_NONCE_LEN bytes)

  • nonce_len – nonce length (must be AXL_AEAD_NONCE_LEN)

  • aad – associated data (NULL iff aad_len == 0)

  • aad_len – associated-data length

  • ciphertext – ciphertext (NULL iff ct_len == 0)

  • ct_len – ciphertext length

  • tag – tag (AXL_AEAD_TAG_LEN bytes)

  • tag_len – tag length (must be AXL_AEAD_TAG_LEN)

  • plaintext – [out] plaintext, ct_len bytes (may alias ciphertext)

Returns:

AXL_OK if the tag is valid and decryption succeeded; AXL_ERR otherwise (bad tag, bad args, length mismatch, or TLS not built).

AxlEcdh *axl_ecdh_new(AxlEcdhAlg alg)

Generate an ephemeral ECDH key pair.

Parameters:
  • alg – curve to generate a key pair on

Returns:

a new context (caller frees with axl_ecdh_free), or NULL on a bad algorithm, RNG failure, or TLS not compiled in.

int axl_ecdh_get_public(AxlEcdh *e, uint8_t *out, size_t *len)

Export this context’s public key.

The encoding is per alg (SEC1 uncompressed point for P-256, the 32-byte u-coordinate for X25519). Uses the size-query / output-buffer protocol: out == NULL writes the required size to *len; otherwise *len is the capacity, set on success to the bytes written, and on a too-small buffer returns AXL_ERR with *len set to the required size.

Parameters:
  • e – ECDH context

  • out – [out] public key, or NULL to size-query

  • len – [in/out] buffer capacity / bytes written

Returns:

AXL_OK on success; AXL_ERR on a too-small buffer, bad args, or TLS not compiled in.

int axl_ecdh_compute(AxlEcdh *e, const uint8_t *peer_pub, size_t peer_len, uint8_t *out, size_t *len)

Compute the shared secret from a peer’s public key.

peer_pub is the peer’s public key in the same encoding axl_ecdh_get_public() produces. The shared secret is 32 bytes for both curves (P-256 X coordinate; X25519 shared u). Output uses the size-query / output-buffer protocol of axl_ecdh_get_public().

The raw secret MUST be passed through a KDF/hash before use as a key (it is not uniformly random). Returns AXL_ERR on an invalid peer key (e.g. not on the curve) — fail closed.

Parameters:
  • e – ECDH context

  • peer_pub – peer public key (encoding per the curve)

  • peer_len – peer public key length

  • out – [out] shared secret, or NULL to size-query

  • len – [in/out] buffer capacity / bytes written

Returns:

AXL_OK on success; AXL_ERR on a bad peer key, too-small buffer, bad args, or TLS not compiled in.

void axl_ecdh_free(AxlEcdh *e)

Free an ECDH context. NULL-safe. Zeroizes the private key.

Parameters:
  • e – context to free

AxlCipher *axl_cipher_ctr_new(AxlCipherAlg alg, const uint8_t *key, size_t key_len, const uint8_t *iv)

Create an AES-CTR context.

key_len must match alg (16 for AES-128, 32 for AES-256). iv is the 16-byte initial counter block. A (key, IV) pair must never be reused across streams.

Parameters:
  • alg – AES-CTR key size

  • key – key (length per alg)

  • key_len – key length in bytes

  • iv – AXL_CIPHER_CTR_IV_LEN-byte counter block

Returns:

a new context (caller frees with axl_cipher_free), or NULL on a key-length mismatch, bad args, or TLS not compiled in.

int axl_cipher_ctr_xcrypt(AxlCipher *c, const uint8_t *in, size_t len, uint8_t *out)

Encrypt or decrypt len bytes, advancing the keystream.

CTR is symmetric — the same call encrypts and decrypts. out may alias in for in-place operation. len need not be a multiple of the block size; the keystream position carries across calls so a stream may be processed in arbitrary chunks. in / out may be NULL only when len is 0.

Parameters:
  • c – cipher context

  • in – input bytes (NULL iff len == 0)

  • len – number of bytes

  • out – [out] output bytes, len bytes (may alias in)

Returns:

AXL_OK on success; AXL_ERR on bad args or TLS not compiled in.

void axl_cipher_free(AxlCipher *c)

Free a cipher context. NULL-safe. Zeroizes key material.

Parameters:
  • c – context to free