facil.io

#X.509 Certificates

c
#define FIO_X509
#include "fio-stl.h"

This module parses DER-encoded X.509v3 certificates for TLS 1.3 style certificate checks. It is a compact parser, verifier, trust-store checker, TLS Certificate-message splitter, and small self-signed certificate generator.

It is non-allocating while parsing: fio_x509_cert_s stores pointers into the original DER bytes. Keep those bytes alive. Certificates dislike disappearing floors.

Scope note: this is a minimal TLS-oriented X.509 implementation, not a full PKI policy engine. Not all X.509 features are supported.

#Supported Pieces

  • RSA, ECDSA P-256, ECDSA P-384, and Ed25519 public keys.
  • RSA PKCS#1 v1.5, RSA-PSS, ECDSA, and Ed25519 signature verification.
  • Validity period checks.
  • Hostname matching using SAN DNS names and CN fallback, with one-label wildcards.
  • Basic Constraints and Key Usage.
  • Certificate chain validation against an optional trust store.
  • TLS 1.3 Certificate message parsing.
  • Self-signed certificate generation for Ed25519 and P-256 keys.

#Main Types

#Key and Signature Enums

c
typedef enum {
  FIO_X509_KEY_UNKNOWN = 0,
  FIO_X509_KEY_RSA = 1,
  FIO_X509_KEY_ECDSA_P256 = 2,
  FIO_X509_KEY_ECDSA_P384 = 3,
  FIO_X509_KEY_ED25519 = 4,
} fio_x509_key_algo_e;
c
typedef enum {
  FIO_X509_SIGNATURE_UNKNOWN = 0,
  FIO_X509_SIGNATURE_RSA_PKCS1_SHA256 = 1,
  FIO_X509_SIGNATURE_RSA_PKCS1_SHA384 = 2,
  FIO_X509_SIGNATURE_RSA_PKCS1_SHA512 = 3,
  FIO_X509_SIGNATURE_RSA_PSS_SHA256 = 4,
  FIO_X509_SIGNATURE_RSA_PSS_SHA384 = 5,
  FIO_X509_SIGNATURE_RSA_PSS_SHA512 = 6,
  FIO_X509_SIGNATURE_ECDSA_SHA256 = 7,
  FIO_X509_SIGNATURE_ECDSA_SHA384 = 8,
  FIO_X509_SIGNATURE_ED25519 = 9,
} fio_x509_signature_algo_e;

#Key Usage and Errors

fio_x509_key_usage_e defines RFC 5280 key-usage bits such as FIO_X509_KU_DIGITAL_SIGNATURE, FIO_X509_KU_KEY_ENCIPHERMENT, and FIO_X509_KU_KEY_CERT_SIGN. ASN.1 bit strings use MSB-first ordering, so the constants look a little backwards until they save you from off-by-one sadness.

fio_x509_error_e gives chain validation results: FIO_X509_OK, parse failure, expired/not-yet-valid, signature failure, issuer mismatch, not-a-CA, no trust anchor, hostname mismatch, empty chain, and chain-too-long.

#fio_x509_trust_store_s

c
typedef struct {
  const uint8_t **roots;
  const size_t *root_lens;
  size_t root_count;
} fio_x509_trust_store_s;

A simple root store: arrays of DER certificate pointers and lengths.

#fio_tls_cert_entry_s

c
typedef struct {
  const uint8_t *cert;
  size_t cert_len;
} fio_tls_cert_entry_s;

One certificate entry extracted from a TLS 1.3 Certificate handshake message.

#fio_x509_cert_s

c
typedef struct fio_x509_cert_s fio_x509_cert_s;

The parsed certificate structure uses non-owning buffer views (fio_buf_info_s / fio_ubuf_info_s) and packs to 256 bytes with no interior padding:

  • verified: non-zero when a TLS backend verified this certificate's chain (peer inspection only; always zero after fio_x509_parse);
  • der: a reference to the original DER bytes (not a copy);
  • fingerprint: 32-byte SHA-256 of the DER bytes (see fio_x509_fingerprint);
  • version, serial, and validity timestamps;
  • raw subject and issuer DNs;
  • subject CN;
  • public key type and key data;
  • signature algorithm and signature bytes;
  • TBS certificate bytes used for verification;
  • Basic Constraints (is_ca), Key Usage (has_key_usage, key_usage), and SAN data (first DNS name, first IP address, and the raw SAN extension for iterating the rest).

All view pointers reference the original DER buffer.

#Parsing and Single-Certificate Checks

#fio_x509_parse

c
SFUNC int fio_x509_parse(fio_x509_cert_s *cert,
                         const uint8_t *der_data,
                         size_t der_len);

Parses one DER certificate. cert is zeroed first. Returns 0 on success, -1 on parse error.

#fio_x509_fingerprint

c
SFUNC void fio_x509_fingerprint(fio_x509_cert_s *cert);

Computes the SHA-256 of the certificate's DER bytes into cert->fingerprint (32 raw bytes). Call after fio_x509_parse; hashing is lazy so parsing never pays for it. Requires the SHA2 module.

#fio_x509_verify_signature

c
SFUNC int fio_x509_verify_signature(const fio_x509_cert_s *cert,
                                    const fio_x509_cert_s *issuer);

Verifies that issuer signed cert, using the issuer public key and the certificate signature algorithm. Returns 0 when the signature is valid, -1 otherwise.

#fio_x509_check_validity

c
FIO_IFUNC int fio_x509_check_validity(const fio_x509_cert_s *cert,
                                      int64_t current_time);

Checks not_before <= current_time <= not_after. Returns 0 when the certificate is in its validity window.

#fio_x509_match_hostname

c
SFUNC int fio_x509_match_hostname(const fio_x509_cert_s *cert,
                                  const char *hostname,
                                  size_t hostname_len);

Checks whether a certificate matches a hostname. SAN DNS entries are supported, with CN fallback. Wildcards are one-label wildcards such as *.example.com; they do not swallow a.b.example.com.

Returns 0 for a match, -1 for no match.

Distinguished Names are compared as raw DER with the core FIO_BUF_INFO_IS_EQ macro (e.g., FIO_BUF_INFO_IS_EQ(cert.issuer, root.subject)).

#Chain Validation

#fio_x509_verify_chain

c
SFUNC int fio_x509_verify_chain(const uint8_t **certs,
                                const size_t *cert_lens,
                                size_t cert_count,
                                const char *hostname,
                                int64_t current_time,
                                fio_x509_trust_store_s *trust_store);

Validates a certificate chain for TLS-style use.

Expected order:

  1. certs[0]: end-entity / server certificate.
  2. certs[1]: intermediate that signed certs[0].
  3. certs[n-1]: closest-to-root certificate.

Validation parses all certificates, checks validity periods, optionally matches the hostname, verifies each signature with the next certificate, checks issuer/subject DN links, requires CA certificates where needed, and optionally checks the final certificate against trust_store.

Trust anchors are matched by (subject DN, public key): the anchor's key must verify the final certificate's signature. This also covers self-signed certificates — a same-named impostor with a different key is rejected.

Returns FIO_X509_OK (0) on success, or a FIO_X509_ERR_* code.

#fio_x509_is_trusted

c
SFUNC int fio_x509_is_trusted(const fio_x509_cert_s *cert,
                              fio_x509_trust_store_s *trust_store);

Checks whether cert appears in the trust store by subject DN match. Returns 0 if trusted, -1 if not found.

#fio_x509_error_str

c
FIO_IFUNC const char *fio_x509_error_str(int error);

Returns a static string for a chain validation error code.

#TLS Certificate Messages

c
#define FIO_TLS_CERT_PARSE_ERROR ((size_t)-1)

#fio_tls_parse_certificate_message

c
SFUNC size_t fio_tls_parse_certificate_message(fio_tls_cert_entry_s *entries,
                                               size_t max_entries,
                                               const uint8_t *data,
                                               size_t data_len);

Parses a TLS 1.3 Certificate message body, after the handshake header. It extracts certificate entries and skips per-certificate extensions.

Returns the number of certificates parsed, or FIO_TLS_CERT_PARSE_ERROR on malformed input.

#Self-Signed Certificate Generation

Generation supports Ed25519 and P-256 key pairs.

c
typedef enum {
  FIO_X509_KEYPAIR_ED25519 = 1,
  FIO_X509_KEYPAIR_P256 = 2,
} fio_x509_keypair_type_e;

fio_x509_keypair_s stores the selected key type, secret key bytes, public key bytes, and their lengths. Use fio_x509_keypair_clear when done.

fio_x509_cert_options_s controls the subject CN, organization, organizational unit, country, validity window, SAN DNS names, CA flag, and key-usage bits.

#fio_x509_keypair_ed25519

c
SFUNC int fio_x509_keypair_ed25519(fio_x509_keypair_s *keypair);

Generates an Ed25519 key pair for certificate signing. Requires the Ed25519 module to be included. Returns 0 on success.

#fio_x509_keypair_p256

c
SFUNC int fio_x509_keypair_p256(fio_x509_keypair_s *keypair);

Generates a P-256 key pair for certificate signing. Requires the P-256 module to be included. Returns 0 on success.

#fio_x509_self_signed_cert

c
SFUNC size_t fio_x509_self_signed_cert(uint8_t *buf,
                                       size_t buf_len,
                                       const fio_x509_keypair_s *keypair,
                                       const fio_x509_cert_options_s *options);

Writes a DER-encoded self-signed X.509v3 certificate. Call with buf == NULL to get a worst-case buffer size, then call again with a real buffer.

Returns bytes written, or 0 on error.

#fio_x509_keypair_clear

c
FIO_IFUNC void fio_x509_keypair_clear(fio_x509_keypair_s *keypair);

Securely clears key material and zeros the structure.

#Example: Parse and Check Hostname

c
#define FIO_X509
#include "fio-stl.h"

int check_cert(const uint8_t *der, size_t der_len, int64_t now) {
  fio_x509_cert_s cert;
  if (fio_x509_parse(&cert, der, der_len))
    return -1;
  if (fio_x509_check_validity(&cert, now))
    return -1;
  if (fio_x509_match_hostname(&cert, "example.com", 11))
    return -1;
  return 0;
}

#Practical Notes

  • Define the crypto modules you need before including the STL: RSA, P-256, P-384, Ed25519, SHA-2, and ASN.1 may all matter depending on certificate algorithms.
  • trust_store == NULL skips root trust checking in fio_x509_verify_chain; useful for structural tests, not for real trust decisions.
  • Hostname checks are DNS-name focused. IP address SAN handling is not a full replacement for platform certificate validation.
  • Parsed strings and DNs are raw certificate data. Normalize policy elsewhere if your application needs browser-grade PKI behavior.