TLS Support

Configuration

setTlsConfiguration(label, configuration) stores one immutable copy of a TlsConfiguration under an application-wide label. Connections resolve and retain that copy when connect() starts, so replacement or clearing affects only later connections. reset() and clearTlsConfigurations() remove every registered entry.

Resolution first tries the exact label, then removes the final slash-delimited segment until it reaches the empty global-default label. For example, http/client/internal tries http/client/internal, http/client, http, and "". One complete entry is selected; fields are never merged between a child and its parent. An unresolved label or a selected entry without the policy required by a connection role fails synchronously before the one-shot connection is consumed.

Labels contain at most 16 non-empty slash-separated segments and 255 bytes. Segments use lowercase ASCII letters, digits, and interior hyphens. The registry contains at most 256 exact entries. hasTlsConfiguration() tests only the exact label, while resolveTlsConfiguration() returns the requested label, matched label, and immutable entry as one coherent result.

TlsConfiguration stores an optional explicit-anchor X509ServerCertificatePolicy and an optional immutable shared TlsServerIdentity. Copies and resolved snapshots share the identity without copying its move-only private key. Protected client identities remain deferred.

TlsConfigurationParser turns one ELCL section-list entry into a labeled TlsConfigurationEntry without accessing the application singleton. See Configuring TLS Profiles for the configuration schema, relative-file behavior, and registration workflow.

Erbsland Core does not automatically read environment variables or command-line options for this interface. The application decides how enterprise configuration is authenticated, parsed, and applied.

Server Identities

TlsServerIdentity combines a leaf-first ordered certificate chain with its move-only signing key. Construction rejects empty values, a leaf/key mismatch, or a key that supports no TLS 1.3 CertificateVerify scheme. Ed25519 points compare exactly, P-256/P-384 points compare after affine normalization so compressed certificate keys are accepted, and RSA matching includes modulus, exponent, key family, and PSS restrictions.

selectSignatureScheme() examines ClientHello schemes in peer order and returns the first compatible value. TlsConfiguration stores an optional identity through immutable shared ownership. Configuration copies and application snapshots therefore remain copyable without copying private key material, and an existing snapshot retains its identity after a later configuration is replaced or cleared.

Hardware-backed keys, multiple or SNI-selected identities, and the TLS server protocol are outside this interface.

TLS 1.3 Record Protection

The record-protection API implements the authenticated TLS 1.3 record transformations from RFC 8446. A TlsRecordEncryptor and TlsRecordDecryptor each retain one independent traffic-secret generation and sequence number. They operate on complete records so the exact wire header used as authenticated data cannot diverge from the header returned to or received from the network layer.

Cipher Suites and Traffic Secrets

TlsCipherSuite represents exactly TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, and TLS_CHACHA20_POLY1305_SHA256. It maps each RFC 8446 wire value to its registry name, HKDF hash, and symmetric construction. All supported constructions use a 96-bit nonce and a 128-bit authentication tag.

Create a move-only TlsTrafficSecret from exact SHA-256- or SHA-384-sized bytes. The consuming factory erases its input allocation after copying the secret into ProtectedByteBlock. There is intentionally no plaintext accessor. Record state resolves the secret only inside an erased callback while deriving a key, IV, or next-generation secret.

One Direction per Object

Construct a separate TlsRecordEncryptor and TlsRecordDecryptor for each protocol direction. protect() returns one complete header-plus-ciphertext record. unprotect() accepts exactly one complete record and returns a move-only TlsRecordPlaintext only after authentication and inner-plaintext validation succeed.

Callers choose the inner content type and an explicit padding length. This API deliberately defines no automatic padding policy. Empty application data is supported; empty Alert and Handshake content is rejected. Stream buffering, alert serialization, the record_size_limit extension, automatic padding strategies, and other optional record extensions remain in the network protocol layer.

Failures and Terminal State

TlsRecordErrorCategory distinguishes decode errors, unexpected messages, authentication failure, record overflow, and exhausted key usage. This lets a network implementation select an alert without parsing diagnostic text.

A local protect() parameter error occurs before key or sequence use and leaves the encryptor usable. Any failure after protection begins erases the sending generation so an uncertain nonce cannot be reused. Every peer-record rejection or backend failure permanently erases the decryptor. Authentication failure therefore exposes no plaintext and cannot be followed by another attempt with the same receiving object.

Bounds and Key Usage

The implementation limits TLSInnerPlaintext to \(2^{14} + 1\) bytes and rejects an outer TLSCiphertext length above \(2^{14} + 256\) bytes. Supported records have the tighter construction bound of inner length plus the fixed 16-byte tag.

For AES-GCM, isKeyUpdateRequired() becomes true after \(2^{24}\) successful records. Protection and deprotection refuse record number 23,726,566, the floor of \(2^{24.5}\). For ChaCha20-Poly1305 the signal becomes true while one old-key record remains, and processing refuses before advancing a 64-bit sequence would wrap.

updateApplicationTrafficKeys() performs only the caller-directed cryptographic transition from RFC 8446 Sections 4.6.3 and 7.2. Message parsing, serialization, and transition timing remain handshake responsibilities. The method fully derives the next traffic secret, key, and IV before replacement, immediately erases the old generation, and resets sequence and record counters to zero.

RFC 8446 Specification Mapping

The implementation follows the defining operations in review order:

  • Section 7.1 encodes HkdfLabel as the requested network-order length, "tls13 "-prefixed label, and empty context before HKDF expansion.

  • Section 7.3 derives write_key and the 96-bit write_iv from Secret with the "key" and "iv" labels.

  • Section 5.3 encodes the 64-bit sequence in network byte order, left-pads it to 12 bytes, and XORs all 12 bytes with write_iv to form the nonce.

  • Sections 5.2 and 5.4 encode content, the nonzero inner type, and explicit zero padding as TLSInnerPlaintext.

  • Section 5.2 encodes outer type 23, legacy version 0x0303, and ciphertext length. Those exact five bytes are additional_data for AEAD.

  • Section 5.2 performs AEAD protection or deprotection through the symmetric API. Tentative decryption storage remains sensitive and guarded until tag verification succeeds.

  • Section 5.4 reverse-scans authenticated plaintext over zero padding and validates the recovered inner type before copying content into its returned sensitive allocation.

  • Section 5.5 checks record and sequence usage before processing and advances counters exactly once after success.

  • Sections 4.6.3 and 7.2 derive "traffic upd", construct a complete replacement generation, erase the old secret, key, and IV, then reset the sequence.

Validation

Focused tests include the RFC 8448 Section 3 AES-128-GCM application record and key derivation, plus pinned AES-256-GCM and ChaCha20-Poly1305 composition vectors independently generated with Node.js 24.4.1 and OpenSSL 3.5.0. Boundary tests cover sequences, content types, explicit padding, empty application data, maximum lengths, malformed framing, header and ciphertext tampering, terminal erasure, usage ceilings, and KeyUpdate. A bounded ASan/UBSan libFuzzer target seeds valid, malformed, oversized, and authentication-tampered records.

Specifications

Interface

class TlsCipherSuite

A supported TLS 1.3 cipher suite from RFC 8446 section 9.1.

The suite binds one AEAD construction to the hash used by HKDF and the handshake transcript. Only the three cipher suites defined by RFC 8446 are represented.

See: TLS Support

Public Types

enum Value

Supported RFC 8446 cipher-suite values.

Values:

enumerator Aes128GcmSha256

TLS_AES_128_GCM_SHA256.

enumerator Aes256GcmSha384

TLS_AES_256_GCM_SHA384.

enumerator ChaCha20Poly1305Sha256

TLS_CHACHA20_POLY1305_SHA256.

Public Functions

constexpr TlsCipherSuite() noexcept = default

Create the mandatory-to-implement TLS_AES_128_GCM_SHA256 suite.

inline constexpr TlsCipherSuite(const Value value) noexcept

Create a suite from a supported raw value.

inline constexpr uint16_t toRawValue() const noexcept

Get the exact unsigned 16-bit TLS wire value.

HashAlgorithm hashAlgorithm() const noexcept

Get the suite’s HKDF and transcript hash.

SymmetricEncryptionType encryptionType() const noexcept

Get the suite’s AEAD construction.

text::String toString() const

Convert the suite to its exact TLS registry name.

Public Static Functions

static std::optional<TlsCipherSuite> fromRawValue(uint16_t value) noexcept

Parse one supported unsigned 16-bit TLS wire value.

static TlsCipherSuite fromRawValueOrThrow(uint16_t value)

Parse one supported unsigned 16-bit TLS wire value.

Throws:

err::ParseError – If the value is not supported.

static std::span<const TlsCipherSuite> all() noexcept

Get all supported TLS 1.3 cipher suites in preference order.

class TlsConfiguration

Application-owned material and policies used to establish TLS connections.

Registered configurations are copied into immutable snapshots. A server identity is retained through immutable shared ownership so copies remain cheap and cannot copy its move-only private key.

Public Functions

TlsConfiguration() = default

Create an empty TLS configuration.

inline explicit TlsConfiguration(X509ServerCertificatePolicy serverCertificatePolicy) noexcept

Create a TLS configuration with a server-certificate authentication policy.

Parameters:

serverCertificatePolicy – The policy copied into this configuration.

inline bool hasServerCertificatePolicy() const noexcept

Test whether this configuration can authenticate TLS server certificates.

inline const std::optional<X509ServerCertificatePolicy> &serverCertificatePolicy() const noexcept

Get the server-certificate authentication policy.

Returns:

The policy, or no value if it was not configured.

inline void setServerCertificatePolicy(X509ServerCertificatePolicy policy) noexcept

Set the server-certificate authentication policy.

Parameters:

policy – The replacement policy.

inline void clearServerCertificatePolicy() noexcept

Clear the server-certificate authentication policy.

inline bool hasServerIdentity() const noexcept

Test whether this configuration contains a TLS server identity.

inline const TlsServerIdentityConstPtr &serverIdentity() const noexcept

Get the immutable shared TLS server identity, or a null pointer when absent.

inline void setServerIdentity(TlsServerIdentity identity)

Replace the TLS server identity and transfer ownership of its move-only key.

Parameters:

identity – A completely validated server identity.

inline void clearServerIdentity() noexcept

Clear the TLS server identity from this configuration snapshot source.

class TlsConfigurationEntry

One labeled TLS configuration parsed from an external representation.

See: Configuring TLS Profiles

Public Functions

inline const text::String &label() const noexcept

Get the registry label.

inline const TlsConfiguration &configuration() const noexcept

Get the parsed TLS configuration.

inline TlsConfiguration takeConfiguration() noexcept

Move the parsed TLS configuration out of this entry.

class TlsConfigurationParser

Parse and validate one labeled TLS configuration from an ELCL section.

See: Configuring TLS Profiles

Public Functions

TlsConfigurationEntry parse(const conf::ValuePtr &sectionValue) const

Validate and parse one TLS configuration section-list entry.

Parameters:

sectionValue – The selected ELCL section containing one labeled TLS configuration.

Throws:
  • conf::ConfError – If validation or referenced-material loading fails.

  • err::ParameterError – If sectionValue is null.

Returns:

The validated label and complete TLS configuration.

Public Static Functions

static const conf::vr::RulesPtr &validationRules()

Access the compiled validation rules shared by all parser instances.

Returns:

The immutable compiled rules for one TLS configuration entry.

static conf::Integer version()

Get the current version of the TLS configuration format.

class TlsConfigurationResolution

One coherently resolved immutable TLS configuration.

Public Functions

TlsConfigurationResolution() = default

Create an empty resolution.

inline TlsConfigurationResolution(text::String requestedLabel, text::String matchedLabel, TlsConfigurationConstPtr configuration) noexcept

Create a successful resolution.

Parameters:
  • requestedLabel – The label originally requested.

  • matchedLabel – The exact registry entry selected by fallback.

  • configuration – The immutable configuration snapshot.

inline const text::String &requestedLabel() const noexcept

Get the label originally requested by the caller.

inline const text::String &matchedLabel() const noexcept

Get the exact registered label selected by fallback resolution.

inline const TlsConfigurationConstPtr &configuration() const noexcept

Get the immutable configuration snapshot.

class TlsServerIdentity

One immutable-after-construction TLS server certificate chain and protected signing key.

Matching follows RFC 8410 for Ed25519, RFC 5480 for P-256, and RFC 3279/RFC 4055 for RSA. Signature-scheme selection follows RFC 8446 section 4.2.3 in the peer-provided order and never permits certificate-only PKCS#1 schemes. The identity owns the move-only key; configurations share it only through a pointer to const.

See: TLS Support

Public Functions

TlsServerIdentity(X509CertificateBundle certificateChain, SigningPrivateKey signingKey)

Create a validated server identity.

Parameters:
  • certificateChain – Leaf-first ordered certificate chain.

  • signingKey – Private key matching the leaf public key.

Throws:

err::ParameterError – If either value is empty, the leaf and key differ, or no TLS 1.3 scheme is usable.

std::optional<TlsSignatureScheme> selectSignatureScheme(const util::List<TlsSignatureScheme> &offeredSchemes) const noexcept

Select the first peer-offered scheme compatible with this identity.

Parameters:

offeredSchemes – ClientHello signature_algorithms values in wire order.

Returns:

The first compatible TLS 1.3 CertificateVerify scheme, or no value.

inline const X509CertificateBundle &certificateChain() const noexcept

Get the leaf-first certificate chain.

inline const SigningPrivateKey &signingKey() const noexcept

Get the protected signing key.

class TlsSignatureScheme

A supported TLS 1.3 signature scheme.

Code points and their permitted uses follow RFC 8446 section 4.2.3. The PKCS#1 v1.5 schemes are retained only for signatures appearing in certificates and are never allowed for TLS 1.3 CertificateVerify messages. All values describe public verification inputs; this type creates and retains no secret state requiring erasure.

See: X.509 Certificates

Public Types

enum Value

The supported RFC 8446 SignatureScheme values.

Values:

enumerator RsaPkcs1Sha256

Certificate-only RSASSA-PKCS1-v1_5 with SHA-256.

enumerator RsaPkcs1Sha384

Certificate-only RSASSA-PKCS1-v1_5 with SHA-384.

enumerator EcdsaSecp256r1Sha256

ECDSA with P-256 and SHA-256.

enumerator EcdsaSecp384r1Sha384

ECDSA with P-384 and SHA-384.

enumerator RsaPssRsaeSha256

RSASSA-PSS/SHA-256 with an rsaEncryption public key.

enumerator RsaPssRsaeSha384

RSASSA-PSS/SHA-384 with an rsaEncryption public key.

enumerator Ed25519

Pure Ed25519.

enumerator RsaPssPssSha256

RSASSA-PSS/SHA-256 with an id-RSASSA-PSS public key.

enumerator RsaPssPssSha384

RSASSA-PSS/SHA-384 with an id-RSASSA-PSS public key.

Public Functions

constexpr TlsSignatureScheme() noexcept = default

Create the mandatory-to-implement RSA-PSS-RSAE/SHA-256 scheme.

inline constexpr TlsSignatureScheme(const Value value) noexcept

Create a scheme from its supported raw value.

Parameters:

value – The supported raw scheme value.

bool isAllowedForCertificateVerify() const noexcept

Test whether this scheme may verify a TLS 1.3 CertificateVerify signature.

bool isAllowedForCertificateSignature() const noexcept

Test whether this scheme may describe a signature appearing in an X.509 certificate.

inline constexpr uint16_t toRawValue() const noexcept

Get the exact unsigned 16-bit TLS wire value.

text::String toString() const

Convert the scheme to its stable TLS registry name.

X509AlgorithmIdentifier signatureAlgorithmIdentifier() const

Map this scheme to the complete X.509 signature AlgorithmIdentifier.

Public Static Functions

static std::optional<TlsSignatureScheme> fromRawValue(uint16_t value) noexcept

Parse one supported unsigned 16-bit TLS wire value.

Parameters:

value – The raw wire value.

Returns:

The matching scheme, or no value when unsupported.

static TlsSignatureScheme fromRawValueOrThrow(uint16_t value)

Parse one supported unsigned 16-bit TLS wire value.

Parameters:

value – The raw wire value.

Throws:

err::ParseError – If the value is not supported.

Returns:

The matching scheme.

enum class erbsland::cryptology::TlsRecordContentType : uint8_t

An authenticated TLS 1.3 inner-plaintext content type from RFC 8446 sections 5.1 and 5.2.

Values:

enumerator Alert

One TLS alert message.

enumerator Handshake

One or more TLS handshake-message fragments.

enumerator ApplicationData

Opaque application data.

class TlsRecordDecryptor

A move-only TLS 1.3 record decryptor implementing RFC 8446 sections 5.2&#8212;5.5 and 7.2&#8212;7.3.

It authenticates one complete TLSCiphertext record before returning sensitive plaintext. Any malformed record, authentication failure, overflow, or exhausted key makes this state terminal and erases all retained secrets.

See: TLS Support

Public Functions

TlsRecordDecryptor() noexcept

Create an empty record-decryptor placeholder.

TlsRecordDecryptor(TlsCipherSuite suite, TlsTrafficSecret &&trafficSecret)

Create RFC 8446 Sections 5.3 and 7.3 receiving state by consuming a matching traffic secret.

Throws:
~TlsRecordDecryptor()

Securely erase record-protection state.

TlsRecordPlaintext unprotect(mem::ConstByteSpan record)

Authenticate and deprotect one exact complete TLS 1.3 record under RFC 8446 Sections 5.2&#8212;5.5.

Parameters:

record – The complete five-byte header and encrypted body.

Throws:
  • TlsRecordError – For malformed, unauthenticated, overflowing, or exhausted input.

  • CryptologyError – If the cryptographic backend fails independently of peer authentication.

Returns:

Authenticated sensitive content and its inner content type.

void updateApplicationTrafficKeys()

Derive and install the next application traffic-secret generation under RFC 8446 Sections 4.6.3 and 7.2.

The caller must invoke this only after authenticating the RFC 8446 section 4.6.3 KeyUpdate message.

void secureErase() noexcept

Securely erase all retained secret state and restore the empty state.

inline bool isEmpty() const noexcept

Test whether no record-protection state is retained.

bool isKeyUpdateRequired() const noexcept

Test the RFC 8446 Section 5.5 signal that the peer should update before more application data.

TlsCipherSuite suite() const

Get the configured cipher suite.

Throws:

err::LogicError – If this decryptor is empty.

class TlsRecordEncryptor

A move-only TLS 1.3 record encryptor implementing RFC 8446 sections 5.2&#8212;5.5 and 7.2&#8212;7.3.

It constructs complete TLSCiphertext records, maintains one sending sequence, enforces key-usage limits, and keeps traffic secrets protected outside scoped derivation. Padding length is explicit; no padding policy is implied.

See: TLS Support

Public Functions

TlsRecordEncryptor() noexcept

Create an empty record-encryptor placeholder.

TlsRecordEncryptor(TlsCipherSuite suite, TlsTrafficSecret &&trafficSecret)

Create RFC 8446 Sections 5.3 and 7.3 sending state by consuming a matching traffic secret.

Throws:
~TlsRecordEncryptor()

Securely erase record-protection state.

auto protect(TlsRecordContentType type, mem::ConstByteSpan content, unit::ByteLength paddingLength = unit::ByteLength::zero()) -> mem::ByteBlock

Protect one complete TLS 1.3 record under RFC 8446 Sections 5.2&#8212;5.5.

Parameters:
  • type – The authenticated inner content type.

  • content – The exact content bytes, at most 2^14 bytes.

  • paddingLength – The explicit number of zero padding octets.

Throws:
Returns:

A complete TLSCiphertext header and encrypted body.

void updateApplicationTrafficKeys()

Derive and install the next application traffic-secret generation under RFC 8446 Sections 4.6.3 and 7.2.

The caller must invoke this only at the RFC 8446 section 4.6.3 protocol transition.

void secureErase() noexcept

Securely erase all retained secret state and restore the empty state.

inline bool isEmpty() const noexcept

Test whether no record-protection state is retained.

bool isKeyUpdateRequired() const noexcept

Test the RFC 8446 Section 5.5 signal for sending KeyUpdate before more application data.

TlsCipherSuite suite() const

Get the configured cipher suite.

Throws:

err::LogicError – If this encryptor is empty.

class TlsRecordError : public erbsland::err::RuntimeError

A categorized failure from TLS 1.3 record protection or deprotection.

Categories preserve the RFC 8446 alert distinction required by sections 5 and 6 without coupling cryptology to network alert serialization.

See: TLS Support

Public Functions

TlsRecordError(TlsRecordErrorCategory category, text::String reason) noexcept

Create a categorized record error.

inline TlsRecordErrorCategory category() const noexcept

Get the stable failure category.

enum class erbsland::cryptology::TlsRecordErrorCategory : uint8_t

A stable TLS 1.3 record-protection failure category.

Values:

enumerator DecodeError

The complete record framing or encoded length is malformed.

enumerator UnexpectedMessage

The outer or authenticated inner record semantics are invalid.

enumerator BadRecordMac

AEAD authentication or decryption failed.

enumerator RecordOverflow

A ciphertext or authenticated plaintext exceeds its RFC bound.

enumerator KeyUsageExhausted

The sequence number or cipher-specific record limit is exhausted.

class TlsRecordPlaintext

Authenticated content recovered from one TLS 1.3 record.

The content type is the final nonzero TLSInnerPlaintext octet from RFC 8446 section 5.2. Content is retained in sensitive storage and is never constructed until AEAD authentication succeeds.

See: TLS Support

Public Functions

TlsRecordPlaintext(TlsRecordContentType type, mem::ByteBlock content) noexcept

Create authenticated record content.

~TlsRecordPlaintext()

Securely erase retained plaintext.

void secureErase() noexcept

Securely erase content and restore an empty payload.

mem::ByteBlock takeContent() noexcept

Transfer the authenticated content to the caller.

inline bool isEmpty() const noexcept

Test whether the authenticated content is empty.

inline TlsRecordContentType type() const noexcept

Get the authenticated inner content type.

inline const mem::ByteBlock &content() const noexcept

Borrow the authenticated content.

class TlsTrafficSecret

A move-only TLS 1.3 traffic secret retained in application-protected storage.

The secret length is the selected SHA-256 or SHA-384 digest length required by RFC 8446 sections 7.1&#8212;7.3. There is no public plaintext accessor; record protection resolves the secret only for scoped derivation and erasure.

See: TLS Support

Public Functions

TlsTrafficSecret() noexcept = default

Create an empty traffic-secret placeholder.

~TlsTrafficSecret()

Securely erase the protected envelope.

void secureErase() noexcept

Securely erase the protected envelope and restore the empty state.

inline bool isEmpty() const noexcept

Test whether no traffic secret is stored.

inline HashAlgorithm hashAlgorithm() const noexcept

Get the traffic-secret hash algorithm.

inline unit::ByteLength byteLength() const noexcept

Get the plaintext secret length without resolving it.

Public Static Functions

static TlsTrafficSecret fromBytes(HashAlgorithm algorithm, mem::ConstByteSpan data)

Protect a copy of RFC 8446 Sections 7.1&#8212;7.3 exact digest-sized traffic-secret bytes.

The caller retains ownership and erasure responsibility for the borrowed bytes.

Throws:

err::ParameterError – If the algorithm or length is unsupported.

static TlsTrafficSecret fromBytes(HashAlgorithm algorithm, mem::ByteBlock &&data)

Protect and then erase an owning RFC 8446 Sections 7.1&#8212;7.3 exact digest-sized traffic-secret block.

Throws:

err::ParameterError – If the algorithm or length is unsupported.