The group component is the one that most clearly demonstrates the provider architecture's
reach, because through the TLS-GROUP capability a provider introduces a code point
that appears on the wire. Requirements F3 and F4 are addressed here.
Supplying a group means discharging two separate obligations, and the failure to distinguish them is the most common error in this area:
TLS-GROUP capability entry, so the group can
be negotiated.A provider that discharges only the first supplies a group that works when fetched directly and never appears in a handshake. The symptom is a connection that negotiates some other group with no error at all, which looks like a preference problem and is really a missing capability entry (§5.5, rule 4).
Key management owns the key object: its creation, its parameters, its import and export, and
its destruction. For an EC group the design must decide how the curve is identified, and it
follows the arrangement OpenSSL itself uses — a single EC implementation
parameterised by curve name, rather than one implementation per curve. §4.3.1 showed three
curves mapping to one algorithm in the built-in list.
static const OSSL_DISPATCH tlsext_ec_keymgmt_functions[] = {
{ OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))ec_newkey },
{ OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))ec_freekey },
{ OSSL_FUNC_KEYMGMT_GEN_INIT, (void (*)(void))ec_gen_init },
{ OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS, (void (*)(void))ec_gen_set_params },
{ OSSL_FUNC_KEYMGMT_GEN, (void (*)(void))ec_gen },
{ OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (void (*)(void))ec_gen_cleanup },
{ OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))ec_has },
{ OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))ec_match },
{ OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))ec_import },
{ OSSL_FUNC_KEYMGMT_EXPORT, (void (*)(void))ec_export },
{ OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))ec_import_types },
{ OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (void (*)(void))ec_export_types },
{ 0, NULL }
};
Import and export carry particular weight in TLS. The key_share extension
carries a public share as an opaque octet string, and the TLS stack obtains that string by
exporting the public part of a generated key, then reconstructs the peer's key by importing the
received string. The encoding the component uses for export is therefore the encoding that
appears on the wire, and it must match what the peer expects exactly — a design that exports an
encoding differing in point format or in leading-zero handling produces handshakes that fail
against other implementations while succeeding against itself.
static const OSSL_DISPATCH tlsext_ec_keyexch_functions[] = {
{ OSSL_FUNC_KEYEXCH_NEWCTX, (void (*)(void))ecdh_newctx },
{ OSSL_FUNC_KEYEXCH_FREECTX, (void (*)(void))ecdh_freectx },
{ OSSL_FUNC_KEYEXCH_DUPCTX, (void (*)(void))ecdh_dupctx },
{ OSSL_FUNC_KEYEXCH_INIT, (void (*)(void))ecdh_init },
{ OSSL_FUNC_KEYEXCH_SET_PEER, (void (*)(void))ecdh_set_peer },
{ OSSL_FUNC_KEYEXCH_DERIVE, (void (*)(void))ecdh_derive },
{ OSSL_FUNC_KEYEXCH_SET_CTX_PARAMS, (void (*)(void))ecdh_set_ctx_params },
{ 0, NULL }
};
The derive operation follows the standard two-call convention: called with a null output buffer it reports the required length, and called with a buffer it produces the shared secret. Both calls must agree, and the length must be the fixed field size for the curve rather than the length of the particular secret computed, since a secret with leading zero bytes must not be shortened. Variable-length shared secrets have produced real interoperability failures and, in some protocols, side channels.
This is the security-critical operation of the component and it is specified separately for
emphasis. SET_PEER receives a share that arrived over the network from an
unauthenticated party at that point in the handshake. Before it is used it must be validated.
Design, security-critical. The component shall reject a peer share that is not a valid encoding for the curve, that does not represent a point on the curve, that represents the point at infinity, or that lies in a small subgroup where the curve's cofactor makes that possible. Validation occurs before any operation involving the private key. A component that omits these checks permits invalid-curve attacks, which recover the private key across a sequence of handshakes and leave no trace distinguishable from ordinary connection failures.
For curves whose design makes some of these checks unnecessary — where the encoding guarantees a valid point and the cofactor is handled by the derivation itself — the component should document which checks are subsumed by the construction rather than silently omitting them. A reviewer cannot distinguish "unnecessary" from "forgotten" by reading code that does neither.
The second obligation is advertisement. The component answers TLS-GROUP by
invoking the supplied callback once per group with a parameter array of the shape verified in
§4.3.1:
static int tlsext_group_capability(OSSL_CALLBACK *cb, void *arg)
{
static const OSSL_PARAM group[] = {
OSSL_PARAM_utf8_string(OSSL_CAPABILITY_TLS_GROUP_NAME,
"tlsext256", sizeof("tlsext256")),
OSSL_PARAM_utf8_string(OSSL_CAPABILITY_TLS_GROUP_NAME_INTERNAL,
"TLSEXT-P256", sizeof("TLSEXT-P256")),
OSSL_PARAM_utf8_string(OSSL_CAPABILITY_TLS_GROUP_ALG,
"EC", sizeof("EC")),
OSSL_PARAM_uint(OSSL_CAPABILITY_TLS_GROUP_ID, &group_id),
OSSL_PARAM_uint(OSSL_CAPABILITY_TLS_GROUP_SECURITY_BITS, &secbits),
OSSL_PARAM_int(OSSL_CAPABILITY_TLS_GROUP_MIN_TLS, &min_tls),
OSSL_PARAM_int(OSSL_CAPABILITY_TLS_GROUP_MAX_TLS, &max_tls),
OSSL_PARAM_END
};
return cb(group, arg);
}
and routes the capability request from the provider's dispatch table:
static int tlsext_get_capabilities(void *provctx, const char *capability,
OSSL_CALLBACK *cb, void *arg)
{
if (OPENSSL_strcasecmp(capability, "TLS-GROUP") == 0)
return tlsext_group_capability(cb, arg);
return 0;
}
The group identifier is a number on the wire, drawn from an IANA registry. A design has three options, with different consequences. A registered code point is correct for anything intended to interoperate publicly and requires the IANA process. A code point from the private-use range is correct for a closed deployment where both endpoints are under one administration. An arbitrary unregistered value is incorrect in all cases and will eventually collide with an allocation, producing a failure that is extremely difficult to diagnose because both peers believe they agreed.
The minimum and maximum TLS version parameters should confine an experimental group to TLS 1.3, preventing its offer in protocol versions whose key-exchange structure differs.
The group component discharges two obligations: implementation through key management and key exchange, and advertisement through the capability mechanism. Its export encoding is the wire encoding; its derive length must be the fixed field size; and its peer-share validation is the component's security-critical operation. The code point must come from a registry or from the private-use range, never from neither.