static int tls_process_ske_dhe(SSL *s, PACKET *pkt, EVP_PKEY **pkey)
{
    PACKET prime, generator, pub_key;
    EVP_PKEY *peer_tmp = NULL;
    BIGNUM *p = NULL, *g = NULL, *bnpub_key = NULL;
    EVP_PKEY_CTX *pctx = NULL;
    OSSL_PARAM *params = NULL;
    OSSL_PARAM_BLD *tmpl = NULL;
    int ret = 0;

    if (!PACKET_get_length_prefixed_2(pkt, &prime)
        || !PACKET_get_length_prefixed_2(pkt, &generator)
        || !PACKET_get_length_prefixed_2(pkt, &pub_key)) {
        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_LENGTH_MISMATCH);
        return 0;
    }

    p = BN_bin2bn(PACKET_data(&prime), (int)PACKET_remaining(&prime), NULL);
    g = BN_bin2bn(PACKET_data(&generator), (int)PACKET_remaining(&generator),
                  NULL);
    bnpub_key = BN_bin2bn(PACKET_data(&pub_key),
                          (int)PACKET_remaining(&pub_key), NULL);
    if (p == NULL || g == NULL || bnpub_key == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_BN_LIB);
        goto err;
    }

    tmpl = OSSL_PARAM_BLD_new();
    if (tmpl == NULL
            || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_P, p)
            || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_G, g)
            || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_PUB_KEY,
                                       bnpub_key)
            || (params = OSSL_PARAM_BLD_to_param(tmpl)) == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
        goto err;
    }

    pctx = EVP_PKEY_CTX_new_from_name(s->ctx->libctx, "DH", s->ctx->propq);
    if (pctx == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
        goto err;
    }
    if (EVP_PKEY_fromdata_init(pctx) <= 0
            || EVP_PKEY_fromdata(pctx, &peer_tmp, EVP_PKEY_KEYPAIR, params) <= 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_DH_VALUE);
        goto err;
    }

    EVP_PKEY_CTX_free(pctx);
    pctx = EVP_PKEY_CTX_new_from_pkey(s->ctx->libctx, peer_tmp, s->ctx->propq);
    if (pctx == NULL
            /*
             * EVP_PKEY_param_check() will verify that the DH params are using
             * a safe prime. In this context, because we're using ephemeral DH,
             * we're ok with it not being a safe prime.
             * EVP_PKEY_param_check_quick() skips the safe prime check.
             */
            || EVP_PKEY_param_check_quick(pctx) != 1
            || EVP_PKEY_public_check(pctx) != 1) {
        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_BAD_DH_VALUE);
        goto err;
    }

    if (!ssl_security(s, SSL_SECOP_TMP_DH,
                      EVP_PKEY_get_security_bits(peer_tmp),
                      0, peer_tmp)) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_R_DH_KEY_TOO_SMALL);
        goto err;
    }

    s->s3.peer_tmp = peer_tmp;
    peer_tmp = NULL;

    /*
     * FIXME: This makes assumptions about which ciphersuites come with
     * public keys. We should have a less ad-hoc way of doing this
     */
    if (s->s3.tmp.new_cipher->algorithm_auth & (SSL_aRSA | SSL_aDSS))
        *pkey = X509_get0_pubkey(s->session->peer);
    /* else anonymous DH, so no certificate or pkey. */

    ret = 1;

 err:
    OSSL_PARAM_BLD_free(tmpl);
    OSSL_PARAM_free(params);
    EVP_PKEY_free(peer_tmp);
    EVP_PKEY_CTX_free(pctx);
    BN_free(p);
    BN_free(g);
    BN_free(bnpub_key);

    return ret;
}