static int ssl_prepare_server_key_exchange( mbedtls_ssl_context *ssl,
                                            size_t *signature_len )
{
    const mbedtls_ssl_ciphersuite_t *ciphersuite_info =
        ssl->handshake->ciphersuite_info;

#if defined(MBEDTLS_KEY_EXCHANGE_SOME_PFS_ENABLED)
#if defined(MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED)
    unsigned char *dig_signed = NULL;
#endif /* MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED */
#endif /* MBEDTLS_KEY_EXCHANGE_SOME_PFS_ENABLED */

    (void) ciphersuite_info; /* unused in some configurations */
#if !defined(MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED)
    (void) signature_len;
#endif /* MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED */

    ssl->out_msglen = 4; /* header (type:1, length:3) to be written later */

    /*
     *
     * Part 1: Provide key exchange parameters for chosen ciphersuite.
     *
     */

    /*
     * - ECJPAKE key exchanges
     */
#if defined(MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED)
    if( ciphersuite_info->key_exchange == MBEDTLS_KEY_EXCHANGE_ECJPAKE )
    {
        int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
        size_t len = 0;

        ret = mbedtls_ecjpake_write_round_two(
            &ssl->handshake->ecjpake_ctx,
            ssl->out_msg + ssl->out_msglen,
            MBEDTLS_SSL_OUT_CONTENT_LEN - ssl->out_msglen, &len,
            ssl->conf->f_rng, ssl->conf->p_rng );
        if( ret != 0 )
        {
            MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ecjpake_write_round_two", ret );
            return( ret );
        }

        ssl->out_msglen += len;
    }
#endif /* MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED */

    /*
     * For (EC)DHE key exchanges with PSK, parameters are prefixed by support
     * identity hint (RFC 4279, Sec. 3). Until someone needs this feature,
     * we use empty support identity hints here.
     **/
#if defined(MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED)   || \
    defined(MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED)
    if( ciphersuite_info->key_exchange == MBEDTLS_KEY_EXCHANGE_DHE_PSK ||
        ciphersuite_info->key_exchange == MBEDTLS_KEY_EXCHANGE_ECDHE_PSK )
    {
        ssl->out_msg[ssl->out_msglen++] = 0x00;
        ssl->out_msg[ssl->out_msglen++] = 0x00;
    }
#endif /* MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED ||
          MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED */

    /*
     * - DHE key exchanges
     */
#if defined(MBEDTLS_KEY_EXCHANGE_SOME_DHE_ENABLED)
    if( mbedtls_ssl_ciphersuite_uses_dhe( ciphersuite_info ) )
    {
        int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
        size_t len = 0;

        if( ssl->conf->dhm_P.p == NULL || ssl->conf->dhm_G.p == NULL )
        {
            MBEDTLS_SSL_DEBUG_MSG( 1, ( "no DH parameters set" ) );
            return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
        }

        /*
         * Ephemeral DH parameters:
         *
         * struct {
         *     opaque dh_p<1..2^16-1>;
         *     opaque dh_g<1..2^16-1>;
         *     opaque dh_Ys<1..2^16-1>;
         * } ServerDHParams;
         */
        if( ( ret = mbedtls_dhm_set_group( &ssl->handshake->dhm_ctx,
                                           &ssl->conf->dhm_P,
                                           &ssl->conf->dhm_G ) ) != 0 )
        {
            MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_dhm_set_group", ret );
            return( ret );
        }

        if( ( ret = mbedtls_dhm_make_params(
                  &ssl->handshake->dhm_ctx,
                  (int) mbedtls_mpi_size( &ssl->handshake->dhm_ctx.P ),
                  ssl->out_msg + ssl->out_msglen, &len,
                  ssl->conf->f_rng, ssl->conf->p_rng ) ) != 0 )
        {
            MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_dhm_make_params", ret );
            return( ret );
        }

#if defined(MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED)
        dig_signed = ssl->out_msg + ssl->out_msglen;
#endif

        ssl->out_msglen += len;

        MBEDTLS_SSL_DEBUG_MPI( 3, "DHM: X ", &ssl->handshake->dhm_ctx.X  );
        MBEDTLS_SSL_DEBUG_MPI( 3, "DHM: P ", &ssl->handshake->dhm_ctx.P  );
        MBEDTLS_SSL_DEBUG_MPI( 3, "DHM: G ", &ssl->handshake->dhm_ctx.G  );
        MBEDTLS_SSL_DEBUG_MPI( 3, "DHM: GX", &ssl->handshake->dhm_ctx.GX );
    }
#endif /* MBEDTLS_KEY_EXCHANGE_SOME_DHE_ENABLED */

    /*
     * - ECDHE key exchanges
     */
#if defined(MBEDTLS_KEY_EXCHANGE_SOME_ECDHE_ENABLED)
    if( mbedtls_ssl_ciphersuite_uses_ecdhe( ciphersuite_info ) )
    {
        /*
         * Ephemeral ECDH parameters:
         *
         * struct {
         *     ECParameters curve_params;
         *     ECPoint      public;
         * } ServerECDHParams;
         */
        const mbedtls_ecp_curve_info **curve = NULL;
        const mbedtls_ecp_group_id *gid;
        int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
        size_t len = 0;

        /* Match our preference list against the offered curves */
        for( gid = ssl->conf->curve_list; *gid != MBEDTLS_ECP_DP_NONE; gid++ )
            for( curve = ssl->handshake->curves; *curve != NULL; curve++ )
                if( (*curve)->grp_id == *gid )
                    goto curve_matching_done;

curve_matching_done:
        if( curve == NULL || *curve == NULL )
        {
            MBEDTLS_SSL_DEBUG_MSG( 1, ( "no matching curve for ECDHE" ) );
            return( MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN );
        }

        MBEDTLS_SSL_DEBUG_MSG( 2, ( "ECDHE curve: %s", (*curve)->name ) );

        if( ( ret = mbedtls_ecdh_setup( &ssl->handshake->ecdh_ctx,
                                        (*curve)->grp_id ) ) != 0 )
        {
            MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ecp_group_load", ret );
            return( ret );
        }

        if( ( ret = mbedtls_ecdh_make_params(
                  &ssl->handshake->ecdh_ctx, &len,
                  ssl->out_msg + ssl->out_msglen,
                  MBEDTLS_SSL_OUT_CONTENT_LEN - ssl->out_msglen,
                  ssl->conf->f_rng, ssl->conf->p_rng ) ) != 0 )
        {
            MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ecdh_make_params", ret );
            return( ret );
        }

#if defined(MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED)
        dig_signed = ssl->out_msg + ssl->out_msglen;
#endif

        ssl->out_msglen += len;

        MBEDTLS_SSL_DEBUG_ECDH( 3, &ssl->handshake->ecdh_ctx,
                                MBEDTLS_DEBUG_ECDH_Q );
    }
#endif /* MBEDTLS_KEY_EXCHANGE_SOME_ECDHE_ENABLED */

    /*
     *
     * Part 2: For key exchanges involving the server signing the
     *         exchange parameters, compute and add the signature here.
     *
     */
#if defined(MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED)
    if( mbedtls_ssl_ciphersuite_uses_server_signature( ciphersuite_info ) )
    {
        size_t dig_signed_len = ssl->out_msg + ssl->out_msglen - dig_signed;
        size_t hashlen = 0;
#if defined(MBEDTLS_USE_PSA_CRYPTO)
        unsigned char hash[PSA_HASH_MAX_SIZE];
#else
        unsigned char hash[MBEDTLS_MD_MAX_SIZE];
#endif
        int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;

        /*
         * 2.1: Choose hash algorithm:
         * A: For TLS 1.2, obey signature-hash-algorithm extension
         *    to choose appropriate hash.
         * B: For SSL3, TLS1.0, TLS1.1 and ECDHE_ECDSA, use SHA1
         *    (RFC 4492, Sec. 5.4)
         * C: Otherwise, use MD5 + SHA1 (RFC 4346, Sec. 7.4.3)
         */

        mbedtls_md_type_t md_alg;

#if defined(MBEDTLS_SSL_PROTO_TLS1_2)
        mbedtls_pk_type_t sig_alg =
            mbedtls_ssl_get_ciphersuite_sig_pk_alg( ciphersuite_info );
        if( ssl->minor_ver == MBEDTLS_SSL_MINOR_VERSION_3 )
        {
            /* A: For TLS 1.2, obey signature-hash-algorithm extension
             *    (RFC 5246, Sec. 7.4.1.4.1). */
            if( sig_alg == MBEDTLS_PK_NONE ||
                ( md_alg = mbedtls_ssl_sig_hash_set_find( &ssl->handshake->hash_algs,
                                                          sig_alg ) ) == MBEDTLS_MD_NONE )
            {
                MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) );
                /* (... because we choose a cipher suite
                 *      only if there is a matching hash.) */
                return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
            }
        }
        else
#endif /* MBEDTLS_SSL_PROTO_TLS1_2 */
#if defined(MBEDTLS_SSL_PROTO_SSL3) || defined(MBEDTLS_SSL_PROTO_TLS1) || \
    defined(MBEDTLS_SSL_PROTO_TLS1_1)
        if( ciphersuite_info->key_exchange == MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA )
        {
            /* B: Default hash SHA1 */
            md_alg = MBEDTLS_MD_SHA1;
        }
        else
#endif /* MBEDTLS_SSL_PROTO_SSL3 || MBEDTLS_SSL_PROTO_TLS1 || \
          MBEDTLS_SSL_PROTO_TLS1_1 */
        {
            /* C: MD5 + SHA1 */
            md_alg = MBEDTLS_MD_NONE;
        }

        MBEDTLS_SSL_DEBUG_MSG( 3, ( "pick hash algorithm %u for signing", (unsigned) md_alg ) );

        /*
         * 2.2: Compute the hash to be signed
         */
#if defined(MBEDTLS_SSL_PROTO_SSL3) || defined(MBEDTLS_SSL_PROTO_TLS1) || \
    defined(MBEDTLS_SSL_PROTO_TLS1_1)
        if( md_alg == MBEDTLS_MD_NONE )
        {
            hashlen = 36;
            ret = mbedtls_ssl_get_key_exchange_md_ssl_tls( ssl, hash,
                                                           dig_signed,
                                                           dig_signed_len );
            if( ret != 0 )
                return( ret );
        }
        else
#endif /* MBEDTLS_SSL_PROTO_SSL3 || MBEDTLS_SSL_PROTO_TLS1 || \
          MBEDTLS_SSL_PROTO_TLS1_1 */
#if defined(MBEDTLS_SSL_PROTO_TLS1) || defined(MBEDTLS_SSL_PROTO_TLS1_1) || \
    defined(MBEDTLS_SSL_PROTO_TLS1_2)
        if( md_alg != MBEDTLS_MD_NONE )
        {
            ret = mbedtls_ssl_get_key_exchange_md_tls1_2( ssl, hash, &hashlen,
                                                          dig_signed,
                                                          dig_signed_len,
                                                          md_alg );
            if( ret != 0 )
                return( ret );
        }
        else
#endif /* MBEDTLS_SSL_PROTO_TLS1 || MBEDTLS_SSL_PROTO_TLS1_1 || \
          MBEDTLS_SSL_PROTO_TLS1_2 */
        {
            MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) );
            return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
        }

        MBEDTLS_SSL_DEBUG_BUF( 3, "parameters hash", hash, hashlen );

        /*
         * 2.3: Compute and add the signature
         */
#if defined(MBEDTLS_SSL_PROTO_TLS1_2)
        if( ssl->minor_ver == MBEDTLS_SSL_MINOR_VERSION_3 )
        {
            /*
             * For TLS 1.2, we need to specify signature and hash algorithm
             * explicitly through a prefix to the signature.
             *
             * struct {
             *    HashAlgorithm hash;
             *    SignatureAlgorithm signature;
             * } SignatureAndHashAlgorithm;
             *
             * struct {
             *    SignatureAndHashAlgorithm algorithm;
             *    opaque signature<0..2^16-1>;
             * } DigitallySigned;
             *
             */

            ssl->out_msg[ssl->out_msglen++] =
                mbedtls_ssl_hash_from_md_alg( md_alg );
            ssl->out_msg[ssl->out_msglen++] =
                mbedtls_ssl_sig_from_pk_alg( sig_alg );
        }
#endif /* MBEDTLS_SSL_PROTO_TLS1_2 */

#if defined(MBEDTLS_SSL_ASYNC_PRIVATE)
        if( ssl->conf->f_async_sign_start != NULL )
        {
            ret = ssl->conf->f_async_sign_start( ssl,
                                                 mbedtls_ssl_own_cert( ssl ),
                                                 md_alg, hash, hashlen );
            switch( ret )
            {
            case MBEDTLS_ERR_SSL_HW_ACCEL_FALLTHROUGH:
                /* act as if f_async_sign was null */
                break;
            case 0:
                ssl->handshake->async_in_progress = 1;
                return( ssl_resume_server_key_exchange( ssl, signature_len ) );
            case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS:
                ssl->handshake->async_in_progress = 1;
                return( MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS );
            default:
                MBEDTLS_SSL_DEBUG_RET( 1, "f_async_sign_start", ret );
                return( ret );
            }
        }
#endif /* MBEDTLS_SSL_ASYNC_PRIVATE */

        if( mbedtls_ssl_own_key( ssl ) == NULL )
        {
            MBEDTLS_SSL_DEBUG_MSG( 1, ( "got no private key" ) );
            return( MBEDTLS_ERR_SSL_PRIVATE_KEY_REQUIRED );
        }

        /* Append the signature to ssl->out_msg, leaving 2 bytes for the
         * signature length which will be added in ssl_write_server_key_exchange
         * after the call to ssl_prepare_server_key_exchange.
         * ssl_write_server_key_exchange also takes care of incrementing
         * ssl->out_msglen. */
        if( ( ret = mbedtls_pk_sign( mbedtls_ssl_own_key( ssl ),
                                     md_alg, hash, hashlen,
                                     ssl->out_msg + ssl->out_msglen + 2,
                                     signature_len,
                                     ssl->conf->f_rng,
                                     ssl->conf->p_rng ) ) != 0 )
        {
            MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_pk_sign", ret );
            return( ret );
        }
    }
#endif /* MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED */

    return( 0 );
}