/*
 * Copyright 2016 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "tcn.h"

#include "ssl_private.h"

/*  _________________________________________________________________
**
**  Additional High-Level Functions for OpenSSL
**  _________________________________________________________________
*/


/*
 * Adapted from OpenSSL:
 * http://osxr.org/openssl/source/ssl/ssl_locl.h#0291
 */
/* Bits for algorithm_mkey (key exchange algorithm) */
#define SSL_kRSA        0x00000001L /* RSA key exchange */
#define SSL_kDHr        0x00000002L /* DH cert, RSA CA cert */ /* no such ciphersuites supported! */
#define SSL_kDHd        0x00000004L /* DH cert, DSA CA cert */ /* no such ciphersuite supported! */
#define SSL_kEDH        0x00000008L /* tmp DH key no DH cert */
#define SSL_kKRB5       0x00000010L /* Kerberos5 key exchange */
#define SSL_kECDHr      0x00000020L /* ECDH cert, RSA CA cert */
#define SSL_kECDHe      0x00000040L /* ECDH cert, ECDSA CA cert */
#define SSL_kEECDH      0x00000080L /* ephemeral ECDH */
#define SSL_kPSK        0x00000100L /* PSK */
#define SSL_kGOST       0x00000200L /* GOST key exchange */
#define SSL_kSRP        0x00000400L /* SRP */

/* Bits for algorithm_auth (server authentication) */
#define SSL_aRSA        0x00000001L /* RSA auth */
#define SSL_aDSS        0x00000002L /* DSS auth */
#define SSL_aNULL       0x00000004L /* no auth (i.e. use ADH or AECDH) */
#define SSL_aDH         0x00000008L /* Fixed DH auth (kDHd or kDHr) */ /* no such ciphersuites supported! */
#define SSL_aECDH       0x00000010L /* Fixed ECDH auth (kECDHe or kECDHr) */
#define SSL_aKRB5       0x00000020L /* KRB5 auth */
#define SSL_aECDSA      0x00000040L /* ECDSA auth*/
#define SSL_aPSK        0x00000080L /* PSK auth */
#define SSL_aGOST94     0x00000100L /* GOST R 34.10-94 signature auth */
#define SSL_aGOST01     0x00000200L /* GOST R 34.10-2001 signature auth */

const char* TCN_UNKNOWN_AUTH_METHOD = "UNKNOWN";

/* OpenSSL end */

/*
 * Adapted from Android:
 * https://android.googlesource.com/platform/external/openssl/+/master/patches/0003-jsse.patch
 */
const char* tcn_SSL_cipher_authentication_method(const SSL_CIPHER* cipher){
#ifdef OPENSSL_IS_BORINGSSL
	return SSL_CIPHER_get_kx_name(cipher);
#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2070000fL)
    switch (SSL_CIPHER_get_kx_nid(cipher)) {
        case NID_kx_rsa:
            return SSL_TXT_RSA;
        case NID_kx_dhe:
            switch (SSL_CIPHER_get_auth_nid(cipher)) {
#ifndef LIBRESSL_VERSION_NUMBER
                case NID_auth_dss:
                    return "DHE_" SSL_TXT_DSS;
#endif // LIBRESSL_VERSION_NUMBER

                case NID_auth_rsa:
                    return "DHE_" SSL_TXT_RSA;
                case NID_auth_null:
                    return SSL_TXT_DH "_anon";
                default:
                    return TCN_UNKNOWN_AUTH_METHOD;
            }
        case NID_kx_ecdhe:
            switch (SSL_CIPHER_get_auth_nid(cipher)) {
                case NID_auth_ecdsa:
                    return "ECDHE_" SSL_TXT_ECDSA;
                case NID_auth_rsa:
                    return "ECDHE_" SSL_TXT_RSA;
                case NID_auth_null:
                    return SSL_TXT_ECDH "_anon";
                default:
                    return TCN_UNKNOWN_AUTH_METHOD;
            }
#if !defined(OPENSSL_NO_TLS1_3) && !defined(LIBRESSL_VERSION_NUMBER)
        case NID_kx_any:
            // Let us just pick one as we could use whatever we want.
            // See https://www.openssl.org/docs/man1.1.1/man3/SSL_CIPHER_get_kx_nid.html
            return "ECDHE_" SSL_TXT_RSA;
#endif // OPENSSL_NO_TLS1_3
        default:
            return TCN_UNKNOWN_AUTH_METHOD;
    }
#else
    switch (cipher->algorithm_mkey) {
        case SSL_kRSA:
            return SSL_TXT_RSA;
        case SSL_kDHr:
            return SSL_TXT_DH "_" SSL_TXT_RSA;
        case SSL_kDHd:
            return SSL_TXT_DH "_" SSL_TXT_DSS;
        case SSL_kEDH:
            switch (cipher->algorithm_auth) {
                case SSL_aDSS:
                    return "DHE_" SSL_TXT_DSS;
                case SSL_aRSA:
                    return "DHE_" SSL_TXT_RSA;
                case SSL_aNULL:
                    return SSL_TXT_DH "_anon";
                default:
                    return TCN_UNKNOWN_AUTH_METHOD;
            }
        case SSL_kKRB5:
            return SSL_TXT_KRB5;
        case SSL_kECDHr:
            return SSL_TXT_ECDH "_" SSL_TXT_RSA;
        case SSL_kECDHe:
            return SSL_TXT_ECDH "_" SSL_TXT_ECDSA;
        case SSL_kEECDH:
            switch (cipher->algorithm_auth) {
                case SSL_aECDSA:
                    return "ECDHE_" SSL_TXT_ECDSA;
                case SSL_aRSA:
                    return "ECDHE_" SSL_TXT_RSA;
                case SSL_aNULL:
                    return SSL_TXT_ECDH "_anon";
                default:
                    return TCN_UNKNOWN_AUTH_METHOD;
            }
        default:
            return TCN_UNKNOWN_AUTH_METHOD;
    }
#endif

}

/* we initialize this index at startup time
 * and never write to it at request time,
 * so this static is thread safe.
 * also note that OpenSSL increments at static variable when
 * SSL_get_ex_new_index() is called, so we _must_ do this at startup.
 */
static int tcn_SSL_app_state_idx = -1;
static int tcn_SSL_CTX_app_state_idx = -1;

void tcn_init_app_state_idx()
{
    int i;

    if (tcn_SSL_app_state_idx == -1) {
        /* we _do_ need to call this two times */
        for (i = 0; i <= 1; i++) {
            tcn_SSL_app_state_idx = SSL_get_ex_new_index(0, "tcn_ssl_state_t*", NULL, NULL, NULL);
        }
    }

    if (tcn_SSL_CTX_app_state_idx == -1) {
        /* we _do_ need to call this two times */
        for (i = 0; i <= 1; i++) {
            tcn_SSL_CTX_app_state_idx = SSL_CTX_get_ex_new_index(0, "tcn_ssl_ctxt_t*", NULL, NULL, NULL);
        }
    }
}

void *tcn_SSL_get_app_state(const SSL *ssl)
{
    return (void *)SSL_get_ex_data(ssl, tcn_SSL_app_state_idx);
}

void tcn_SSL_set_app_state(SSL *ssl, void *arg)
{
    SSL_set_ex_data(ssl, tcn_SSL_app_state_idx, (char *)arg);
}

void *tcn_SSL_CTX_get_app_state(const SSL_CTX *ctx)
{
    return (void *)SSL_CTX_get_ex_data(ctx, tcn_SSL_CTX_app_state_idx);
}

void tcn_SSL_CTX_set_app_state(SSL_CTX *ctx, void *arg)
{
    SSL_CTX_set_ex_data(ctx, tcn_SSL_CTX_app_state_idx, (char *)arg);
}


int tcn_SSL_password_callback(char *buf, int bufsiz, int verify,
                          void *cb)
{
    char *password = (char *) cb;

    if (buf == NULL || password == NULL) {
        return 0;
    }
    *buf = '\0';

    if (password[0]) {
        /* Return already obtained password */
        strncpy(buf, password, bufsiz);
    }

    buf[bufsiz - 1] = '\0';
    return (int)strlen(buf);
}

#if OPENSSL_VERSION_NUMBER < 0x30000000L
static unsigned char dh0512_p[]={
    0xD9,0xBA,0xBF,0xFD,0x69,0x38,0xC9,0x51,0x2D,0x19,0x37,0x39,
    0xD7,0x7D,0x7E,0x3E,0x25,0x58,0x55,0x94,0x90,0x60,0x93,0x7A,
    0xF2,0xD5,0x61,0x5F,0x06,0xE8,0x08,0xB4,0x57,0xF4,0xCF,0xB4,
    0x41,0xCC,0xC4,0xAC,0xD4,0xF0,0x45,0x88,0xC9,0xD1,0x21,0x4C,
    0xB6,0x72,0x48,0xBD,0x73,0x80,0xE0,0xDD,0x88,0x41,0xA0,0xF1,
    0xEA,0x4B,0x71,0x13
};
static unsigned char dh1024_p[]={
    0xA2,0x95,0x7E,0x7C,0xA9,0xD5,0x55,0x1D,0x7C,0x77,0x11,0xAC,
    0xFD,0x48,0x8C,0x3B,0x94,0x1B,0xC5,0xC0,0x99,0x93,0xB5,0xDC,
    0xDC,0x06,0x76,0x9E,0xED,0x1E,0x3D,0xBB,0x9A,0x29,0xD6,0x8B,
    0x1F,0xF6,0xDA,0xC9,0xDF,0xD5,0x02,0x4F,0x09,0xDE,0xEC,0x2C,
    0x59,0x1E,0x82,0x32,0x80,0x9B,0xED,0x51,0x68,0xD2,0xFB,0x1E,
    0x25,0xDB,0xDF,0x9C,0x11,0x70,0xDF,0xCA,0x19,0x03,0x3D,0x3D,
    0xC1,0xAC,0x28,0x88,0x4F,0x13,0xAF,0x16,0x60,0x6B,0x5B,0x2F,
    0x56,0xC7,0x5B,0x5D,0xDE,0x8F,0x50,0x08,0xEC,0xB1,0xB9,0x29,
    0xAA,0x54,0xF4,0x05,0xC9,0xDF,0x95,0x9D,0x79,0xC6,0xEA,0x3F,
    0xC9,0x70,0x42,0xDA,0x90,0xC7,0xCC,0x12,0xB9,0x87,0x86,0x39,
    0x1E,0x1A,0xCE,0xF7,0x3F,0x15,0xB5,0x2B
};
static unsigned char dh2048_p[]={
    0xF2,0x4A,0xFC,0x7E,0x73,0x48,0x21,0x03,0xD1,0x1D,0xA8,0x16,
    0x87,0xD0,0xD2,0xDC,0x42,0xA8,0xD2,0x73,0xE3,0xA9,0x21,0x31,
    0x70,0x5D,0x69,0xC7,0x8F,0x95,0x0C,0x9F,0xB8,0x0E,0x37,0xAE,
    0xD1,0x6F,0x36,0x1C,0x26,0x63,0x2A,0x36,0xBA,0x0D,0x2A,0xF5,
    0x1A,0x0F,0xE8,0xC0,0xEA,0xD1,0xB5,0x52,0x47,0x1F,0x9A,0x0C,
    0x0F,0xED,0x71,0x51,0xED,0xE6,0x62,0xD5,0xF8,0x81,0x93,0x55,
    0xC1,0x0F,0xB4,0x72,0x64,0xB3,0x73,0xAA,0x90,0x9A,0x81,0xCE,
    0x03,0xFD,0x6D,0xB1,0x27,0x7D,0xE9,0x90,0x5E,0xE2,0x10,0x74,
    0x4F,0x94,0xC3,0x05,0x21,0x73,0xA9,0x12,0x06,0x9B,0x0E,0x20,
    0xD1,0x5F,0xF7,0xC9,0x4C,0x9D,0x4F,0xFA,0xCA,0x4D,0xFD,0xFF,
    0x6A,0x62,0x9F,0xF0,0x0F,0x3B,0xA9,0x1D,0xF2,0x69,0x29,0x00,
    0xBD,0xE9,0xB0,0x9D,0x88,0xC7,0x4A,0xAE,0xB0,0x53,0xAC,0xA2,
    0x27,0x40,0x88,0x58,0x8F,0x26,0xB2,0xC2,0x34,0x7D,0xA2,0xCF,
    0x92,0x60,0x9B,0x35,0xF6,0xF3,0x3B,0xC3,0xAA,0xD8,0x58,0x9C,
    0xCF,0x5D,0x9F,0xDB,0x14,0x93,0xFA,0xA3,0xFA,0x44,0xB1,0xB2,
    0x4B,0x0F,0x08,0x70,0x44,0x71,0x3A,0x73,0x45,0x8E,0x6D,0x9C,
    0x56,0xBC,0x9A,0xB5,0xB1,0x3D,0x8B,0x1F,0x1E,0x2B,0x0E,0x93,
    0xC2,0x9B,0x84,0xE2,0xE8,0xFC,0x29,0x85,0x83,0x8D,0x2E,0x5C,
    0xDD,0x9A,0xBB,0xFD,0xF0,0x87,0xBF,0xAF,0xC4,0xB6,0x1D,0xE7,
    0xF9,0x46,0x50,0x7F,0xC3,0xAC,0xFD,0xC9,0x8C,0x9D,0x66,0x6B,
    0x4C,0x6A,0xC9,0x3F,0x0C,0x0A,0x74,0x94,0x41,0x85,0x26,0x8F,
    0x9F,0xF0,0x7C,0x0B
};
static unsigned char dh4096_p[] = {
    0x8D,0xD3,0x8F,0x77,0x6F,0x6F,0xB0,0x74,0x3F,0x22,0xE9,0xD1,
    0x17,0x15,0x69,0xD8,0x24,0x85,0xCD,0xC4,0xE4,0x0E,0xF6,0x52,
    0x40,0xF7,0x1C,0x34,0xD0,0xA5,0x20,0x77,0xE2,0xFC,0x7D,0xA1,
    0x82,0xF1,0xF3,0x78,0x95,0x05,0x5B,0xB8,0xDB,0xB3,0xE4,0x17,
    0x93,0xD6,0x68,0xA7,0x0A,0x0C,0xC5,0xBB,0x9C,0x5E,0x1E,0x83,
    0x72,0xB3,0x12,0x81,0xA2,0xF5,0xCD,0x44,0x67,0xAA,0xE8,0xAD,
    0x1E,0x8F,0x26,0x25,0xF2,0x8A,0xA0,0xA5,0xF4,0xFB,0x95,0xAE,
    0x06,0x50,0x4B,0xD0,0xE7,0x0C,0x55,0x88,0xAA,0xE6,0xB8,0xF6,
    0xE9,0x2F,0x8D,0xA7,0xAD,0x84,0xBC,0x8D,0x4C,0xFE,0x76,0x60,
    0xCD,0xC8,0xED,0x7C,0xBF,0xF3,0xC1,0xF8,0x6A,0xED,0xEC,0xE9,
    0x13,0x7D,0x4E,0x72,0x20,0x77,0x06,0xA4,0x12,0xF8,0xD2,0x34,
    0x6F,0xDC,0x97,0xAB,0xD3,0xA0,0x45,0x8E,0x7D,0x21,0xA9,0x35,
    0x6E,0xE4,0xC9,0xC4,0x53,0xFF,0xE5,0xD9,0x72,0x61,0xC4,0x8A,
    0x75,0x78,0x36,0x97,0x1A,0xAB,0x92,0x85,0x74,0x61,0x7B,0xE0,
    0x92,0xB8,0xC6,0x12,0xA1,0x72,0xBB,0x5B,0x61,0xAA,0xE6,0x2C,
    0x2D,0x9F,0x45,0x79,0x9E,0xF4,0x41,0x93,0x93,0xEF,0x8B,0xEF,
    0xB7,0xBF,0x6D,0xF0,0x91,0x11,0x4F,0x7C,0x71,0x84,0xB5,0x88,
    0xA3,0x8C,0x1A,0xD5,0xD0,0x81,0x9C,0x50,0xAC,0xA9,0x2B,0xE9,
    0x92,0x2D,0x73,0x7C,0x0A,0xA3,0xFA,0xD3,0x6C,0x91,0x43,0xA6,
    0x80,0x7F,0xD7,0xC4,0xD8,0x6F,0x85,0xF8,0x15,0xFD,0x08,0xA6,
    0xF8,0x7B,0x3A,0xF4,0xD3,0x50,0xB4,0x2F,0x75,0xC8,0x48,0xB8,
    0xA8,0xFD,0xCA,0x8F,0x62,0xF1,0x4C,0x89,0xB7,0x18,0x67,0xB2,
    0x93,0x2C,0xC4,0xD4,0x71,0x29,0xA9,0x26,0x20,0xED,0x65,0x37,
    0x06,0x87,0xFC,0xFB,0x65,0x02,0x1B,0x3C,0x52,0x03,0xA1,0xBB,
    0xCF,0xE7,0x1B,0xA4,0x1A,0xE3,0x94,0x97,0x66,0x06,0xBF,0xA9,
    0xCE,0x1B,0x07,0x10,0xBA,0xF8,0xD4,0xD4,0x05,0xCF,0x53,0x47,
    0x16,0x2C,0xA1,0xFC,0x6B,0xEF,0xF8,0x6C,0x23,0x34,0xEF,0xB7,
    0xD3,0x3F,0xC2,0x42,0x5C,0x53,0x9A,0x00,0x52,0xCF,0xAC,0x42,
    0xD3,0x3B,0x2E,0xB6,0x04,0x32,0xE1,0x09,0xED,0x64,0xCD,0x6A,
    0x63,0x58,0xB8,0x43,0x56,0x5A,0xBE,0xA4,0x9F,0x68,0xD4,0xF7,
    0xC9,0x04,0xDF,0xCD,0xE5,0x93,0xB0,0x2F,0x06,0x19,0x3E,0xB8,
    0xAB,0x7E,0xF8,0xE7,0xE7,0xC8,0x53,0xA2,0x06,0xC3,0xC7,0xF9,
    0x18,0x3B,0x51,0xC3,0x9B,0xFF,0x8F,0x00,0x0E,0x87,0x19,0x68,
    0x2F,0x40,0xC0,0x68,0xFA,0x12,0xAE,0x57,0xB5,0xF0,0x97,0xCA,
    0x78,0x23,0x31,0xAB,0x67,0x7B,0x10,0x6B,0x59,0x32,0x9C,0x64,
    0x20,0x38,0x1F,0xC5,0x07,0x84,0x9E,0xC4,0x49,0xB1,0xDF,0xED,
    0x7A,0x8A,0xC3,0xE0,0xDD,0x30,0x55,0xFF,0x95,0x45,0xA6,0xEE,
    0xCB,0xE4,0x26,0xB9,0x8E,0x89,0x37,0x63,0xD4,0x02,0x3D,0x5B,
    0x4F,0xE5,0x90,0xF6,0x72,0xF8,0x10,0xEE,0x31,0x04,0x54,0x17,
    0xE3,0xD5,0x63,0x84,0x80,0x62,0x54,0x46,0x85,0x6C,0xD2,0xC1,
    0x3E,0x19,0xBD,0xE2,0x80,0x11,0x86,0xC7,0x4B,0x7F,0x67,0x86,
    0x47,0xD2,0x38,0xCD,0x8F,0xFE,0x65,0x3C,0x11,0xCD,0x96,0x99,
    0x4E,0x45,0xEB,0xEC,0x1D,0x94,0x8C,0x53,
};
static unsigned char dhxxx2_g[]={
    0x02
};

static DH *get_dh(int idx)
{
    DH *dh = NULL;
    BIGNUM *p = NULL;
    BIGNUM *g = NULL;
    if ((dh = DH_new()) == NULL) {
        return NULL;
    }

    switch (idx) {
        case SSL_TMP_KEY_DH_512:
            p = BN_bin2bn(dh0512_p, sizeof(dh0512_p), NULL);
        break;
        case SSL_TMP_KEY_DH_1024:
            p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
        break;
        case SSL_TMP_KEY_DH_2048:
            p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL);
        break;
        case SSL_TMP_KEY_DH_4096:
            p = BN_bin2bn(dh4096_p, sizeof(dh4096_p), NULL);
        break;
    }
    if (p == NULL) {
        goto error;
    }

    g = BN_bin2bn(dhxxx2_g, sizeof(dhxxx2_g), NULL);
    if (g == NULL) {
        goto error;
    } else {
// DH_set0_pqg() was introduced to initialize the DH parameters in OpenSSL 1.1.0 and DH is opaque now.
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x20700000)
        if (DH_set0_pqg(dh, p, NULL, g) == 0) {
            goto error;
        }
#else
        dh->p = p;
        dh->g = g;
#endif
        return dh;
    }
error:
    // All these functions can handle NULL as argument.
    DH_free(dh);
    BN_free(p);
    BN_free(g);
    return NULL;
}

DH *tcn_SSL_dh_get_tmp_param(int key_len)
{
    DH *dh = NULL;

    if (key_len == 512) {
        dh = get_dh(SSL_TMP_KEY_DH_512);
    } else if (key_len == 1024) {
        dh = get_dh(SSL_TMP_KEY_DH_1024);
    } else if (key_len == 2048) {
        dh = get_dh(SSL_TMP_KEY_DH_2048);
    } else if (key_len == 4096) {
        dh = get_dh(SSL_TMP_KEY_DH_4096);
    } else {
        dh = get_dh(SSL_TMP_KEY_DH_1024);
    }
    return dh;
}

/*
 * Hand out the already generated DH parameters...
 */
DH *tcn_SSL_callback_tmp_DH(SSL *ssl, int export, int keylen)
{
    int idx;
    switch (keylen) {
        case 512:
            idx = SSL_TMP_KEY_DH_512;
        break;
        case 2048:
            idx = SSL_TMP_KEY_DH_2048;
        break;
        case 4096:
            idx = SSL_TMP_KEY_DH_4096;
        break;
        case 1024:
        default:
            idx = SSL_TMP_KEY_DH_1024;
        break;
    }
    return (DH *)SSL_temp_keys[idx];
}

DH *tcn_SSL_callback_tmp_DH_512(SSL *ssl, int export, int keylen)
{
    return (DH *)SSL_temp_keys[SSL_TMP_KEY_DH_512];
}

DH *tcn_SSL_callback_tmp_DH_1024(SSL *ssl, int export, int keylen)
{
    return (DH *)SSL_temp_keys[SSL_TMP_KEY_DH_1024];
}

DH *tcn_SSL_callback_tmp_DH_2048(SSL *ssl, int export, int keylen)
{
    return (DH *)SSL_temp_keys[SSL_TMP_KEY_DH_2048];
}

DH *tcn_SSL_callback_tmp_DH_4096(SSL *ssl, int export, int keylen)
{
    return (DH *)SSL_temp_keys[SSL_TMP_KEY_DH_4096];
}
#endif // OPENSSL_VERSION_NUMBER < 0x30000000L

/*
 * Read a file that optionally contains the server certificate in PEM
 * format, possibly followed by a sequence of CA certificates that
 * should be sent to the peer in the SSL Certificate message.
 */
int tcn_SSL_CTX_use_certificate_chain(SSL_CTX *ctx, const char *file, bool skipfirst)
{
    BIO *bio = NULL;
    int n;

    if ((bio = BIO_new(BIO_s_file())) == NULL) {
        return -1;
    }
    if (BIO_read_filename(bio, file) <= 0) {
        BIO_free(bio);
        return -1;
    }
    n = tcn_SSL_CTX_use_certificate_chain_bio(ctx, bio, skipfirst);
    BIO_free(bio);
    return n;
}

// TODO: in the future we may want to add a function which does not need X509 at all for this.
static int SSL_CTX_setup_certs(SSL_CTX *ctx, BIO *bio, bool skipfirst, bool ca)
{
#ifdef OPENSSL_IS_BORINGSSL
    STACK_OF(CRYPTO_BUFFER) *names = sk_CRYPTO_BUFFER_new_null();
    CRYPTO_BUFFER *buffer = NULL;
    uint8_t *outp = NULL;
    int len;
#endif // OPENSSL_IS_BORINGSSL

    X509 *x509 = NULL;
    unsigned long err;
    int n = 0;

    /* optionally skip a leading server certificate */
    if (skipfirst) {
        if ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL) {
            return -1;
        }
        X509_free(x509);
    }

    if (ca) {
        while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
#ifdef OPENSSL_IS_BORINGSSL
            len = i2d_X509_NAME(X509_get_subject_name(x509), &outp);
            if (len < 0) {
                sk_CRYPTO_BUFFER_pop_free(names, CRYPTO_BUFFER_free);
                X509_free(x509);
                return -1;
            }
            buffer = CRYPTO_BUFFER_new(outp, len, NULL);
            OPENSSL_free(outp);
            outp = NULL;

            if (buffer == NULL || sk_CRYPTO_BUFFER_push(names, buffer) <= 0) {
                sk_CRYPTO_BUFFER_pop_free(names, CRYPTO_BUFFER_free);
                CRYPTO_BUFFER_free(buffer);
                X509_free(x509);
                return -1;
            }
#else
            if (SSL_CTX_add_client_CA(ctx, x509) != 1) {
                X509_free(x509);
                return -1;
            }
            // SSL_CTX_add_client_CA does not take ownership of the x509. It just calls X509_get_subject_name
            // and make a duplicate of this value. So we should always free the x509 after this call.
            // See https://github.com/netty/netty/issues/6249.
#endif // OPENSSL_IS_BORINGSSL

            X509_free(x509);
            n++;
        }

#ifdef OPENSSL_IS_BORINGSSL
        SSL_CTX_set0_client_CAs(ctx, names);
#endif // OPENSSL_IS_BORINGSSL

    } else {

#ifdef OPENSSL_IS_BORINGSSL
        return -1;
#else
        /* free a perhaps already configured extra chain */
        SSL_CTX_clear_extra_chain_certs(ctx);

        /* create new extra chain by loading the certs */
        while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
            // SSL_CTX_add_extra_chain_cert transfers ownership of the x509 certificate if the method succeeds.
            if (SSL_CTX_add_extra_chain_cert(ctx, x509) != 1) {
                X509_free(x509);
                return -1;
            }
            n++;
        }
#endif // OPENSSL_IS_BORINGSSL

    }
    /* Make sure that only the error is just an EOF */
    if ((err = ERR_peek_error()) > 0) {
        if (!(   ERR_GET_LIB(err) == ERR_LIB_PEM
              && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
            return -1;
        }
        ERR_clear_error();
    }
    return n;
}

int tcn_SSL_CTX_use_certificate_chain_bio(SSL_CTX *ctx, BIO *bio, bool skipfirst)
{
    return SSL_CTX_setup_certs(ctx, bio, skipfirst, false);
}

int tcn_SSL_CTX_use_client_CA_bio(SSL_CTX *ctx, BIO *bio)
{
    return SSL_CTX_setup_certs(ctx, bio, false, true);
}

#ifndef OPENSSL_IS_BORINGSSL
int tcn_SSL_use_certificate_chain_bio(SSL *ssl, BIO *bio, bool skipfirst)
{
#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER <= 0x2090200fL
    // Only supported on openssl 1.0.2+ and LibreSSL 2.9.2
    return -1;
#else
    X509 *x509 = NULL;
    unsigned long err;
    int n;

    /* optionally skip a leading server certificate */
    if (skipfirst) {
        if ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL) {
            return -1;
        }
        X509_free(x509);
    }

    /* create new extra chain by loading the certs */
    n = 0;

    while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
        if (tcn_SSL_add0_chain_cert(ssl, x509) != 1) {
            X509_free(x509);
            return -1;
        }
        n++;
    }
    /* Make sure that only the error is just an EOF */
    if ((err = ERR_peek_error()) > 0) {
        if (!(   ERR_GET_LIB(err) == ERR_LIB_PEM
              && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
            return -1;
        }
        ERR_clear_error();
    }
    return n;
#endif // defined(LIBRESSL_VERSION_NUMBER)
}

X509 *tcn_load_pem_cert_bio(const char *password, const BIO *bio)
{
    X509 *cert = PEM_read_bio_X509_AUX((BIO*) bio, NULL,
                (pem_password_cb *) tcn_SSL_password_callback,
                (void *)password);
    if (cert == NULL &&
       (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE)) {
        ERR_clear_error();
        BIO_ctrl((BIO*) bio, BIO_CTRL_RESET, 0, NULL);
        cert = d2i_X509_bio((BIO*) bio, NULL);
    }
    return cert;
}
#endif // OPENSSL_IS_BORINGSSL

EVP_PKEY *tcn_load_pem_key_bio(const char *password, const BIO *bio)
{
    EVP_PKEY *key = PEM_read_bio_PrivateKey((BIO*) bio, NULL,
                    (pem_password_cb *)tcn_SSL_password_callback,
                    (void *)password);

    BIO_ctrl((BIO*) bio, BIO_CTRL_RESET, 0, NULL);
    return key;
}

int tcn_EVP_PKEY_up_ref(EVP_PKEY* pkey) {
#if !defined(OPENSSL_IS_BORINGSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL))
    return CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
#else
    return EVP_PKEY_up_ref(pkey);
#endif
}

int tcn_X509_up_ref(X509* cert) {
#if !defined(OPENSSL_IS_BORINGSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2060000fL))
    return CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509);
#else
    return X509_up_ref(cert);
#endif
}

int tcn_set_verify_config(tcn_ssl_verify_config_t* c, jint tcn_mode, jint depth) {
    if (depth >= 0) {
        c->verify_depth = depth;
    }

    switch (tcn_mode) {
      case SSL_CVERIFY_IGNORED:
        switch (c->verify_mode) {
          case SSL_CVERIFY_NONE:
            return SSL_VERIFY_NONE;
          case SSL_CVERIFY_OPTIONAL:
            return SSL_VERIFY_PEER;
          default:
            return (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
        }
      case SSL_CVERIFY_NONE:
        c->verify_mode = SSL_CVERIFY_NONE;
        return SSL_VERIFY_NONE;
      case SSL_CVERIFY_OPTIONAL:
        c->verify_mode = SSL_CVERIFY_OPTIONAL;
        return SSL_VERIFY_PEER;
      default:
        c->verify_mode = SSL_CVERIFY_REQUIRED;
        return SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
    }
}

int tcn_SSL_callback_next_protos(SSL *ssl, const unsigned char **data,
                             unsigned int *len, void *arg)
{
    tcn_ssl_ctxt_t *ssl_ctxt = arg;

    *data = ssl_ctxt->next_proto_data;
    *len = ssl_ctxt->next_proto_len;

    return SSL_TLSEXT_ERR_OK;
}

/* The code here is inspired by nghttp2
 *
 * See https://github.com/tatsuhiro-t/nghttp2/blob/ae0100a9abfcf3149b8d9e62aae216e946b517fb/src/shrpx_ssl.cc#L244 */
int select_next_proto(SSL *ssl, const unsigned char **out, unsigned char *outlen,
        const unsigned char *in, unsigned int inlen, unsigned char *supported_protos,
        unsigned int supported_protos_len, int failure_behavior) {

    unsigned int i = 0;
    unsigned char target_proto_len;
    unsigned char *p = NULL;
    const unsigned char *end = NULL;
    unsigned char *proto = NULL;
    unsigned char proto_len;

    while (i < supported_protos_len) {
        target_proto_len = *supported_protos;
        ++supported_protos;

        p = (unsigned char*) in;
        end = p + inlen;

        while (p < end) {
            proto_len = *p;
            proto = ++p;

            if (proto + proto_len <= end && target_proto_len == proto_len &&
                    memcmp(supported_protos, proto, proto_len) == 0) {

                // We found a match, so set the output and return with OK!
                *out = proto;
                *outlen = proto_len;

                return SSL_TLSEXT_ERR_OK;
            }
            // Move on to the next protocol.
            p += proto_len;
        }

        // increment len and pointers.
        i += target_proto_len;
        supported_protos += target_proto_len;
    }

    if (failure_behavior == SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL) {
         // There were no match but we just select our last protocol and hope the other peer support it.
         //
         // decrement the pointer again so the pointer points to the start of the protocol.
         p -= proto_len;
         *out = p;
         *outlen = proto_len;
         return SSL_TLSEXT_ERR_OK;
    }
    // TODO: OpenSSL currently not support to fail with fatal error. Once this changes we can also support it here.
    //       Issue https://github.com/openssl/openssl/issues/188 has been created for this.
    // Nothing matched so not select anything and just accept.
    return SSL_TLSEXT_ERR_NOACK;
}

int tcn_SSL_callback_select_next_proto(SSL *ssl, unsigned char **out, unsigned char *outlen,
                         const unsigned char *in, unsigned int inlen,
                         void *arg) {
    tcn_ssl_ctxt_t *ssl_ctxt = arg;
    return select_next_proto(ssl, (const unsigned char**) out, outlen, in, inlen, ssl_ctxt->next_proto_data, ssl_ctxt->next_proto_len, ssl_ctxt->next_selector_failure_behavior);
}

int tcn_SSL_callback_alpn_select_proto(SSL* ssl, const unsigned char **out, unsigned char *outlen,
        const unsigned char *in, unsigned int inlen, void *arg) {
    tcn_ssl_ctxt_t *ssl_ctxt = arg;
    return select_next_proto(ssl, out, outlen, in, inlen, ssl_ctxt->alpn_proto_data, ssl_ctxt->alpn_proto_len, ssl_ctxt->alpn_selector_failure_behavior);
}
