9. Design of the Hash Component

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.

9.1 Dispatch table

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.

9.2 The duplication requirement

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.

9.3 Output length as a first-class parameter

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.

ParameterMeaningConsumed by
sizeDigest output length in bytesKey schedule; secret and Finished sizing
blocksizeInternal block sizeHMAC 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.

9.4 Streaming behaviour

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.

9.5 Naming and aliasing

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.

9.6 The retrospective-use problem

§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.

9.7 Summary

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.