facil.io

#ASN.1 DER

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

ASN.1 DER is the small binary grammar hiding inside X.509 certificates, keys, signatures, and TLS messages. This module parses and writes the pieces facil.io needs for that world.

The parser is non-allocating. Parsed elements point into the original DER buffer, so keep that buffer alive while you use the results. No buffer, no treasure map.

#Core Types

#fio_der_element_s

c
typedef struct {
  const uint8_t *data;
  size_t len;
  uint8_t tag;
  uint8_t is_constructed;
  uint8_t tag_class;
  uint8_t tag_number;
} fio_der_element_s;

A parsed DER element. data points to the content bytes, after the tag and length fields. len is the content length. The remaining fields describe the tag byte.

#fio_der_iterator_s

c
typedef struct {
  const uint8_t *pos;
  const uint8_t *end;
} fio_der_iterator_s;

Iterator state for walking a SEQUENCE or SET element.

#Tags and Classes

fio_der_tag_e defines the common universal tags, including FIO_DER_INTEGER, FIO_DER_BIT_STRING, FIO_DER_OID, FIO_DER_SEQUENCE, FIO_DER_SET, string types, FIO_DER_UTC_TIME, FIO_DER_GENERALIZED_TIME, and context wrappers FIO_DER_CONTEXT_0 through FIO_DER_CONTEXT_3.

fio_der_class_e names the tag classes: universal, application, context-specific, and private.

#OID Values

OID constants live in the X.509 module (FIO_X509_OID_*, see the X.509 documentation). An OID value is a plain fio_u128 where bytes 0-14 hold the DER content bytes (zero-padded) and byte 15 holds the content length. The length is folded in because 0x00 is a legal OID content byte (arc 0), so padding alone could not distinguish {..0B} from {..0B 00} - different OIDs. Oversized OIDs (content > 15 bytes) simply never match, which is safe: AlgorithmIdentifier OIDs are attacker-controlled and RFC 5280 requires exact match.

Build a value once per parsed element with fio___der_oid_value (internal), then compare it against any number of constants with fio___der_oid_eq (internal) - two u64-lane == comparisons, no memcmp, no call:

c
fio_u128 oid = fio___der_oid_value(&elem); /* build ONCE per element */
if (fio___der_oid_eq(oid, FIO_X509_OID_COMMON_NAME)) { /* ... */ }

#Parsing

#fio_der_parse

c
SFUNC const uint8_t *fio_der_parse(fio_der_element_s *elem,
                                    const uint8_t *data,
                                    size_t data_len);

Parses one DER element. On success, fills elem and returns a pointer to the next element. On error, returns NULL.

It rejects invalid DER lengths, truncated buffers, unsupported high-tag-number encodings, and indefinite lengths.

#fio_der_element_total_len

c
FIO_IFUNC size_t fio_der_element_total_len(const fio_der_element_s *elem,
                                            const uint8_t *data);

Returns the full encoded length of an element: tag + length + content. data must be the original pointer used to parse the element.

#Type Parsers

#fio_der_parse_integer

c
SFUNC int fio_der_parse_integer(const fio_der_element_s *elem,
                                 uint64_t *value);

Parses an ASN.1 INTEGER. For small integers, pass value and receive the 64-bit value. For large integers such as RSA moduli, pass NULL and use elem->data / elem->len directly. Leading zero bytes for positive integers are handled.

Returns 0 on success, -1 on error.

#fio_der_parse_bit_string

c
SFUNC int fio_der_parse_bit_string(const fio_der_element_s *elem,
                                    const uint8_t **bits,
                                    size_t *bit_len,
                                    uint8_t *unused_bits);

Parses an ASN.1 BIT STRING. bits points into the element data after the unused-bit count byte. bit_len is the byte length of the bit payload. unused_bits is the number of unused bits in the last byte.

#fio_der_parse_oid

c
SFUNC int fio_der_parse_oid(const fio_der_element_s *elem,
                             char *buf,
                             size_t buf_len);

Converts an ASN.1 OBJECT IDENTIFIER into dotted text such as 1.2.840.113549.1.1.11.

Returns the number of characters written, excluding the NUL byte, or -1 on error.

#fio_der_parse_time

c
SFUNC int fio_der_parse_time(const fio_der_element_s *elem,
                              int64_t *unix_time);

Parses UTCTime or GeneralizedTime into a Unix timestamp. Times must be UTC (Z). Fractional seconds are skipped when present.

#fio_der_parse_string

c
FIO_IFUNC const char *fio_der_parse_string(const fio_der_element_s *elem,
                                            size_t *len);

Returns a pointer to the string bytes for supported ASN.1 string tags, with the byte length in len. This does not validate or transcode the text; it simply gives you the payload.

#fio_der_parse_boolean

c
FIO_IFUNC int fio_der_parse_boolean(const fio_der_element_s *elem,
                                     int *value);

Parses a DER boolean and writes 0 or non-zero to value.

#Walking Sequences and Sets

#fio_der_iterator_init

c
FIO_IFUNC void fio_der_iterator_init(fio_der_iterator_s *it,
                                      const fio_der_element_s *sequence);

Initializes an iterator over the content bytes of a parsed SEQUENCE or SET.

#fio_der_iterator_next

c
SFUNC int fio_der_iterator_next(fio_der_iterator_s *it,
                                 fio_der_element_s *elem);

Parses the next child element and advances the iterator. Returns 0 when an element was read, -1 at end or on parse error.

#fio_der_iterator_has_next

c
FIO_IFUNC int fio_der_iterator_has_next(const fio_der_iterator_s *it);

Returns 1 when the iterator still has bytes to parse, otherwise 0.

#Tag Helpers

c
FIO_IFUNC int fio_der_is_tag(const fio_der_element_s *elem, uint8_t tag);
FIO_IFUNC int fio_der_is_context_tag(const fio_der_element_s *elem,
                                      uint8_t tag_num);
FIO_IFUNC uint8_t fio_der_tag_number(const fio_der_element_s *elem);

Use these to keep parser code readable:

  • fio_der_is_tag checks a universal tag such as FIO_DER_INTEGER.
  • fio_der_is_context_tag checks [0], [1], and friends.
  • fio_der_tag_number returns the decoded tag number.

#Encoding

All encoder functions return the number of bytes written or needed. Pass NULL as buf to calculate the encoded length before writing.

#Basic Encoders

c
SFUNC size_t fio_der_encode_length(uint8_t *buf, size_t len);
SFUNC size_t fio_der_encode_integer(uint8_t *buf,
                                     const uint8_t *data,
                                     size_t data_len);
SFUNC size_t fio_der_encode_integer_small(uint8_t *buf, uint64_t value);
SFUNC size_t fio_der_encode_null(uint8_t *buf);
SFUNC size_t fio_der_encode_boolean(uint8_t *buf, int value);

These write DER length fields, positive integers, NULL, and booleans. fio_der_encode_integer expects big-endian integer bytes and adds the leading zero byte when DER needs one to keep the integer positive.

OIDs are encoded with the internal fio___der_encode_oid(buf, fio_u128 oid), which TLV-wraps an OID value (see OID Values above) without any dot-string parsing.

#String and Byte Encoders

c
SFUNC size_t fio_der_encode_utf8_string(uint8_t *buf,
                                         const char *str,
                                         size_t str_len);
SFUNC size_t fio_der_encode_printable_string(uint8_t *buf,
                                              const char *str,
                                              size_t str_len);
SFUNC size_t fio_der_encode_bit_string(uint8_t *buf,
                                        const uint8_t *bits,
                                        size_t bit_len,
                                        uint8_t unused_bits);
SFUNC size_t fio_der_encode_octet_string(uint8_t *buf,
                                          const uint8_t *data,
                                          size_t data_len);

These write the tag, DER length, and payload for common primitive values.

#Header Encoders

c
SFUNC size_t fio_der_encode_sequence_header(uint8_t *buf, size_t content_len);
SFUNC size_t fio_der_encode_set_header(uint8_t *buf, size_t content_len);
SFUNC size_t fio_der_encode_context_header(uint8_t *buf,
                                            uint8_t tag_num,
                                            size_t content_len,
                                            int constructed);

These write only the wrapper tag and length. Write the content bytes immediately after the returned header length.

#Time Encoders

c
SFUNC size_t fio_der_encode_utc_time(uint8_t *buf, int64_t unix_time);
SFUNC size_t fio_der_encode_generalized_time(uint8_t *buf, int64_t unix_time);

These encode Unix timestamps as DER UTCTime or GeneralizedTime.

#Example

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

void scan_sequence(const uint8_t *der, size_t der_len) {
  fio_der_element_s seq;
  if (!fio_der_parse(&seq, der, der_len))
    return;
  if (!fio_der_is_tag(&seq, FIO_DER_SEQUENCE))
    return;

  fio_der_iterator_s it;
  fio_der_iterator_init(&it, &seq);

  fio_der_element_s elem;
  while (fio_der_iterator_next(&it, &elem) == 0) {
    if (fio_der_is_tag(&elem, FIO_DER_OID)) {
      fio_u128 oid = fio___der_oid_value(&elem);
      /* compare with FIO_X509_OID_* constants via fio___der_oid_eq, or */
      char dot[128];
      if (fio_der_parse_oid(&elem, dot, sizeof(dot)) > 0) {
        /* dotted string available for diagnostics */
      }
    }
  }
}