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
HkdfLabelas the requested network-order length,"tls13 "-prefixed label, and empty context before HKDF expansion.Section 7.3 derives
write_keyand the 96-bitwrite_ivfromSecretwith 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_ivto 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 areadditional_datafor 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
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.
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.
-
constexpr TlsCipherSuite() noexcept = default
-
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.
-
TlsConfiguration() = default
-
class TlsConfigurationEntry
One labeled TLS configuration parsed from an external representation.
Public Functions
-
inline const TlsConfiguration &configuration() const noexcept
Get the parsed TLS configuration.
-
inline TlsConfiguration takeConfiguration() noexcept
Move the parsed TLS configuration out of this entry.
-
inline const TlsConfiguration &configuration() const noexcept
-
class TlsConfigurationParser
Parse and validate one labeled TLS configuration from an ELCL section.
Public Functions
-
TlsConfigurationEntry parse(const conf::ValuePtr §ionValue) 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
sectionValueis 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.
-
TlsConfigurationEntry parse(const conf::ValuePtr §ionValue) const
-
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.
-
TlsConfigurationResolution() = default
-
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_algorithmsvalues in wire order.- Returns:
The first compatible TLS 1.3
CertificateVerifyscheme, 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.
-
TlsServerIdentity(X509CertificateBundle certificateChain, SigningPrivateKey signingKey)
-
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
CertificateVerifymessages. 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.
-
enumerator RsaPkcs1Sha256
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
CertificateVerifysignature.
-
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.
-
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 Value
-
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.
-
enumerator Alert
-
class TlsRecordDecryptor
A move-only TLS 1.3 record decryptor implementing RFC 8446 sections 5.2—5.5 and 7.2—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:
err::ParameterError – If the traffic secret does not match the suite hash.
CryptologyError – If key derivation or protected storage fails.
-
~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—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.
-
TlsRecordDecryptor() noexcept
-
class TlsRecordEncryptor
A move-only TLS 1.3 record encryptor implementing RFC 8446 sections 5.2—5.5 and 7.2—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:
err::ParameterError – If the traffic secret does not match the suite hash.
CryptologyError – If key derivation or protected storage fails.
-
~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—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:
err::ParameterError – If type or plaintext bounds are invalid.
TlsRecordError – If key usage is exhausted.
CryptologyError – If encryption fails.
- 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.
-
TlsRecordEncryptor() noexcept
-
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.
-
TlsRecordError(TlsRecordErrorCategory category, text::String reason) noexcept
-
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.
-
enumerator DecodeError
-
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.
-
inline bool isEmpty() const noexcept
Test whether the authenticated content is empty.
-
inline TlsRecordContentType type() const noexcept
Get the authenticated inner content type.
-
TlsRecordPlaintext(TlsRecordContentType type, mem::ByteBlock content) noexcept
-
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—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—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—7.3 exact digest-sized traffic-secret block.
- Throws:
err::ParameterError – If the algorithm or length is unsupported.
-
TlsTrafficSecret() noexcept = default