Utilities

Serial Number Generator

serial_generator.py

X.509 certificate serial number generation and parsing utilities.

Two implementations are provided:

  • SerialGenerator — stateful generator that maintains an internal

    mapping between serial numbers and their source identifiers (int or str). Suitable when an authoritative in-process registry is acceptable.

  • SerialWithEncoding — stateless generator that encodes a short name

    prefix and a random UUID fragment into a single integer. No shared state; safe for concurrent use.

SOLID compliance

SRPEach class has a single reason to change.

SerialGenerator → manages serial ↔ id mapping. SerialWithEncoding → encodes/decodes name + random bits. _PrefixRegistry → owns the CertType ↔ hex-prefix mapping.

OCPNew CertType variants require only a new entry in _PrefixRegistry;

no existing logic changes.

LSPBoth generators expose compatible generate / parse signatures,

so they can be swapped behind an ISerialGenerator Protocol.

ISP : ISerialGenerator declares only the two methods callers actually need. DIP : CertificateFactory (and other consumers) depend on ISerialGenerator,

not on concrete classes.

class tiny_ca.utils.serial_generator.ISerialGenerator(*args, **kwargs)[source]

Bases: Protocol

Minimal contract for serial-number generators.

Any class that exposes generate and parse with compatible signatures satisfies this Protocol and can be injected wherever certificate serial numbers are needed.

__init__(*args, **kwargs)
static generate(name, serial_type)[source]

Generate a unique serial number for the given name and certificate type.

Parameters:
  • name (str) – Human-readable identifier (e.g. CN or service name).

  • serial_type (CertType) – Category of the certificate (CA, USER, SERVICE, …).

Returns:

A non-negative integer suitable for use as an X.509 serial number.

Return type:

int

static parse(serial)[source]

Decode a previously generated serial number.

Parameters:

serial (int) – Integer serial number produced by generate.

Returns:

(cert_type, name) recovered from the serial.

Return type:

tuple[CertType, str]

class tiny_ca.utils.serial_generator.SerialGenerator[source]

Bases: object

Stateful serial-number generator with a bidirectional id ↔ serial registry.

Serial number layout (64-bit integer)

[ 16-bit prefix ][ 48-bit data ]
  • prefix — 2-byte ASCII code derived from CertType (see _PrefixRegistry).

  • data — for int inputs: the value itself (truncated to 48 bits);

    for str inputs: an auto-incrementing counter per type.

The mapping between serial numbers and their original identifiers is kept in instance-level dictionaries, making this class unsuitable for concurrent multi-process use without external synchronisation.

_id_map

Maps serial → original identifier (int or str).

Type:

dict[int, int | str]

_name_map

Maps "<cert_type_value>_<name>" → serial; populated for string ids.

Type:

dict[str, int]

_last_serial

Per-type auto-increment counter for string-based identifiers.

Type:

dict[CertType, int]

__init__()[source]
Return type:

None

generate(id_value, serial_type)[source]

Generate a serial number for id_value and register the mapping.

For integer inputs the value is embedded directly into the data field (truncated to 48 bits). For string inputs a per-type auto-increment counter is used and the original string is stored in _id_map for later retrieval by parse().

Parameters:
  • id_value (int | str) – Source identifier. Integers are encoded directly; strings trigger the auto-increment path.

  • serial_type (CertType) – Certificate category; determines the 2-byte prefix.

Returns:

64-bit serial number: (prefix << 48) | data.

Return type:

int

Raises:

KeyError – If serial_type has no registered prefix.

get_serial_by_name(name, serial_type)[source]

Look up the serial number previously assigned to a string identifier.

Parameters:
  • name (str) – Original string identifier passed to generate().

  • serial_type (CertType) – Certificate type under which the name was registered.

Returns:

Registered serial number, or None if not found.

Return type:

int | None

parse(serial)[source]

Decode a serial number back to its type and original identifier.

For CertType.USER the numeric data portion is returned directly. For all other types the original identifier is looked up from the internal registry; None is returned if not found.

Parameters:

serial (int) – 64-bit serial number previously produced by generate().

Returns:

(cert_type, original_id). Both members may be None if the serial was not generated by this instance.

Return type:

tuple[CertType | None, int | str | None]

class tiny_ca.utils.serial_generator.SerialWithEncoding[source]

Bases: object

Stateless serial-number generator that encodes a short name prefix and a UUID-derived random fragment into a single integer.

Serial number layout

[ 16-bit prefix ][ 80-bit encoded name ][ 64-bit random ]

Total width: 160 bits (well within Python’s arbitrary-precision int; X.509 allows up to 20 bytes / 160 bits per RFC 5280 §4.1.2.2).

  • prefix — 2-byte ASCII code from _PrefixRegistry.

  • encoded name — up to 4 ASCII characters packed into 32 bits

    (little-endian byte order, zero-padded).

  • random — lower 64 bits of a fresh uuid.uuid4() ensuring

    global uniqueness without shared state.

Because no mutable state is kept, this class is safe to use from multiple threads or processes simultaneously.

Class Attributes

RANDOM_BITSint

Number of bits reserved for the random (UUID) portion. Default: 64.

NAME_BITSint

Number of bits reserved for the encoded name portion. Default: 32 (4 bytes × 8 bits).

MAX_NAME_LENGTHint

Maximum number of ASCII characters that can be encoded. Default: 4.

MAX_NAME_LENGTH: int = 10

Maximum number of characters accepted by _encode_name().

NAME_BITS: int = 80

Bit-width of the encoded-name segment (10 ASCII chars × 8 bits).

RANDOM_BITS: int = 64

Bit-width of the random (UUID) segment.

classmethod generate(name, serial_type)[source]

Generate a globally unique serial number for name and serial_type.

Only the first MAX_NAME_LENGTH characters of name are encoded; uniqueness is guaranteed by the UUID random segment, not by the name.

Parameters:
  • name (str) – Human-readable identifier. Only the first 4 ASCII characters are embedded; the remainder is ignored (not hashed or truncated with loss).

  • serial_type (CertType) – Certificate category; determines the 2-byte prefix.

Returns:

Non-negative integer serial suitable for X.509 certificates.

Return type:

int

Raises:

KeyError – If serial_type has no registered prefix.

Examples

>>> serial = SerialWithEncoding.generate("nginx", CertType.SERVICE)
>>> cert_type, name = SerialWithEncoding.parse(serial)
>>> assert cert_type == CertType.SERVICE
>>> assert name == "ngin"  # only first 4 chars are stored
classmethod parse(serial)[source]

Decode a serial number produced by generate().

Parameters:

serial (int) – Integer serial number to decode.

Returns:

(cert_type, name_prefix) where name_prefix is the up-to-4-char string recovered from the encoded-name segment. cert_type is None if the prefix is unrecognised.

Return type:

tuple[CertType | None, str]

Examples

>>> serial = SerialWithEncoding.generate("ca-root", CertType.CA)
>>> cert_type, name = SerialWithEncoding.parse(serial)
>>> assert cert_type == CertType.CA
>>> assert name == "ca-r"