/*
 * librdkafka - The Apache Kafka C/C++ library
 *
 * Copyright (c) 2019 Magnus Edenhill
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @name SSL certificates
 *
 */

#include "rdkafka_int.h"
#include "rdkafka_transport_int.h"


#if WITH_SSL
#include "rdkafka_ssl.h"

#include <openssl/x509.h>
#include <openssl/evp.h>

/**
 * @brief OpenSSL password query callback using a conf struct.
 *
 * @locality application thread
 */
static int rd_kafka_conf_ssl_passwd_cb (char *buf, int size, int rwflag,
                                        void *userdata) {
        const rd_kafka_conf_t *conf = userdata;
        int pwlen;

        if (!conf->ssl.key_password)
                return -1;

        pwlen = (int) strlen(conf->ssl.key_password);
        memcpy(buf, conf->ssl.key_password, RD_MIN(pwlen, size));

        return pwlen;
}



static const char *rd_kafka_cert_type_names[] = {
        "public-key",
        "private-key",
        "CA"
};

static const char *rd_kafka_cert_enc_names[] = {
        "PKCS#12",
        "DER",
        "PEM"
};


/**
 * @brief Destroy a certificate
 */
static void rd_kafka_cert_destroy (rd_kafka_cert_t *cert) {
        if (rd_refcnt_sub(&cert->refcnt) > 0)
                return;

        if (cert->x509)
                X509_free(cert->x509);
        if (cert->pkey)
                EVP_PKEY_free(cert->pkey);
        if (cert->store)
                X509_STORE_free(cert->store);

        rd_free(cert);
}


/**
 * @brief Create a copy of a cert
 */
static rd_kafka_cert_t *rd_kafka_cert_dup (rd_kafka_cert_t *src) {
        rd_refcnt_add(&src->refcnt);
        return src;
}

/**
 * @brief Print the OpenSSL error stack do stdout, for development use.
 */
static RD_UNUSED void rd_kafka_print_ssl_errors (void) {
        unsigned long l;
        const char *file, *data;
        int line, flags;

        while ((l = ERR_get_error_line_data(&file, &line,
                                            &data, &flags)) != 0) {
                char buf[256];

                ERR_error_string_n(l, buf, sizeof(buf));

                printf("ERR: %s:%d: %s: %s:\n",
                       file, line, buf, (flags & ERR_TXT_STRING) ? data : "");
                printf("  %lu:%s : %s : %s : %d : %s (%p, %d, fl 0x%x)\n",
                       l,
                       ERR_lib_error_string(l),
                       ERR_func_error_string(l),
                       file, line,
                       (flags & ERR_TXT_STRING) && data && *data ?
                       data : ERR_reason_error_string(l),
                       data, data ? (int)strlen(data) : -1,
                       flags & ERR_TXT_STRING);

        }
}

/**
 * @returns a cert structure with a copy of the memory in \p buffer on success,
 *          or NULL on failure in which case errstr will have a human-readable
 *          error string written to it.
 */
static rd_kafka_cert_t *rd_kafka_cert_new (const rd_kafka_conf_t *conf,
                                           rd_kafka_cert_type_t type,
                                           rd_kafka_cert_enc_t encoding,
                                           const void *buffer, size_t size,
                                           char *errstr, size_t errstr_size) {
        static const rd_bool_t
                valid[RD_KAFKA_CERT__CNT][RD_KAFKA_CERT_ENC__CNT] = {
                /* Valid encodings per certificate type */
                [RD_KAFKA_CERT_PUBLIC_KEY] = {
                        [RD_KAFKA_CERT_ENC_PKCS12] = rd_true,
                        [RD_KAFKA_CERT_ENC_DER] = rd_true,
                        [RD_KAFKA_CERT_ENC_PEM] =  rd_true
                },
                [RD_KAFKA_CERT_PRIVATE_KEY] = {
                        [RD_KAFKA_CERT_ENC_PKCS12] = rd_true,
                        [RD_KAFKA_CERT_ENC_DER] = rd_true,
                        [RD_KAFKA_CERT_ENC_PEM] =  rd_true
                },
                [RD_KAFKA_CERT_CA] = {
                        [RD_KAFKA_CERT_ENC_PKCS12] = rd_true,
                        [RD_KAFKA_CERT_ENC_DER] = rd_true,
                        [RD_KAFKA_CERT_ENC_PEM] = rd_true
                },
        };
        const char *action = "";
        BIO *bio;
        rd_kafka_cert_t *cert = NULL;
        PKCS12 *p12 = NULL;

        if ((int)type < 0 || type >= RD_KAFKA_CERT__CNT) {
                rd_snprintf(errstr, errstr_size,
                            "Invalid certificate type %d", (int)type);
                return NULL;
        }

        if ((int)encoding < 0 || encoding >= RD_KAFKA_CERT_ENC__CNT) {
                rd_snprintf(errstr, errstr_size,
                            "Invalid certificate encoding %d", (int)encoding);
                return NULL;
        }

        if (!valid[type][encoding]) {
                rd_snprintf(errstr, errstr_size,
                            "Invalid encoding %s for certificate type %s",
                            rd_kafka_cert_enc_names[encoding],
                            rd_kafka_cert_type_names[type]);
                return NULL;
        }

        action = "read memory";
        bio = BIO_new_mem_buf((void *)buffer, (long)size);
        if (!bio)
                goto fail;

        if (encoding == RD_KAFKA_CERT_ENC_PKCS12) {
                action = "read PKCS#12";
                p12 = d2i_PKCS12_bio(bio, NULL);
                if (!p12)
                        goto fail;
        }

        cert = rd_calloc(1, sizeof(*cert));
        cert->type = type;
        cert->encoding = encoding;

        rd_refcnt_init(&cert->refcnt, 1);

        switch (type)
        {
        case RD_KAFKA_CERT_CA:
                cert->store = X509_STORE_new();

                switch (encoding)
                {
                        case RD_KAFKA_CERT_ENC_PKCS12:
                        {
                                EVP_PKEY *ign_pkey;
                                X509 *ign_cert;
                                STACK_OF(X509) *cas = NULL;
                                int i;

                                action = "parse PKCS#12";
                                if (!PKCS12_parse(p12, conf->ssl.key_password,
                                                  &ign_pkey, &ign_cert,
                                                  &cas))
                                        goto fail;

                                EVP_PKEY_free(ign_pkey);
                                X509_free(ign_cert);

                                if (!cas || sk_X509_num(cas) < 1) {
                                        action = "retrieve at least one CA "
                                                "cert from PKCS#12";
                                        if (cas)
                                                sk_X509_pop_free(cas,
                                                                 X509_free);
                                        goto fail;
                                }

                                for (i = 0 ; i < sk_X509_num(cas) ; i++) {
                                        if (!X509_STORE_add_cert(
                                                    cert->store,
                                                    sk_X509_value(cas, i))) {
                                                action = "add certificate to "
                                                        "X.509 store";
                                                sk_X509_pop_free(cas,
                                                                 X509_free);
                                                goto fail;
                                        }
                                }

                                sk_X509_pop_free(cas, X509_free);
                        }
                        break;

                        case RD_KAFKA_CERT_ENC_DER:
                        {
                                X509 *x509;

                                action = "read DER / X.509 ASN.1";
                                if (!(x509 = d2i_X509_bio(bio, NULL)))
                                        goto fail;

                                if (!X509_STORE_add_cert(cert->store, x509)) {
                                        action = "add certificate to "
                                                "X.509 store";
                                        X509_free(x509);
                                        goto fail;
                                }
                        }
                        break;

                        case RD_KAFKA_CERT_ENC_PEM:
                        {
                                X509 *x509;
                                int cnt = 0;

                                action = "read PEM";

                                /* This will read one certificate per call
                                 * until an error occurs or the end of the
                                 * buffer is reached (which is an error
                                 * we'll need to clear). */
                                while ((x509 =
                                        PEM_read_bio_X509(
                                                bio, NULL,
                                                rd_kafka_conf_ssl_passwd_cb,
                                                (void *)conf))) {

                                        if (!X509_STORE_add_cert(cert->store,
                                                                 x509)) {
                                                action = "add certificate to "
                                                        "X.509 store";
                                                X509_free(x509);
                                                goto fail;
                                        }

                                        cnt++;
                                }

                                if (!BIO_eof(bio)) {
                                        /* Encountered parse error before
                                         * reaching end, propagate error and
                                         * fail. */
                                        goto fail;
                                }

                                if (!cnt) {
                                        action = "retrieve at least one "
                                                "CA cert from PEM";

                                        goto fail;
                                }

                                /* Reached end, which is raised as an error,
                                 * so clear it since it is not. */
                                ERR_clear_error();
                        }
                        break;

                        default:
                                RD_NOTREACHED();
                                break;
                }
                break;


        case RD_KAFKA_CERT_PUBLIC_KEY:
                switch (encoding)
                {
                case RD_KAFKA_CERT_ENC_PKCS12:
                {
                        EVP_PKEY *ign_pkey;

                        action = "parse PKCS#12";
                        if (!PKCS12_parse(p12, conf->ssl.key_password,
                                          &ign_pkey, &cert->x509, NULL))
                                goto fail;

                        EVP_PKEY_free(ign_pkey);

                        action = "retrieve public key";
                        if (!cert->x509)
                                goto fail;
                }
                break;

                case RD_KAFKA_CERT_ENC_DER:
                        action = "read DER / X.509 ASN.1";
                        cert->x509 = d2i_X509_bio(bio, NULL);
                        if (!cert->x509)
                                goto fail;
                        break;

                case RD_KAFKA_CERT_ENC_PEM:
                        action = "read PEM";
                        cert->x509 = PEM_read_bio_X509(
                                bio, NULL, rd_kafka_conf_ssl_passwd_cb,
                                (void *)conf);
                        if (!cert->x509)
                                goto fail;
                        break;

                default:
                        RD_NOTREACHED();
                        break;
                }
                break;


        case RD_KAFKA_CERT_PRIVATE_KEY:
                switch (encoding)
                {
                case RD_KAFKA_CERT_ENC_PKCS12:
                {
                        X509 *x509;

                        action = "parse PKCS#12";
                        if (!PKCS12_parse(p12, conf->ssl.key_password,
                                          &cert->pkey, &x509, NULL))
                                goto fail;

                        X509_free(x509);

                        action = "retrieve private key";
                        if (!cert->pkey)
                                goto fail;
                }
                break;

                case RD_KAFKA_CERT_ENC_DER:
                        action = "read DER / X.509 ASN.1 and "
                                "convert to EVP_PKEY";
                        cert->pkey = d2i_PrivateKey_bio(bio, NULL);
                        if (!cert->pkey)
                                goto fail;
                        break;

                case RD_KAFKA_CERT_ENC_PEM:
                        action = "read PEM";
                        cert->pkey = PEM_read_bio_PrivateKey(
                                bio, NULL, rd_kafka_conf_ssl_passwd_cb,
                                (void *)conf);
                        if (!cert->pkey)
                                goto fail;
                        break;

                default:
                        RD_NOTREACHED();
                        break;
                }
                break;

        default:
                RD_NOTREACHED();
                break;
        }

        if (bio)
                BIO_free(bio);
        if (p12)
                PKCS12_free(p12);

        return cert;

 fail:
        rd_snprintf(errstr, errstr_size,
                    "Failed to %s %s (encoding %s): %s",
                    action,
                    rd_kafka_cert_type_names[type],
                    rd_kafka_cert_enc_names[encoding],
                    rd_kafka_ssl_last_error_str());

        if (cert)
                rd_kafka_cert_destroy(cert);
        if (bio)
                BIO_free(bio);
        if (p12)
                PKCS12_free(p12);

        return NULL;
}
#endif /* WITH_SSL */


/**
 * @name Public API
 * @brief These public methods must be available regardless if
 *        librdkafka was built with OpenSSL or not.
 * @{
 */

rd_kafka_conf_res_t
rd_kafka_conf_set_ssl_cert (rd_kafka_conf_t *conf,
                            rd_kafka_cert_type_t cert_type,
                            rd_kafka_cert_enc_t cert_enc,
                            const void *buffer, size_t size,
                            char *errstr, size_t errstr_size) {
#if !WITH_SSL
        rd_snprintf(errstr, errstr_size,
                    "librdkafka not built with OpenSSL support");
        return RD_KAFKA_CONF_INVALID;
#else
        rd_kafka_cert_t *cert;
        rd_kafka_cert_t **cert_map[RD_KAFKA_CERT__CNT] = {
                [RD_KAFKA_CERT_PUBLIC_KEY]  = &conf->ssl.cert,
                [RD_KAFKA_CERT_PRIVATE_KEY] = &conf->ssl.key,
                [RD_KAFKA_CERT_CA]          = &conf->ssl.ca
        };
        rd_kafka_cert_t **certp;

        if ((int)cert_type < 0 || cert_type >= RD_KAFKA_CERT__CNT) {
                rd_snprintf(errstr, errstr_size,
                            "Invalid certificate type %d", (int)cert_type);
                return RD_KAFKA_CONF_INVALID;
        }

        /* Make sure OpenSSL is loaded */
        rd_kafka_global_init();

        certp = cert_map[cert_type];

        if (!buffer) {
                /* Clear current value */
                if (*certp) {
                        rd_kafka_cert_destroy(*certp);
                        *certp = NULL;
                }
                return RD_KAFKA_CONF_OK;
        }

        cert = rd_kafka_cert_new(conf, cert_type, cert_enc, buffer, size,
                                 errstr, errstr_size);
        if (!cert)
                return RD_KAFKA_CONF_INVALID;

        if (*certp)
                rd_kafka_cert_destroy(*certp);

        *certp = cert;

        return RD_KAFKA_CONF_OK;
#endif
}



/**
 * @brief Destructor called when configuration object is destroyed.
 */
void rd_kafka_conf_cert_dtor (int scope, void *pconf) {
#if WITH_SSL
        rd_kafka_conf_t *conf = pconf;
        assert(scope == _RK_GLOBAL);
        if (conf->ssl.key) {
                rd_kafka_cert_destroy(conf->ssl.key);
                conf->ssl.key = NULL;
        }
        if (conf->ssl.cert) {
                rd_kafka_cert_destroy(conf->ssl.cert);
                conf->ssl.cert = NULL;
        }
        if (conf->ssl.ca) {
                rd_kafka_cert_destroy(conf->ssl.ca);
                conf->ssl.ca = NULL;
        }
#endif
}

/**
 * @brief Copy-constructor called when configuration object \p psrcp is
 *        duplicated to \p dstp.
 */
void rd_kafka_conf_cert_copy (int scope, void *pdst, const void *psrc,
                              void *dstptr, const void *srcptr,
                              size_t filter_cnt, const char **filter) {
#if WITH_SSL
        rd_kafka_conf_t *dconf = pdst;
        const rd_kafka_conf_t *sconf = psrc;

        assert(scope == _RK_GLOBAL);

        /* Free and reset any exist certs on the destination conf */
        rd_kafka_conf_cert_dtor(scope, pdst);

        if (sconf->ssl.key)
                dconf->ssl.key = rd_kafka_cert_dup(sconf->ssl.key);

        if (sconf->ssl.cert)
                dconf->ssl.cert = rd_kafka_cert_dup(sconf->ssl.cert);

        if (sconf->ssl.ca)
                dconf->ssl.ca = rd_kafka_cert_dup(sconf->ssl.ca);
#endif
}


/**@}*/
