/*
 * 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.
 */

#ifndef SSL_PRIVATE_H
#define SSL_PRIVATE_H

/* Exclude unused OpenSSL features
 * even if the OpenSSL supports them
 */
#ifndef OPENSSL_NO_IDEA
#define OPENSSL_NO_IDEA
#endif
#ifndef OPENSSL_NO_KRB5
#define OPENSSL_NO_KRB5
#endif
#ifndef OPENSSL_NO_MDC2
#define OPENSSL_NO_MDC2
#endif
#ifndef OPENSSL_NO_RC5
#define OPENSSL_NO_RC5
#endif

#include "apr_thread_rwlock.h"
#include "apr_atomic.h"
#include <stdbool.h>

/* OpenSSL headers */
#include <openssl/opensslv.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h> 
#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/x509v3.h>

#define ERR_LEN 256

/* Avoid tripping over an engine build installed globally and detected
 * when the user points at an explicit non-engine flavor of OpenSSL
 */
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif

#ifndef RAND_MAX
#include <limits.h>
#define RAND_MAX INT_MAX
#endif

/*
 * Define IDs for the temporary RSA keys and DH params
 */

#define SSL_TMP_KEY_DH_512      (1)
#define SSL_TMP_KEY_DH_1024     (2)
#define SSL_TMP_KEY_DH_2048     (3)
#define SSL_TMP_KEY_DH_4096     (4)
#define SSL_TMP_KEY_MAX         (5)

/*
 * Define the SSL Protocol options
 */
#define SSL_PROTOCOL_NONE       (0)
#define SSL_PROTOCOL_SSLV2      (1<<0)
#define SSL_PROTOCOL_SSLV3      (1<<1)
#define SSL_PROTOCOL_TLSV1      (1<<2)
#define SSL_PROTOCOL_TLSV1_1    (1<<3)
#define SSL_PROTOCOL_TLSV1_2    (1<<4)
/* TLS_*method according to https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_new.html */
#define SSL_PROTOCOL_TLS        (SSL_PROTOCOL_SSLV3|SSL_PROTOCOL_TLSV1|SSL_PROTOCOL_TLSV1_1|SSL_PROTOCOL_TLSV1_2)
#define SSL_PROTOCOL_ALL        (SSL_PROTOCOL_SSLV2|SSL_PROTOCOL_TLS)

#define SSL_MODE_CLIENT         (0)
#define SSL_MODE_SERVER         (1)
#define SSL_MODE_COMBINED       (2)

#define SSL_DEFAULT_CACHE_SIZE  (256)
#define SSL_DEFAULT_VHOST_NAME  ("_default_:443")

#define SSL_CVERIFY_IGNORED             (-1)
#define SSL_CVERIFY_NONE                (0)
#define SSL_CVERIFY_OPTIONAL            (1)
#define SSL_CVERIFY_REQUIRED            (2)

#define SSL_TO_APR_ERROR(X)         (APR_OS_START_USERERR + 1000 + X)

#define MAX_ALPN_NPN_PROTO_SIZE 65535

extern const char* TCN_UNKNOWN_AUTH_METHOD;

/* ECC: make sure we have at least 1.0.0 */
#if !defined(OPENSSL_NO_EC) && defined(TLSEXT_ECPOINTFORMAT_uncompressed)
#define HAVE_ECC              1
#endif

/* OpenSSL 1.0.2 compatibility */
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
#define TLS_method SSLv23_method
#define TLS_client_method SSLv23_client_method
#define TLS_server_method SSLv23_server_method
#define OPENSSL_VERSION SSLEAY_VERSION
#define OpenSSL_version SSLeay_version
#define OPENSSL_malloc_init CRYPTO_malloc_init
#define X509_REVOKED_get0_serialNumber(x) x->serialNumber
#define OpenSSL_version_num SSLeay
#define BIO_get_init(x)       ((x)->init)
#define BIO_set_init(x,v)     ((x)->init=(v))
#define BIO_get_data(x)       ((x)->ptr)
#define BIO_set_data(x,v)     ((x)->ptr=(v))
#define BIO_set_shutdown(x,v) ((x)->shutdown=(v))
#define BIO_get_shutdown(x)   ((x)->shutdown)
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */

#define SSL_SELECTOR_FAILURE_NO_ADVERTISE                       0
#define SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL            1

#define SSL_SESSION_TICKET_KEY_NAME_LEN 16
#define SSL_SESSION_TICKET_AES_KEY_LEN  16
#define SSL_SESSION_TICKET_HMAC_KEY_LEN 16
#define SSL_SESSION_TICKET_KEY_SIZE     48

extern void *SSL_temp_keys[SSL_TMP_KEY_MAX];

// HACK!
// LibreSSL 2.4.x doesn't support the X509_V_ERR_UNSPECIFIED so we introduce a work around to make sure a supported alert is used.
// This should be reverted when we support LibreSSL 2.5.x (which does support X509_V_ERR_UNSPECIFIED).
#ifndef X509_V_ERR_UNSPECIFIED
#define TCN_X509_V_ERR_UNSPECIFIED 99999
#else
#define TCN_X509_V_ERR_UNSPECIFIED (X509_V_ERR_UNSPECIFIED)
#endif /*X509_V_ERR_UNSPECIFIED*/

typedef struct tcn_ssl_ctxt_t tcn_ssl_ctxt_t;

typedef struct {
    unsigned char   key_name[SSL_SESSION_TICKET_KEY_NAME_LEN];
    unsigned char   hmac_key[SSL_SESSION_TICKET_HMAC_KEY_LEN];
    unsigned char   aes_key[SSL_SESSION_TICKET_AES_KEY_LEN];
} tcn_ssl_ticket_key_t;

typedef struct {
    int verify_depth;
    int verify_mode;
} tcn_ssl_verify_config_t;

struct tcn_ssl_ctxt_t {
    apr_pool_t*              pool;
    SSL_CTX*                 ctx;

    /* Holds the alpn protocols, each of them prefixed with the len of the protocol */
    unsigned char*           alpn_proto_data;
    unsigned char*           next_proto_data;

    /* for client or downstream server authentication */
    char*                    password;

    apr_thread_rwlock_t*     mutex; // Session ticket mutext
    tcn_ssl_ticket_key_t*    ticket_keys;

    /* certificate verifier callback */
    jobject                  verifier;
    jmethodID                verifier_method;

    jobject                  cert_requested_callback;
    jmethodID                cert_requested_callback_method;

    tcn_ssl_verify_config_t  verify_config;

    int                      protocol;
    /* we are one or the other */
    int                      mode;

    unsigned int             next_proto_len;
    int                      next_selector_failure_behavior;

    unsigned int             alpn_proto_len;
    int                      alpn_selector_failure_behavior;

    unsigned int             ticket_keys_len;
    unsigned int             pad;

    /* TLS ticket key session resumption statistics */

    // The client did not present a ticket and we issued a new one.
    apr_uint32_t             ticket_keys_new;
    // The client presented a ticket derived from the primary key
    apr_uint32_t             ticket_keys_resume;
    // The client presented a ticket derived from an older key, and we upgraded to the primary key.
    apr_uint32_t             ticket_keys_renew;
    // The client presented a ticket that did not match any key in the list.
    apr_uint32_t             ticket_keys_fail;

    unsigned char            context_id[SHA_DIGEST_LENGTH];
};

/*
 *  Additional Functions
 */
void        SSL_init_app_data_idx(void);
// The app_data2 is used to store the tcn_ssl_ctxt_t pointer for the SSL instance.
void       *SSL_get_app_data2(SSL *);
void        SSL_set_app_data2(SSL *, void *);
// The app_data3 is used to store the handshakeCount pointer for the SSL instance.
void       *SSL_get_app_data3(SSL *);
void        SSL_set_app_data3(SSL *, void *);
// The app_data4 is used to store the tcn_ssl_verify_config_t pointer for the SSL instance.
// This will initially point back to the tcn_ssl_ctxt_t in tcn_ssl_ctxt_t.
void       *SSL_get_app_data4(SSL *);
void        SSL_set_app_data4(SSL *, void *);
int         SSL_password_callback(char *, int, int, void *);
DH         *SSL_dh_get_tmp_param(int);
DH         *SSL_callback_tmp_DH(SSL *, int, int);
// The following provided callbacks will always return DH of a given length.
// See https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_set_tmp_dh_callback.html
DH         *SSL_callback_tmp_DH_512(SSL *, int, int);
DH         *SSL_callback_tmp_DH_1024(SSL *, int, int);
DH         *SSL_callback_tmp_DH_2048(SSL *, int, int);
DH         *SSL_callback_tmp_DH_4096(SSL *, int, int);
int         SSL_CTX_use_certificate_chain(SSL_CTX *, const char *, bool);
int         SSL_CTX_use_certificate_chain_bio(SSL_CTX *, BIO *, bool);
int         SSL_CTX_use_client_CA_bio(SSL_CTX *, BIO *);
int         SSL_use_certificate_chain_bio(SSL *, BIO *, bool);
X509        *load_pem_cert_bio(const char *, const BIO *);
EVP_PKEY    *load_pem_key_bio(const char *, const BIO *);
int         tcn_set_verify_config(tcn_ssl_verify_config_t* c, jint tcn_mode, jint depth);
int         tcn_EVP_PKEY_up_ref(EVP_PKEY* pkey);
int         tcn_X509_up_ref(X509* cert);
int         SSL_callback_next_protos(SSL *, const unsigned char **, unsigned int *, void *);
int         SSL_callback_select_next_proto(SSL *, unsigned char **, unsigned char *, const unsigned char *, unsigned int,void *);
int         SSL_callback_alpn_select_proto(SSL *, const unsigned char **, unsigned char *, const unsigned char *, unsigned int, void *);
const char *SSL_cipher_authentication_method(const SSL_CIPHER *);

#if !defined(OPENSSL_IS_BORINGSSL) && (defined(__GNUC__) || defined(__GNUG__))
    // only supported with GCC, this will be used to support different openssl versions at the same time.
    extern int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos,
           unsigned protos_len) __attribute__((weak));
    extern void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, int (*cb) (SSL *ssl, const unsigned char **out,
           unsigned char *outlen, const unsigned char *in, unsigned int inlen,
           void *arg), void *arg) __attribute__((weak));
    extern void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
           unsigned *len) __attribute__((weak));
#endif

#endif /* SSL_PRIVATE_H */
