Appendix A. Provider Skeleton

The following is the specified skeleton of the provider described in Chapters 7–11. It is a specification expressed as code, not a tested artefact: it was not compiled, and it omits the algorithm bodies, which are the subject of the respective chapters. Error handling is shown where it carries design content and elided as /* ... */ where it does not.

A.1 Provider core

/*
 * tlsext provider -- core entry points.
 * Specification skeleton accompanying Chapter 7. Not compiled.
 */
#include <openssl/core.h>
#include <openssl/core_dispatch.h>
#include <openssl/core_names.h>
#include <openssl/params.h>
#include <openssl/evp.h>
#include <string.h>

typedef struct prov_ctx_st {
    const OSSL_CORE_HANDLE *handle;
    OSSL_FUNC_core_new_error_fn      *core_new_error;
    OSSL_FUNC_core_set_error_debug_fn *core_set_error_debug;
    OSSL_FUNC_core_vset_error_fn     *core_vset_error;
    OSSL_LIB_CTX *delegate_libctx;   /* private; see 7.4 */
    EVP_MD       *delegate_md;
} PROV_CTX;

/* 7.2: the incoming table is walked, never indexed. */
static PROV_CTX *prov_ctx_new(const OSSL_CORE_HANDLE *handle,
                              const OSSL_DISPATCH *in)
{
    PROV_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));

    if (ctx == NULL)
        return NULL;
    ctx->handle = handle;

    for (; in->function_id != 0; in++) {
        switch (in->function_id) {
        case OSSL_FUNC_CORE_NEW_ERROR:
            ctx->core_new_error = OSSL_FUNC_core_new_error(in);
            break;
        case OSSL_FUNC_CORE_SET_ERROR_DEBUG:
            ctx->core_set_error_debug = OSSL_FUNC_core_set_error_debug(in);
            break;
        case OSSL_FUNC_CORE_VSET_ERROR:
            ctx->core_vset_error = OSSL_FUNC_core_vset_error(in);
            break;
        default:
            break;                   /* unknown ids are ignored, not errors */
        }
    }

    /* 7.4: delegation uses a private context, never the caller's. */
    if ((ctx->delegate_libctx = OSSL_LIB_CTX_new()) == NULL)
        goto err;
    ctx->delegate_md = EVP_MD_fetch(ctx->delegate_libctx, "SHA2-256",
                                    "provider=default");
    if (ctx->delegate_md == NULL)
        goto err;
    return ctx;

 err:
    prov_ctx_free(ctx);
    return NULL;
}

static void tlsext_teardown(void *provctx)
{
    prov_ctx_free((PROV_CTX *)provctx);   /* N4: safe on NULL */
}

/* 7.3 */
static const OSSL_ALGORITHM *tlsext_query(void *provctx, int operation_id,
                                          int *no_cache)
{
    *no_cache = 0;
    switch (operation_id) {
    case OSSL_OP_DIGEST:    return tlsext_digests;
    case OSSL_OP_CIPHER:    return tlsext_ciphers;
    case OSSL_OP_KEYMGMT:   return tlsext_keymgmt;
    case OSSL_OP_KEYEXCH:   return tlsext_keyexch;
    case OSSL_OP_SIGNATURE: return tlsext_signatures;
    }
    return NULL;
}

/* 10.5: advertisement is a separate obligation from implementation. */
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);
    if (OPENSSL_strcasecmp(capability, "TLS-SIGALG") == 0)
        return tlsext_sigalg_capability(cb, arg);
    return 0;                        /* capability not supported */
}

static const OSSL_DISPATCH provider_dispatch[] = {
    { OSSL_FUNC_PROVIDER_TEARDOWN,         (void (*)(void))tlsext_teardown },
    { OSSL_FUNC_PROVIDER_QUERY_OPERATION,  (void (*)(void))tlsext_query },
    { OSSL_FUNC_PROVIDER_GET_CAPABILITIES,
                                    (void (*)(void))tlsext_get_capabilities },
    { OSSL_FUNC_PROVIDER_GETTABLE_PARAMS,  (void (*)(void))tlsext_gettable },
    { OSSL_FUNC_PROVIDER_GET_PARAMS,       (void (*)(void))tlsext_get_params },
    { 0, NULL }
};

int OSSL_provider_init(const OSSL_CORE_HANDLE *handle,
                       const OSSL_DISPATCH *in,
                       const OSSL_DISPATCH **out,
                       void **provctx)
{
    PROV_CTX *ctx;

    if ((ctx = prov_ctx_new(handle, in)) == NULL)
        return 0;
    *provctx = ctx;
    *out = provider_dispatch;
    return 1;
}

A.2 Algorithm tables

/* 9.5: distinctive names; the component is inert until asked for by name. */
static const OSSL_ALGORITHM tlsext_digests[] = {
    { "TLSEXT-HASH256", "provider=tlsext",
      tlsext_digest_functions, "Experimental TLS handshake hash" },
    { NULL, NULL, NULL, NULL }
};

static const OSSL_ALGORITHM tlsext_ciphers[] = {
    { "TLSEXT-AEAD", "provider=tlsext",
      tlsext_aead_functions, "Experimental AEAD for TLS record protection" },
    { NULL, NULL, NULL, NULL }
};

static const OSSL_ALGORITHM tlsext_keymgmt[] = {
    { "TLSEXT-P256", "provider=tlsext",
      tlsext_ec_keymgmt_functions, "Experimental EC group key management" },
    { NULL, NULL, NULL, NULL }
};

static const OSSL_ALGORITHM tlsext_keyexch[] = {
    { "TLSEXT-P256", "provider=tlsext",
      tlsext_ec_keyexch_functions, "Experimental EC key agreement" },
    { NULL, NULL, NULL, NULL }
};

A.3 Digest component, showing the duplication contract

/*
 * 9.2: duplication must be deep. Copying the delegate handle instead of
 * duplicating the delegate context is the shallow-copy error that appears
 * to work until one of the two contexts is updated.
 */
typedef struct digest_ctx_st {
    PROV_CTX    *provctx;
    EVP_MD_CTX  *delegate;      /* owned */
} DIGEST_CTX;

static void *digest_dupctx(void *vctx)
{
    DIGEST_CTX *src = vctx, *dst;

    if (src == NULL)
        return NULL;
    if ((dst = OPENSSL_zalloc(sizeof(*dst))) == NULL)
        return NULL;
    dst->provctx = src->provctx;

    if ((dst->delegate = EVP_MD_CTX_new()) == NULL)
        goto err;
    /* duplicate the STATE, not the handle */
    if (!EVP_MD_CTX_copy_ex(dst->delegate, src->delegate))
        goto err;
    return dst;

 err:
    digest_freectx(dst);
    return NULL;
}

/* 9.3: the key schedule sizes itself from these answers. */
static const OSSL_PARAM *digest_gettable(void *provctx)
{
    static const OSSL_PARAM table[] = {
        OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_BLOCK_SIZE, NULL),
        OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_SIZE, NULL),
        OSSL_PARAM_END
    };
    return table;
}

static int digest_get_params(OSSL_PARAM params[])
{
    OSSL_PARAM *p;

    if ((p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_SIZE)) != NULL
            && !OSSL_PARAM_set_size_t(p, TLSEXT_DIGEST_SIZE))
        return 0;
    if ((p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_BLOCK_SIZE)) != NULL
            && !OSSL_PARAM_set_size_t(p, TLSEXT_DIGEST_BLOCK))
        return 0;
    return 1;
}

A.4 Group capability entry

/* 10.5 and 4.3.1. The code point comes from a registry or the
 * private-use range -- never from neither (10.6). */
static unsigned int group_id  = TLSEXT_GROUP_CODE_POINT;
static unsigned int secbits   = 128;
static int          min_tls   = TLS1_3_VERSION;
static int          max_tls   = TLS1_3_VERSION;

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,
                               "TLSEXT-P256", sizeof("TLSEXT-P256")),
        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);
}

A.5 Peer share validation

/*
 * 10.4 and 17.4.1. This is the design's most security-critical function.
 * Validation happens BEFORE any operation involving the private key.
 */
static int ecdh_set_peer(void *vctx, void *vpeer)
{
    KEYEXCH_CTX *ctx  = vctx;
    EC_KEY      *peer = vpeer;

    if (ctx == NULL || peer == NULL)
        return 0;

    /* 1. the encoding decoded to a point at all */
    if (!tlsext_point_decoded(peer))
        return 0;
    /* 2. the point satisfies the curve equation */
    if (!tlsext_point_on_curve(peer))
        return 0;
    /* 3. the point is not the identity */
    if (tlsext_point_is_infinity(peer))
        return 0;
    /* 4. small-subgroup check, where the cofactor permits one */
    if (!tlsext_point_order_ok(peer))
        return 0;

    ctx->peer = peer;
    return 1;
}