/* src/scep.h */

#ifndef SCEP_H_
#define SCEP_H_

#include <stdarg.h>
#include <string.h>
#include <libgen.h>

#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif /* OPENSSL_NO_ENGINE */

#define DEFAULT_SIGALG EVP_md5()
#define DEFAULT_ENCALG EVP_des_cbc()
#define DEFAULT_VERBOSITY ERROR

#define libscep_VERSION_MAJOR @libscep_VERSION_MAJOR@
#define libscep_VERSION_MINOR @libscep_VERSION_MINOR@
#define libscep_STR(x) #x
#define libscep_VERSION_STR(major, minor) "scep-client v" libscep_STR(major) "." libscep_STR(minor)

#define SCEP_MSG_PKCSREQ_STR "19"
#define SCEP_MSG_CERTREP_STR "3"
#define SCEP_MSG_GETCERTINITIAL_STR "20"
#define SCEP_MSG_GETCERT_STR "21"
#define SCEP_MSG_GETCRL_STR "22"

#define SCEP_PKISTATUS_SUCCESS "0"
#define SCEP_PKISTATUS_FAILURE "2"
#define SCEP_PKISTATUS_PENDING "3"


#define SCEP_BAD_ALG_NR "0"
#define SCEP_BAD_MESSAGE_CHECK_NR "1"
#define SCEP_BAD_REQUEST_NR "2"
#define SCEP_BAD_TIME_NR "3"
#define SCEP_BAD_CERT_ID_NR "4"

// number of nonce bytes, defined by protocol
#define NONCE_LENGTH 16

#define SCEP_SELFSIGNED_EXPIRE_DAYS 7

#define SCEP_MIME_GETCA_RA "application/x-x509-ca-ra-cert"
#define SCEP_MIME_GETCA "application/x-x509-ca-cert"

#define scep_log(handle, verbosity, format, ...) \
    _scep_log(handle, verbosity, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define SCEP_ERR(type, msg, ...)                        \
    do {                                                \
        error = type;                                   \
        if(msg)                                         \
            scep_log(handle, FATAL, msg, ##__VA_ARGS__);\
        goto finally;                                   \
    } while(0)

#define OSSL_ERR(msg, ...)                              \
    do {                                                \
        ERR_print_errors(handle->configuration->log);   \
        SCEP_ERR(SCEPE_OPENSSL, msg, ##__VA_ARGS__);    \
    } while(0)

typedef enum {
    FATAL,
    ERROR,
    WARN,
    INFO,
    DEBUGG,
} SCEP_VERBOSITY;

typedef enum {
    /* Global options */
    SCEPCFG_VERBOSITY,
    SCEPCFG_SIGALG,
    SCEPCFG_ENCALG,
    SCEPCFG_LOG,

#ifndef OPENSSL_NO_ENGINE
    /* Engine stuff */
    SCEPCFG_ENGINE,
    SCEPCFG_ENGINE_OBJ,
    SCEPCFG_ENGINE_PARAM,
#endif /* OPENSSL_NO_ENGINE */

    /* Flags to be set */
    SCEPCFG_FLAG_CLEAR, // clear all flags
    SCEPCFG_FLAG_SET,
} SCEPCFG_TYPE;

typedef enum {
    SCEPE_OK,
    SCEPE_MEMORY,
    SCEPE_UNKNOWN_CONFIGURATION,
    SCEPE_UNKOWN_OPERATION,
    SCEPE_DUPLICATE_BIO,
    SCEPE_PROTOCOL,

    SCEPE_INVALID_CONTENT,
    SCEPE_INVALID_PARAMETER,
    SCEPE_UNHANDLED,

    SCEPE_PARAM,

    SCEPE_NYI,
    SCEPE_OPENSSL,

    SCEPE_INTERNAL,

    // this always needs to be the last error for unit tests. It is used to
    // make sure we test all error messages.
    SCEPE_DUMMY_LAST_ERROR,
} SCEP_ERROR;

typedef enum {
    SCEPOP_NONE,
    SCEPOP_GETCACERT,
    SCEPOP_PKCSREQ,
    SCEPOP_GETCERT,
    SCEPOP_GETCRL,
    SCEPOP_GETNEXTCACERT,
    SCEPOP_GETCERTINITIAL,
    SCEPOP_CERTREP,
} SCEP_OPERATION;

typedef enum {
    SCEP_SUCCESS = 0,
    SCEP_FAILURE = 2,
    SCEP_PENDING = 3,
} SCEP_PKISTATUS;

typedef enum {
    SCEP_BAD_ALG = 0,
    SCEP_BAD_MESSAGE_CHECK = 1,
    SCEP_BAD_REQUEST = 2,
    SCEP_BAD_TIME = 3,
    SCEP_BAD_CERT_ID = 4,
} SCEP_FAILINFO;

typedef enum {
    SCEP_MSG_PKCSREQ = 19,
    SCEP_MSG_CERTREP = 3,
    SCEP_MSG_GETCERTINITIAL = 20,
    SCEP_MSG_GETCERT = 21,
    SCEP_MSG_GETCRL = 22,
} SCEP_MESSAGE_TYPE;

typedef enum {
    SCEP_SKIP_SIGNER_CERT           = 0x0001,
    SCEP_ALLOW_MULTIPLE_SIGNER_CERT = 0x0002,
    SCEP_STRICT_SENDER_NONCE        = 0x0004,
} SCEP_FLAGS;

typedef enum {
    SCEP_PARAM_SENDERNONCE          = 0x0001,
} SCEP_PARAM;

#ifndef OPENSSL_NO_ENGINE
struct engine_params_t {
    char *name;
    char *value;
    struct engine_params_t *next;
};
#endif /* OPENSSL_NO_ENGINE */

typedef struct {
    SCEP_VERBOSITY verbosity;
    const EVP_MD *sigalg;
    const EVP_CIPHER *encalg;
    BIO *log;
    SCEP_FLAGS flags;
#ifndef OPENSSL_NO_ENGINE
    ENGINE *engine;
    int internal_engine;
    struct engine_params_t *params;
#endif /* OPENSSL_NO_ENGINE */
} SCEP_CONFIGURATION;

typedef struct {
    int messageType;
    int pkiStatus;
    int failInfo;
    int senderNonce;
    int recipientNonce;
    int transId;
    int extensionReq;
} SCEP_OIDS;

typedef struct {
    SCEP_CONFIGURATION *configuration;
    SCEP_OIDS *oids;
    SCEP_PARAM params_set;
    unsigned char senderNonce[NONCE_LENGTH];
} SCEP;

typedef struct pkcs7_issuer_and_subject_st
{
    X509_NAME *issuer;
    X509_NAME *subject;
} PKCS7_ISSUER_AND_SUBJECT;

/* public return value containing all relevant values of a request */
typedef struct {
    SCEP_PKISTATUS pkiStatus;
    SCEP_FAILINFO failInfo;
    char *transactionID;
    unsigned char senderNonce[NONCE_LENGTH];
    unsigned char recipientNonce[NONCE_LENGTH];
    ASN1_TYPE *challenge_password;
    X509 *signer_certificate;
    char *messageType_str;
    SCEP_MESSAGE_TYPE messageType;
    /* Different types of message content for different operations.
     * Contains the required data for the specified operation
     * if there is data attached to the request/response
     */
    union {
        struct {
            X509_REQ *request;
            int initialEnrollment;
        }; /* PKCSReq */
        PKCS7_ISSUER_AND_SERIAL *issuer_and_serial; /* GetCert */
        PKCS7_ISSUER_AND_SUBJECT *issuer_and_subject; /* GetCertInitial */

        struct {
            SCEP_OPERATION request_type;
            /* CertRep depends on request type */
            union {
                STACK_OF(X509) *certs;  /* all except GetCRL response */
                X509_CRL *crl; /* for GetCRL */

                PKCS7 *messageData; /* internal */
            };
        };
    };
} SCEP_DATA;

/* internal structure to handle operations */
struct p7_data_t {
    PKCS7 *p7;
    BIO *bio;
    PKCS7_SIGNER_INFO *signer_info;
    unsigned char sender_nonce[NONCE_LENGTH];
    char *transaction_id;
};

/* External functions */
SCEP_ERROR scep_init(SCEP **handle);
void scep_cleanup(SCEP *handle);
SCEP_ERROR scep_conf_set(SCEP *handle, SCEPCFG_TYPE type, ...);
SCEP_ERROR scep_param_set(SCEP *handle, SCEP_PARAM type, void *value);
SCEP_ERROR scep_param_get(SCEP *handle, SCEP_PARAM type, void **value);
#ifndef OPENSSL_NO_ENGINE
SCEP_ERROR scep_engine_get(SCEP *handle, ENGINE **e);
#endif /* OPENSSL_NO_ENGINE */
char *scep_strerror(SCEP_ERROR err);
char *scep_fail_info_str(SCEP_FAILINFO fail_info);
SCEP_ERROR scep_PKCS7_base64_encode(SCEP *handle, PKCS7 *p7, char **encoded);
SCEP_ERROR scep_new_selfsigned_X509(
    SCEP *handle, X509_REQ *req, EVP_PKEY *req_key, X509 **cert);
SCEP_ERROR SCEP_DATA_free(SCEP_DATA *data);
SCEP_ERROR scep_certrep(
    SCEP *handle, char *transactionID, unsigned char *senderNonce,
    SCEP_PKISTATUS pkiStatus, SCEP_FAILINFO failInfo, X509 *requestedCert,
    X509 *sig_cert, EVP_PKEY *sig_key, X509 *enc_cert,
    STACK_OF(X509) *additionalCerts, X509_CRL *crl, PKCS7 **pkiMessage);
SCEP_ERROR scep_pkcsreq(
    SCEP *handle, X509_REQ *req, X509 *sig_cert, EVP_PKEY *sig_key,
    X509 *enc_cert, PKCS7 **pkiMessage);
SCEP_ERROR scep_unwrap(
    SCEP *handle, PKCS7 *pkiMessage, X509 *ca_cert, X509 *dec_cert,
    EVP_PKEY *dec_key, SCEP_DATA **output);
SCEP_ERROR scep_unwrap_response(
    SCEP *handle, PKCS7 *pkiMessage, X509 *ca_cert,
    X509 *request_cert, EVP_PKEY *request_key,
    SCEP_OPERATION request_type, SCEP_DATA **output);
SCEP_ERROR scep_get_cert_initial(
    SCEP *handle, X509_REQ *req, X509 *sig_cert, EVP_PKEY *sig_key,
    X509 *cacert, X509 *enc_cert,
    PKCS7 **pkiMessage);
SCEP_ERROR scep_get_cert(
    SCEP *handle, X509 *sig_cert, EVP_PKEY *sig_key,
    X509_NAME *issuer, ASN1_INTEGER *serial, X509 *enc_cert,
    PKCS7 **pkiMessage);
SCEP_ERROR scep_get_crl(
    SCEP *handle, X509 *sig_cert, EVP_PKEY *sig_key,
    X509 *req_cert, X509 *enc_cert,
    PKCS7 **pkiMessage);
SCEP_ERROR scep_getcacert_reply(
    SCEP *handle, STACK_OF(X509) *certs, X509 *signcert,
    EVP_PKEY *key, PKCS7 **p7);

/* Internal functions */

/* scep.c */
SCEP_ERROR scep_create_oids(SCEP *);

/* configuration.c */
SCEP_ERROR scep_conf_init(SCEP *handle);
void scep_conf_free(SCEP_CONFIGURATION *conf);
SCEP_ERROR scep_conf_sanity_check(SCEP *handle);

/* util.c */
SCEP_ERROR scep_calculate_transaction_id_pubkey(
    SCEP *handle, EVP_PKEY *pubkey, char **transaction_id);
void _scep_log(SCEP *handle, SCEP_VERBOSITY verbosity, const char *file,
        int line, char *format, ...);
SCEP_ERROR scep_calculate_transaction_id_ias_type(
    SCEP *handle, PKCS7_ISSUER_AND_SERIAL *ias, char *messageType, char **transaction_id);
int X509_REQ_cmp(X509_REQ *req1, X509_REQ *req2);
DECLARE_ASN1_FUNCTIONS(PKCS7_ISSUER_AND_SUBJECT)

/* message.c */
SCEP_ERROR scep_p7_client_init(
    SCEP *handle, X509 *sig_cert, EVP_PKEY *sig_key,
    struct p7_data_t *p7data);
SCEP_ERROR scep_p7_server_init(SCEP *handle, struct p7_data_t *p7data);
SCEP_ERROR scep_p7_final(SCEP *handle, struct p7_data_t *p7data, PKCS7 **p7);

SCEP_ERROR scep_pkiMessage(
    SCEP *handle, char *messageType, BIO *data,
    X509 *enc_cert,
    struct p7_data_t *p7data);
#endif /* SCEP_H_ */
