The hash component is the smallest of the three in interface and the largest in consequence. It is used by the transcript, by every step of the key schedule, and by the Finished computation, and its output length propagates into every secret the connection holds. Requirement F2 is addressed here; the key-schedule consequences are developed in Chapter 15.
static const OSSL_DISPATCH tlsext_digest_functions[] = {
{ OSSL_FUNC_DIGEST_NEWCTX, (void (*)(void))digest_newctx },
{ OSSL_FUNC_DIGEST_FREECTX, (void (*)(void))digest_freectx },
{ OSSL_FUNC_DIGEST_DUPCTX, (void (*)(void))digest_dupctx },
{ OSSL_FUNC_DIGEST_INIT, (void (*)(void))digest_init },
{ OSSL_FUNC_DIGEST_UPDATE, (void (*)(void))digest_update },
{ OSSL_FUNC_DIGEST_FINAL, (void (*)(void))digest_final },
{ OSSL_FUNC_DIGEST_GET_PARAMS, (void (*)(void))digest_get_params },
{ OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (void (*)(void))digest_gettable },
{ 0, NULL }
};
This is the table the companion paper's provider implements, and it is reproduced here because the TLS use imposes requirements on two of its entries that a general-purpose digest does not face.
TLS computes the transcript hash repeatedly over a growing prefix of the handshake. The running context is never finalised, because more messages will follow; instead it is duplicated and the copy finalised whenever a digest of the transcript so far is needed — for the signature input, for the Finished computation, and for key-schedule steps that bind the transcript.
OSSL_FUNC_DIGEST_DUPCTX is therefore not optional for a TLS handshake hash, and
it must produce a genuinely independent context. The failure mode of a shallow implementation is
instructive: if the duplicate shares the source's internal buffer, finalising the copy corrupts
the original, and the handshake fails at the Finished check with a message that suggests a MAC
error rather than a context-management bug.
Design. The duplication entry allocates a new context and copies the entire algorithm state by value, including any partial input block and the byte counter. Where the component delegates to another implementation (§7.4), duplication must also duplicate the delegate's context — copying the delegate handle is precisely the shallow-copy error described above.
The size parameter reports the digest length in bytes. In a general-purpose
setting a caller that ignores it merely wastes buffer space; in TLS the value determines the
width of the key schedule (§2.3), so an incorrect answer produces secrets of the wrong length
and a handshake that fails after several successful-looking steps.
| Parameter | Meaning | Consumed by |
|---|---|---|
size | Digest output length in bytes | Key schedule; secret and Finished sizing |
blocksize | Internal block size | HMAC construction inside HKDF |
The block size matters because HKDF is built on HMAC, and HMAC's key padding and inner/outer construction are defined in terms of the hash's block size. A component that reports an output length but not a block size may work under direct digest use and fail inside HKDF.
The transcript is supplied incrementally, message by message, in arbitrary sizes determined by the handshake rather than by the hash's block boundaries. The update entry point must therefore buffer partial blocks correctly and produce results identical to a single-shot computation over the concatenation. This is ordinary for a hash implementation, but it is the property most worth testing exhaustively, because a boundary error appears only for particular input lengths. Chapter 19 proposes testing every length across at least two block boundaries rather than a handful of convenient sizes.
The OSSL_ALGORITHM names field is a colon-separated list, so a component may
answer to several names:
static const OSSL_ALGORITHM tlsext_digests[] = {
{ "TLSEXT-HASH256:TLSEXT-SHA256", "provider=tlsext",
tlsext_digest_functions, "Experimental TLS handshake hash" },
{ NULL, NULL, NULL, NULL }
};
Naming deserves more care than it usually receives. A component that claims a standard name —
SHA2-256 — competes with the default provider for every fetch in the context,
including fetches made by code with no relation to TLS. A component that claims only a
distinctive name is inert until something asks for it by that name, which is the safer default
and the one this design adopts. Chapter 14 treats the policy consequences.
§2.2 observed that the handshake hash is selected after some of the data it must cover has already been exchanged. Implementations buffer the early handshake messages and hash them once the cipher suite fixes the algorithm.
The consequence for the component is that its first update may be presented with
the entire accumulated prefix in one call, and subsequent updates with individual messages. The
component must handle both without special-casing, which follows from §9.4 but is worth stating
because a component tested only with uniform chunk sizes may not have exercised the pattern.
The hash component must duplicate contexts deeply, report both output and block size, stream correctly at arbitrary boundaries, and avoid claiming standard names unless substitution is intended. Its consequences reach further than its interface suggests: Chapter 15 shows that changing this one algorithm changes the width of every secret in the connection.