/*
 * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
 *
 * openGauss is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * cipher.cpp
 *
 *
 * IDENTIFICATION
 *    src/lib/pq/port/cipher.cpp
 *
 * -------------------------------------------------------------------------
 */
#include "cipher.h"
#include "securec.h"
#include "securec_check.h"
#include "utils/syscall_lock.h"
#include "utils/pg_crc_tables.h"
#include "openssl/rand.h"
#include "openssl/evp.h"
#include "openssl/ossl_typ.h"
#include "openssl/x509.h"
#include "openssl/ssl.h"
#include "openssl/asn1.h"
#include "openssl/hmac.h"

#define ENTROPY_F_LEN 128
#define NONCE_F_LEN 128

#ifndef CACHELINE_SIZE
#define CACHELINE_SIZE 32
#endif

#ifndef L1CACHE_SIZE
#define L1CACHE_SIZE (64 * 1024)
#endif

pthread_mutex_t read_cipher_lock;

/* Store entropy message of the seed which used by random function. */
typedef struct {
    uint64 data;
    uint32 prev_time;
    uint32 last_delta;
    int32 last_delta2;
    uint32 n;
    double sum;
} entropy_t;

static bool gen_cipher_file(KeyMode mode, GS_UCHAR* init_rand, GS_UCHAR server_vector[], GS_UCHAR client_vector[],
    const char* plain_key, const char* user_name, const char* datadir, const char* preStr);
static bool gen_rand_file(
    KeyMode mode, GS_UCHAR* init_rand, const char* user_name, const char* datadir, const char* preStr);
static bool WriteContentToFile(const char* filename, const void* content, size_t csize);
static bool IsSpecialCharacter(char ch);
static bool isModeExists(KeyMode mode);
void ClearCipherKeyFile(CipherkeyFile* cipher_file_content);
void ClearRandKeyFile(RandkeyFile* rand_file_content);
bool init_vector_random(GS_UCHAR* init_vector, size_t vector_len);

GS_UINT32 CRYPT_decrypt(GS_UINT32 ulAlgId, const GS_UCHAR* pucKey, GS_UINT32 ulKeyLen, const GS_UCHAR* pucIV,
    GS_UINT32 ulIVLen, GS_UCHAR* pucCipherText, GS_UINT32 ulCLen, GS_UCHAR* pucPlainText, GS_UINT32* pulPLen);
GS_UINT32 CRYPT_encrypt(GS_UINT32 ulAlgId, const GS_UCHAR* pucKey, GS_UINT32 ulKeyLen, const GS_UCHAR* pucIV,
    GS_UINT32 ulIVLen, GS_UCHAR* pucPlainText, GS_UINT32 ulPlainLen, GS_UCHAR* pucCipherText, GS_UINT32* pulCLen);
GS_UINT32 CRYPT_hmac(GS_UINT32 ulAlgType, const GS_UCHAR* pucKey, GS_UINT32 upucKeyLen, const GS_UCHAR* pucData,
    GS_UINT32 ulDataLen, GS_UCHAR* pucDigest, GS_UINT32* pulDigestLen);

/* for stored cipherfile buffer */
RandkeyFile g_rand_file_content[CIPHER_TYPE_MAX];
CipherkeyFile g_cipher_file_content[CIPHER_TYPE_MAX];

bool init_vector_random(GS_UCHAR* init_vector, size_t vector_len)
{
    errno_t errorno = EOK;
    int retval = 0;
    GS_UCHAR random_vector[RANDOM_LEN] = {0};

    retval = RAND_priv_bytes(random_vector, RANDOM_LEN);
    if (retval != 1) {
        errorno = memset_s(random_vector, RANDOM_LEN, '\0', RANDOM_LEN);
        securec_check_c(errorno, "", "");
        (void)fprintf(stderr, _("generate random initial vector failed, errcode:%d\n"), retval);
        return false;
    }

    errorno = memcpy_s(init_vector, vector_len, random_vector, RANDOM_LEN);
    securec_check_c(errorno, "", "");
    errorno = memset_s(random_vector, RANDOM_LEN, '\0', RANDOM_LEN);
    securec_check_c(errorno, "", "");
    return true;
}

/* check whether the input password(for key derivation) meet the requirements of the length and complexity */
bool check_input_password(const char* password)
{
    int key_input_len = 0;
    int kinds[PASSWD_KINDS] = {0};
    int kinds_num = 0;
    const char* ptr = NULL;
    int i = 0;
    if (password == NULL) {
        (void)fprintf(stderr, _("Invalid password,please check it\n"));
        return false;
    }
    key_input_len = strlen(password);
    if (key_input_len < MIN_KEY_LEN) {
        (void)fprintf(stderr, _("Invalid password,it must contain at least eight characters\n"));
        return false;
    }
    if (key_input_len > MAX_KEY_LEN) {
        (void)fprintf(stderr, _("Invalid password,the length exceed %d\n"), MAX_KEY_LEN);
        return false;
    }
    ptr = password;
    while (*ptr != '\0') {
        if (*ptr >= 'A' && *ptr <= 'Z') {
            kinds[0]++;
        } else if (*ptr >= 'a' && *ptr <= 'z') {
            kinds[1]++;
        } else if (*ptr >= '0' && *ptr <= '9') {
            kinds[2]++;
        } else if (IsSpecialCharacter(*ptr)) {
            kinds[3]++;
        }
        ptr++;
    }
    for (i = 0; i < PASSWD_KINDS; ++i) {
        if (kinds[i] > 0) {
            kinds_num++;
        }
    }
    if (kinds_num < PASSWD_KINDS - 1) {
        (void)fprintf(stderr, _("Invalid password,it must contain at least three kinds of characters\n"));
        return false;
    }
    return true;
}

/* encrypt the plain text to cipher text */
bool EncryptInputKey(GS_UCHAR* pucPlainText, GS_UCHAR* initrand, GS_UCHAR* keySaltVector, GS_UCHAR* encryptVector,
    GS_UCHAR* pucCipherText, GS_UINT32* pulCLen)
{
    GS_UCHAR deriver_key[KEDF2_KEY_SIZE] = {0};
    GS_UINT32 retval = 0;
    GS_UINT32 ulPlainLen = 0;
    errno_t rc = EOK;

    if (pucPlainText == NULL) {
        (void)fprintf(stderr, _("invalid plain text, please check it!\n"));
        return false;
    }
    ulPlainLen = strlen((const char*)pucPlainText);

    /* use PKCS5 HMAC sha256 to dump the key for encryption */
    retval = PKCS5_PBKDF2_HMAC((const char*)initrand,
        RANDOM_LEN,
        keySaltVector,
        RANDOM_LEN,
        ITERATE_TIMES,
        EVP_sha256(),
        KEDF2_KEY_SIZE,
        deriver_key);
    if (retval != 1) {
        rc = memset_s(deriver_key, KEDF2_KEY_SIZE, 0, KEDF2_KEY_SIZE);
        securec_check_c(rc, "\0", "\0");
        (void)fprintf(stderr, _("generate the derived key failed, errcode:%u\n"), retval);
        return false;
    }

    retval = CRYPT_encrypt(NID_aes_128_cbc,
        deriver_key,
        KEDF2_KEY_SIZE,
        encryptVector,
        RANDOM_LEN,
        pucPlainText,
        ulPlainLen,
        pucCipherText,
        pulCLen);
    if (retval != 0) {
        rc = memset_s(deriver_key, KEDF2_KEY_SIZE, 0, KEDF2_KEY_SIZE);
        securec_check_c(rc, "\0", "\0");
        (void)fprintf(stderr, _("encrypt plain text to cipher text failed, errcode:%u\n"), retval);
        return false;
    }

    rc = memset_s(deriver_key, KEDF2_KEY_SIZE, 0, KEDF2_KEY_SIZE);
    securec_check_c(rc, "\0", "\0");
    return true;
}

/* decrypt the cipher text to plain text */
bool DecryptInputKey(GS_UCHAR* pucCipherText, GS_UINT32 ulCLen, GS_UCHAR* initrand, GS_UCHAR* initVector,
    GS_UCHAR* decryptVector, GS_UCHAR* pucPlainText, GS_UINT32* pulPLen)
{
    GS_UINT32 retval = 0;
    GS_UCHAR decrypt_key[RANDOM_LEN] = {0};
    errno_t rc = EOK;

    if (pucCipherText == NULL) {
        (void)fprintf(stderr, _("invalid cipher text, please check it!\n"));
        return false;
    }

    /* get the decrypt key value */
    retval = PKCS5_PBKDF2_HMAC((const char*)initrand,
        RANDOM_LEN,
        initVector,
        RANDOM_LEN,
        ITERATE_TIMES,
        EVP_sha256(),
        RANDOM_LEN,
        decrypt_key);
    if (retval != 1) {
        rc = memset_s(decrypt_key, RANDOM_LEN, 0, RANDOM_LEN);
        securec_check_c(rc, "\0", "\0");
        (void)fprintf(stderr, _("generate the derived key failed, errcode:%u\n"), retval);
        return false;
    }

    /*decrypt the cipher*/
    retval = CRYPT_decrypt(NID_aes_128_cbc,
        decrypt_key,
        RANDOM_LEN,
        decryptVector,
        RANDOM_LEN,
        pucCipherText,
        ulCLen,
        pucPlainText,
        pulPLen);

    if (retval != 0) {
        rc = memset_s(decrypt_key, RANDOM_LEN, 0, RANDOM_LEN);
        securec_check_c(rc, "\0", "\0");
        (void)fprintf(stderr, _("decrypt cipher text to plain text failed, errcode:%u\n"), retval);
        return false;
    }

    rc = memset_s(decrypt_key, RANDOM_LEN, 0, RANDOM_LEN);
    securec_check_c(rc, "\0", "\0");
    return true;
}

/* copy the cipher text to CipherkeyFile */
static void CopyCipher(GS_UCHAR* cipher_str, /* points to cipher string */
    GS_UINT32 cipher_len,                    /* cipher string length */
    GS_UCHAR* key_salt,                      /* salt vector used to derive key */
    GS_UCHAR* vector_salt,                   /* salt vector used to encrypt plaintext */
    CipherkeyFile* content)                  /* file content buffer, be careful caller make sure it's not null */
{
    errno_t rc = EOK;

    rc = memcpy_s(content->cipherkey, CIPHER_LEN + 1, cipher_str, CIPHER_LEN);
    securec_check_c(rc, "\0", "\0");

    rc = memcpy_s(content->key_salt, RANDOM_LEN + 1, key_salt, RANDOM_LEN);
    securec_check_c(rc, "\0", "\0");

    rc = memcpy_s(content->vector_salt, RANDOM_LEN + 1, vector_salt, RANDOM_LEN);
    securec_check_c(rc, "\0", "\0");

    /* generate the crc value to protect the value in case someone modify it */
    INIT_CRC32(content->crc);
    COMP_CRC32(content->crc, (char*)content, offsetof(CipherkeyFile, crc));
    FIN_CRC32(content->crc);
}

/* copy the cipher text to RandkeyFile */
static void CopyRand(GS_UCHAR* rand_str, RandkeyFile* randfile)
{
    errno_t rc = EOK;

    /* append rand_key to the front part of cipher text */
    rc = memcpy_s(randfile->randkey, RANDOM_LEN + 1, rand_str, RANDOM_LEN);
    securec_check_c(rc, "\0", "\0");

    /* generate the crc value to protect the value in case someone modify it */
    INIT_CRC32(randfile->crc);
    COMP_CRC32(randfile->crc, (char*)randfile, offsetof(RandkeyFile, crc));
    FIN_CRC32(randfile->crc);
}

/* check the crc of rand file storing the randtext */
bool RandFileIsValid(RandkeyFile* randfile)
{
    pg_crc32 rand_crc;

    INIT_CRC32(rand_crc);
    COMP_CRC32(rand_crc, (char*)randfile, offsetof(RandkeyFile, crc));

    FIN_CRC32(rand_crc);

    if (!EQ_CRC32(randfile->crc, rand_crc)) {
        (void)fprintf(stderr, _("CRC checksum does not match value stored in file, maybe the rand file is corrupt\n"));
        return false;
    }

    return true;
}

/* check the crc of cipher file storing the ciphertext */
bool CipherFileIsValid(CipherkeyFile* cipher)
{
    pg_crc32 cipher_crc;

    INIT_CRC32(cipher_crc);
    COMP_CRC32(cipher_crc, (char*)cipher, offsetof(CipherkeyFile, crc));

    FIN_CRC32(cipher_crc);

    if (!EQ_CRC32(cipher->crc, cipher_crc)) {
        (void)fprintf(
            stderr, _("CRC checksum does not match value stored in file, maybe the cipher file is corrupt\n"));
        return false;
    }

    return true;
}

/* Read the contents of the file to buffer */
bool ReadContentFromFile(const char* filename, void* content, size_t csize)
{
    FILE* pfRead = NULL;
    int cnt = 0;
    /*open and read file*/
    if ((pfRead = fopen(filename, "rb")) == NULL) {
        (void)fprintf(stderr, _("could not open file \"%s\": %s\n"), filename, gs_strerror(errno));
        return false;
    }
    cnt = fread(content, csize, 1, pfRead);
    if (cnt < 0) {
        fclose(pfRead);
        (void)fprintf(stderr, _("could not read file \"%s\": %s\n"), filename, gs_strerror(errno));
        return false;
    }
    if (fclose(pfRead)) {
        (void)fprintf(stderr, _("could not close file \"%s\": %s\n"), filename, gs_strerror(errno));
        return false;
    }

    return true;
}

/* write data in buffer to file */
static bool WriteContentToFile(const char* filename, const void* content, size_t csize)
{
    FILE* pfWrite = NULL;
    int ret = 0;

    /*open and write file*/
    if ((pfWrite = fopen(filename, "wb")) == NULL) {
        (void)fprintf(stderr, _("could not open file \"%s\" for writing: %s\n"), filename, gs_strerror(errno));
        return false;
    }
    if (fwrite(content, csize, 1, pfWrite) != 1) {
        fclose(pfWrite);
        (void)fprintf(stderr, _("could not write file \"%s\": %s\n"), filename, gs_strerror(errno));
        return false;
    }

#ifdef WIN32
    ret = _chmod(filename, 0400);
#else
    ret = fchmod(pfWrite->_fileno, 0400);
#endif

    if (fclose(pfWrite)) {
        (void)fprintf(stderr, _("could not close file \"%s\": %s\n"), filename, gs_strerror(errno));
        return false;
    }

    if (-1 == ret) {
        (void)fprintf(stderr, _("could not set permissions of file \"%s\": %s\n"), filename, gs_strerror(errno));
        return false;
    }
    return true;
}

/* Judge if the KeyMode is legal */
static bool isModeExists(KeyMode mode)
{
    if (mode != SERVER_MODE && mode != CLIENT_MODE && mode != HADR_MODE &&
        mode != OBS_MODE && mode != SOURCE_MODE && mode != GDS_MODE &&
        mode != USER_MAPPING_MODE) {
#ifndef ENABLE_LLT
        (void)fprintf(stderr, _("AK/SK encrypt/decrypt encounters invalid key mode.\n"));
        return false;
#endif
    }
    return true;
}

/*
 * @Description : reset cipher file content.
 * @in cipher_file_context : the file need to be cleared.
 * @return		: void.
 */
void ClearCipherKeyFile(CipherkeyFile* cipher_file_content)
{
    if (cipher_file_content != NULL) {
        errno_t rc = EOK;
        rc = memset_s((char*)(cipher_file_content->cipherkey), CIPHER_LEN + 1, 0, CIPHER_LEN + 1);
        securec_check_c(rc, "", "");
        rc = memset_s((char*)(cipher_file_content->key_salt), RANDOM_LEN + 1, 0, RANDOM_LEN + 1);
        securec_check_c(rc, "", "");
        rc = memset_s((char*)(cipher_file_content->vector_salt), RANDOM_LEN + 1, 0, RANDOM_LEN + 1);
        securec_check_c(rc, "", "");
    }

    return;
}

/*
 * @Description : reset rand file content.
 * @in rand_file_context : the file need to be cleared.
 * @return		: void.
 */
void ClearRandKeyFile(RandkeyFile* rand_file_content)
{
    if (rand_file_content != NULL) {
        errno_t rc = EOK;
        rc = memset_s((char*)(rand_file_content->randkey), CIPHER_LEN + 1, 0, CIPHER_LEN + 1);
        securec_check_c(rc, "", "");
    }

    return;
}

/* encrypt the input key,and write the cipher to file */
static bool gen_cipher_file(KeyMode mode, /* SERVER_MODE or CLIENT_MODE or OBS_MODE or SOURCE_MODE */
    GS_UCHAR* init_rand, GS_UCHAR server_vector[], GS_UCHAR client_vector[], const char* plain_key,
    const char* user_name, const char* datadir, const char* preStr)
{
    int ret = 0;

    char cipherkeyfile[MAXPGPATH] = {0x00};
    GS_UCHAR encrypt_rand[RANDOM_LEN] = {0};
    GS_UCHAR ciphertext[CIPHER_LEN + CIPHER_LEN] = {0};
    GS_UCHAR* key_salt = NULL;

    GS_UINT32 cipherlen = 0;
    int retval = 0;

    CipherkeyFile cipher_file_content;

    /* check whether the key mode is valid */
    if (!isModeExists(mode)) {
#ifndef ENABLE_LLT
        goto RETURNFALSE;
#endif
    }

    /* generate init rand key */
    retval = RAND_priv_bytes(encrypt_rand, RANDOM_LEN);
    if (retval != 1) {
#ifndef ENABLE_LLT
        (void)fprintf(stderr, _("generate random key failed,errcode:%d\n"), retval);
        goto RETURNFALSE;
#endif
    }

    if (mode == SERVER_MODE) /* in server_mode,write the file with name like: server% */
    {
        ret = snprintf_s(cipherkeyfile, MAXPGPATH, MAXPGPATH - 1, "%s/server%s", datadir, CIPHER_KEY_FILE);
        securec_check_ss_c(ret, "\0", "\0");
        key_salt = server_vector;
    } else if (mode == OBS_MODE) /* in OBS_mode,write the file with name like: obsserver% */
    {
        if (NULL == preStr) {
            ret = snprintf_s(cipherkeyfile, MAXPGPATH, MAXPGPATH - 1, "%s/obsserver%s", datadir, CIPHER_KEY_FILE);
        } else {
            ret = snprintf_s(cipherkeyfile, MAXPGPATH, MAXPGPATH - 1, "%s/%s%s", datadir, preStr, CIPHER_KEY_FILE);
        }
        securec_check_ss_c(ret, "\0", "\0");
        key_salt = server_vector;
    } else if (mode == SOURCE_MODE) /* in SOURCE_MODE,write the file with name like: datasource% */
    {
        ret = snprintf_s(cipherkeyfile, MAXPGPATH, MAXPGPATH - 1, "%s/datasource%s", datadir, CIPHER_KEY_FILE);
        securec_check_ss_c(ret, "\0", "\0");
        key_salt = server_vector;
    }
    /*in client_mode,check with the user name is appointed.if so, write the files naming
                                    with %user_name%,if not,write files naming with client%*/
    else if (mode == CLIENT_MODE) {
        if (NULL == user_name) {
            ret = snprintf_s(cipherkeyfile, MAXPGPATH, MAXPGPATH - 1, "%s/client%s", datadir, CIPHER_KEY_FILE);
            securec_check_ss_c(ret, "\0", "\0");
        } else {
            ret = snprintf_s(cipherkeyfile, MAXPGPATH, MAXPGPATH - 1, "%s/%s%s", datadir, user_name, CIPHER_KEY_FILE);
            securec_check_ss_c(ret, "\0", "\0");
        }
        key_salt = client_vector;
    } else {
        return false;
    }

    if (!EncryptInputKey((GS_UCHAR*)plain_key, init_rand, key_salt, encrypt_rand, ciphertext, &cipherlen)) {
#ifndef ENABLE_LLT
        goto RETURNFALSE;
#endif
    }

    /*
     * Write ciphertext and encrypt rand vector to cipher_file_content
     * and generate cipher_file_context's CRC and append to the end of
     * cipher_file_context.
     */
    CopyCipher(ciphertext, cipherlen, key_salt, encrypt_rand, &cipher_file_content);

    if (!WriteContentToFile(cipherkeyfile, (const void*)&cipher_file_content, sizeof(CipherkeyFile))) {
#ifndef ENABLE_LLT
        goto RETURNFALSE;
#endif
    }

    /*
     * Change the privileges: include read & write
     * Note: it should be checked by OM tool: gs_ec.
     */
    if (mode == SOURCE_MODE || mode == CLIENT_MODE || mode == SERVER_MODE || mode == OBS_MODE) {
#ifdef WIN32
        ret = _chmod(cipherkeyfile, 0600);
#else
        ret = chmod(cipherkeyfile, 0600);
#endif
        if (ret != 0) {
#ifndef ENABLE_LLT
            (void)fprintf(
                stderr, _("could not set permissions of file \"%s\": %s\n"), cipherkeyfile, gs_strerror(errno));
            goto RETURNFALSE;
#endif
        }
    }

    /*
     * Empty ciphertext and cipher_file_content.
     * This is useful. Although ciphertext and cipher_file_content is in stack,
     * we should manually clear them.
     */
    ret = memset_s(ciphertext, (CIPHER_LEN + CIPHER_LEN), 0, (CIPHER_LEN + CIPHER_LEN));
    securec_check_c(ret, "\0", "\0");
    ret = memset_s((char*)&cipher_file_content, sizeof(CipherkeyFile), 0, sizeof(CipherkeyFile));
    securec_check_c(ret, "\0", "\0");

    return true;

#ifndef ENABLE_LLT
RETURNFALSE:
    /*
     * Empty ciphertext and cipher_file_content.
     * This is useful. Although ciphertext and cipher_file_content is in stack,
     * we should manually clear them.
     */
    ret = memset_s(ciphertext, (CIPHER_LEN + CIPHER_LEN), 0, (CIPHER_LEN + CIPHER_LEN));
    securec_check_c(ret, "\0", "\0");
    ret = memset_s((void*)&cipher_file_content, sizeof(CipherkeyFile), 0, sizeof(CipherkeyFile));
    securec_check_c(ret, "\0", "\0");

    return false;
#endif
}

/* write encryption factor to files */
static bool gen_rand_file(
    KeyMode mode, GS_UCHAR* init_rand, const char* user_name, const char* datadir, const char* preStr)
{
    int ret;
    char randfile[MAXPGPATH] = {0x00};
    RandkeyFile rand_file_content;
    FILE* pfWrite = NULL;

    /*check whether the key mode is valid*/
    if (!isModeExists(mode)) {
#ifndef ENABLE_LLT
        goto RETURNFALSE;
#endif
    }

    /*in server_mode,write the files naming with server%*/
    if (mode == SERVER_MODE) {
        ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/server%s", datadir, RAN_KEY_FILE);
        securec_check_ss_c(ret, "\0", "\0");
    } else if (mode == OBS_MODE) {
        if (preStr == NULL) {
            ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/obsserver%s", datadir, RAN_KEY_FILE);
        } else {
            ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/%s%s", datadir, preStr, RAN_KEY_FILE);
        }
        securec_check_ss_c(ret, "\0", "\0");
    } else if (mode == SOURCE_MODE) {
        ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/datasource%s", datadir, RAN_KEY_FILE);
        securec_check_ss_c(ret, "\0", "\0");
    }
    /*in client_mode,check with the user name is appointed.if so, write the files naming
    with %user_name%,if not,write files naming with client%*/
    else if (mode == CLIENT_MODE) {
        if (user_name == NULL) {
            ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/client%s", datadir, RAN_KEY_FILE);
            securec_check_ss_c(ret, "\0", "\0");
        } else {
            ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/%s%s", datadir, user_name, RAN_KEY_FILE);
            securec_check_ss_c(ret, "\0", "\0");
        }
    }
    CopyRand(init_rand, &rand_file_content);
    if (!WriteContentToFile(randfile, (const void*)&rand_file_content, sizeof(RandkeyFile))) {
#ifndef ENABLE_LLT
        goto RETURNFALSE;
#endif
    }

    /*
     * Change the privileges: include read & write
     * Note: it should be checked by OM tool: gs_ec.
     */
    if (mode == SOURCE_MODE || mode == CLIENT_MODE || mode == SERVER_MODE || mode == OBS_MODE) {
        /*open and write file*/
        if ((pfWrite = fopen(randfile, "r")) == NULL) {
            (void)fprintf(stderr, _("could not open file \"%s\" for writing: %s\n"), randfile, gs_strerror(errno));
            return false;
        }

#ifdef WIN32
        ret = _chmod(randfile, 0600);
#else
        ret = fchmod(pfWrite->_fileno, 0600);
#endif

        if (fclose(pfWrite)) {
            (void)fprintf(stderr, _("could not close file \"%s\": %s\n"), randfile, gs_strerror(errno));
            return false;
        }

        if (ret != 0) {
            (void)fprintf(stderr, _("could not set permissions of file \"%s\": %s\n"), randfile, gs_strerror(errno));
            return false;
        }
    }

    /*
     * Empty rand_file_content.
     * This is useful. Although rand_file_content is in stack,
     * we should manually clear it.
     */
    ret = memset_s((void*)&rand_file_content, sizeof(RandkeyFile), 0, sizeof(RandkeyFile));
    securec_check_c(ret, "\0", "\0");

    return true;

#ifndef ENABLE_LLT
RETURNFALSE:
    /*
     * Empty rand_file_content.
     * This is useful. Although rand_file_content is in stack,
     * we should manually clear it.
     */
    ret = memset_s((void*)&rand_file_content, sizeof(RandkeyFile), 0, sizeof(RandkeyFile));
    securec_check_c(ret, "\0", "\0");

    return false;
#endif
}

/*
 * generate the files of cipher text and encryption factor
 *     Notice: preStr is only used by OBS
 */
int gen_cipher_rand_files(
    KeyMode mode, const char* plain_key, const char* user_name, const char* datadir, const char* preStr)
{
    int retval = 0;
    GS_UCHAR init_rand[RANDOM_LEN] = {0};
    GS_UCHAR server_vector[RANDOM_LEN] = {'\0'};
    GS_UCHAR client_vector[RANDOM_LEN] = {'\0'};

    retval = RAND_priv_bytes(init_rand, RANDOM_LEN);
    if (retval != 1) {
        (void)fprintf(stderr, _("generate random key failed,errcode:%d\n"), retval);
        return 1;
    }

    if (mode == SERVER_MODE || mode == SOURCE_MODE || mode == OBS_MODE) {
        init_vector_random(server_vector, RANDOM_LEN);
    } else if (mode == CLIENT_MODE) {
        init_vector_random(client_vector, RANDOM_LEN);
    } else {
        (void)fprintf(stderr, _("generate cipher file failed, unknown mode:%d.\n"), mode);
        return 1;
    }

    if (!gen_cipher_file(mode, init_rand, server_vector, client_vector, plain_key, user_name, datadir, preStr)) {
#ifndef ENABLE_LLT
        (void)fprintf(stderr, _("generate cipher file failed.\n"));
        return 1;
#endif
    }
    if (!gen_rand_file(mode, init_rand, user_name, datadir, preStr)) {
#ifndef ENABLE_LLT
        (void)fprintf(stderr, _("generate random parameter file failed.\n"));
        return 1;
#endif
    }

    return 0;
}

/* check whether the character is special characters */
static bool IsSpecialCharacter(char ch)
{
    const char* spec_letters = "~!@#$%^&*()-_=+\\|[{}];:,<.>/?";
    const char* ptr = spec_letters;
    while (*ptr != '\0') {
        if (*ptr == ch) {
            return true;
        }
        ptr++;
    }
    return false;
}

/*
 * @Brief	: long check_certificate_time()
 * @Description	: check the time of the certificate.
 * @return	: return the number of seconds when the certificate expires less than alarm_days.
 */
long check_certificate_time(const SSL_CTX* SSL_context, const int alarm_days)
{
    if (SSL_context == NULL) {
        return 0;
    }
    X509* pstCertificate = SSL_CTX_get0_certificate(SSL_context);
    if (pstCertificate != NULL) {
        int pday = 0;
        int psec = 0;
        const ASN1_TIME* notafter = NULL;

        /* Get the notafter time form this certificate.*/
        notafter = X509_get0_notAfter(pstCertificate);
        if (notafter == NULL) {
            return 0;
        }
        int retval = ASN1_TIME_diff(&pday, &psec, NULL, notafter);
        if (retval == 0) {
            return 0;
        }

        /* Compare localTime with notafter.*/
        if (pday < alarm_days && pday >= 0) {
            int diff = pday * 24 * 60 * 60 + psec;
            return diff;
        }
    }
    return 0;
}

/* get_evp_cipher_by_id: if you need to be use,you can add some types */
const EVP_CIPHER* get_evp_cipher_by_id(GS_UINT32 ulAlgType)
{
    const EVP_CIPHER* cipher = NULL;
    switch (ulAlgType & 0xFFFF) {
        case NID_aes_128_cbc:
            cipher = EVP_aes_128_cbc();
            break;
        case NID_aes_256_cbc:
            cipher = EVP_aes_256_cbc();
            break;
        case NID_undef:
            cipher = EVP_enc_null();
            break;
        default:
            break;
    }
    return cipher;
}

/* get_evp_md_by_id: if you need to be use,you can add some types */
const EVP_MD* get_evp_md_by_id(GS_UINT32 ulAlgType)
{
    const EVP_MD* md = NULL;
    switch (ulAlgType & 0xFFFF) {
        case NID_sha1:
        case NID_hmac_sha1:
            md = EVP_sha1();
            break;
        case NID_sha256:
        case NID_hmacWithSHA256:
            md = EVP_sha256();
            break;
        case NID_undef:
            md = EVP_md_null();
            break;
        default:
            break;
    }
    return md;
}

/*
 * @Brief        : GS_UINT32 CRYPT_encrypt()
 * @Description  : encrypts plain text to cipher text using encryption algorithm.
 *		  It creates symmetric context by creating algorithm object, padding object,
 *		  opmode object.After encryption, symmetric context needs to be freed.
 * @return       : success: 0, failed: 1.
 *
 * @Notes	: the last block is not full. so here need to padding the last block.(the block size is an algorithm-related
 *parameter) 1.here *ISO/IEC 7816-4* padding method is adoptted: the first byte uses "0x80" to padding ,and the others
 *uses "0x00". Example(in the following example the block size is 8 bytes): when the last block is not full: The last
 *block has 4 bytes, so four bytes need to be filled
 *	 	 	 	 ... | DD DD DD DD DD DD DD DD | DD DD DD DD 80 00 00 00 |
 *			when the last block is full: here need to add a new block
 *				 ... | DD DD DD DD DD DD DD DD | 80 00 00 00 00 00 00 00 |
 *		  2.Default padding method of OPENSSL(this method is closed at here): Each byte is filled with the number of
 *remaining bytes Example(in the following example the block size is 8 bytes): when the last block is not full: The last
 *block has 4 bytes, so four bytes need to be filled
 *                                ... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
 *                       when the last block is full: here need to add a new block
 *                                ... | DD DD DD DD DD DD DD DD | 08 08 08 08 08 08 08 08 |
 */
GS_UINT32 CRYPT_encrypt(GS_UINT32 ulAlgId, const GS_UCHAR* pucKey, GS_UINT32 ulKeyLen, const GS_UCHAR* pucIV,
    GS_UINT32 ulIVLen, GS_UCHAR* pucPlainText, GS_UINT32 ulPlainLen, GS_UCHAR* pucCipherText, GS_UINT32* pulCLen)
{
    EVP_CIPHER_CTX* ctx = NULL;
    const EVP_CIPHER* cipher = NULL;
    int enc_num = 0;
    int blocksize = 0;
    int padding_size = 0;
    int nInbufferLen = 0;
    errno_t rc = EOK;
    unsigned char* pchInbuffer = NULL;
    if (pucPlainText == NULL) {
        (void)fprintf(stderr, ("invalid plain text,please check it!\n"));
        return 1;
    }
    cipher = get_evp_cipher_by_id(ulAlgId);
    if (cipher == NULL) {
        (void)fprintf(stderr, ("invalid ulAlgType for cipher,please check it!\n"));
        return 1;
    }
    ctx = EVP_CIPHER_CTX_new();
    if (ctx == NULL) {
        (void)fprintf(stderr, ("ERROR in EVP_CIPHER_CTX_new:\n"));
        return 1;
    }
    EVP_CipherInit_ex(ctx, cipher, NULL, pucKey, pucIV, 1);

    /* open padding mode */
    EVP_CIPHER_CTX_set_padding(ctx, 1);

    /* handling the last block */
    blocksize = EVP_CIPHER_CTX_block_size(ctx);
    if (blocksize == 0) {
        (void)fprintf(stderr, ("invalid blocksize, ERROR in EVP_CIPHER_CTX_block_size\n"));
        EVP_CIPHER_CTX_free(ctx);
        return 1;
    }

    nInbufferLen = ulPlainLen % blocksize;
    padding_size = blocksize - nInbufferLen;
    pchInbuffer = (unsigned char*)OPENSSL_malloc(blocksize);
    if (pchInbuffer == NULL) {
        (void)fprintf(stderr, _("malloc failed\n"));
        EVP_CIPHER_CTX_free(ctx);
        return 1;
    }
    /* the first byte uses "0x80" to padding ,and the others uses "0x00" */
    rc = memcpy_s(pchInbuffer, blocksize, pucPlainText + (ulPlainLen - nInbufferLen), nInbufferLen);
    securec_check_c(rc, "\0", "\0");
    rc = memset_s(pchInbuffer + nInbufferLen, padding_size, 0, padding_size);
    securec_check_c(rc, "\0", "\0");
    pchInbuffer[nInbufferLen] = 0x80;

    /* close padding mode, default padding method of OPENSSL is forbidden */
    EVP_CIPHER_CTX_set_padding(ctx, 0);

    if (!EVP_EncryptUpdate(ctx, pucCipherText, &enc_num, pucPlainText, ulPlainLen - nInbufferLen)) {
        (void)fprintf(stderr, ("ERROR in EVP_EncryptUpdate\n"));
        goto err;
    }
    *pulCLen = enc_num;
    if (!EVP_EncryptUpdate(ctx, pucCipherText + enc_num, &enc_num, pchInbuffer, blocksize)) {
        (void)fprintf(stderr, ("ERROR in EVP_EncryptUpdate\n"));
        goto err;
    }
    *pulCLen += enc_num;
    if (!EVP_EncryptFinal(ctx, pucCipherText + *pulCLen, &enc_num)) {
        (void)fprintf(stderr, ("ERROR in EVP_EncryptUpdate\n"));
        goto err;
    }
    *pulCLen += enc_num;
    rc = memset_s(pchInbuffer, blocksize, 0, blocksize);
    securec_check_c(rc, "\0", "\0");
    OPENSSL_free(pchInbuffer);
    EVP_CIPHER_CTX_free(ctx);
    return 0;

err:
    rc = memset_s(pchInbuffer, blocksize, 0, blocksize);
    securec_check_c(rc, "\0", "\0");
    OPENSSL_free(pchInbuffer);
    EVP_CIPHER_CTX_free(ctx);
    return 1;
}

/*
 * @Brief        : GS_UINT32 CRYPT_decrypt()
 * @Description  : decrypts cipher text to plain text using decryption algorithm.
 *		  It creates symmetric context by creating algorithm object, padding object,
 *		  opmode object. After decryption, symmetric context needs to be freed.
 * @return       : success: 0, failed: 1.
 *
 * @Notes        : the last block is not full. so here need to padding the last block.(the block size is an
 *algorithm-related parameter) 1.here *ISO/IEC 7816-4* padding method is adoptted: the first byte uses "0x80" to padding
 *,and the others uses "0x00". Example(in the following example the block size is 8 bytes): when the last block is not
 *full: The last block has 4 bits,so padding is required for 4 bytes
 *                                ... | DD DD DD DD DD DD DD DD | DD DD DD DD 80 00 00 00 |
 *                       when the last block is full: here need to add a new block
 *                                ... | DD DD DD DD DD DD DD DD | 80 00 00 00 00 00 00 00 |
 */
GS_UINT32 CRYPT_decrypt(GS_UINT32 ulAlgId, const GS_UCHAR* pucKey, GS_UINT32 ulKeyLen, const GS_UCHAR* pucIV,
    GS_UINT32 ulIVLen, GS_UCHAR* pucCipherText, GS_UINT32 ulCLen, GS_UCHAR* pucPlainText, GS_UINT32* pulPLen)
{
    EVP_CIPHER_CTX* ctx = NULL;
    const EVP_CIPHER* cipher = NULL;
    int dec_num = 0;
    unsigned int blocksize;
    unsigned int oLen;

    if (pucCipherText == NULL) {
        (void)fprintf(stderr, ("invalid plain text,please check it!\n"));
        return 1;
    }
    cipher = get_evp_cipher_by_id(ulAlgId);
    if (cipher == NULL) {
        (void)fprintf(stderr, ("invalid ulAlgType for cipher,please check it!\n"));
        return 1;
    }
    ctx = EVP_CIPHER_CTX_new();
    if (ctx == NULL) {
        (void)fprintf(stderr, ("ERROR in EVP_CIPHER_CTX_new:\n"));
        return 1;
    }
    EVP_CipherInit_ex(ctx, cipher, NULL, pucKey, pucIV, 0);
    EVP_CIPHER_CTX_set_padding(ctx, 0);
    if (!EVP_DecryptUpdate(ctx, pucPlainText, &dec_num, pucCipherText, ulCLen)) {
        (void)fprintf(stderr, ("ERROR in EVP_DecryptUpdate\n"));
        goto err;
    }
    *pulPLen = dec_num;
    if (!EVP_DecryptFinal(ctx, pucPlainText + dec_num, &dec_num)) {
        (void)fprintf(stderr, ("ERROR in EVP_DecryptFinal\n"));
        goto err;
    }
    *pulPLen += dec_num;

    /* padding bytes of the last block need to be removed */
    blocksize = EVP_CIPHER_CTX_block_size(ctx);
    oLen = (*pulPLen) - 1;
    while (*(pucPlainText + oLen) == 0) {
        oLen--;
    }
    if (oLen >= ((*pulPLen) - blocksize) && *(pucPlainText + oLen) == 0x80) {
        (*pulPLen) = oLen;
    } else {
        goto err;
    }

    pucPlainText[oLen] = '\0';
    EVP_CIPHER_CTX_free(ctx);
    return 0;

err:
    EVP_CIPHER_CTX_free(ctx);
    return 1;
}

/*
 * @Brief		: GS_UINT32 CRYPT_hmac()
 * @Description	: computes hmac of a given data block. Calls init, update, and final.
 * 		  This function is used when data is present all at once. There is no need of calling
 * 		  Init, Update, Final and MAC can be calculated in one go. Context is not needed here.
 * @return	: success:1, failed:0.
 */
GS_UINT32 CRYPT_hmac(GS_UINT32 ulAlgType, const GS_UCHAR* pucKey, GS_UINT32 upucKeyLen, const GS_UCHAR* pucData,
    GS_UINT32 ulDataLen, GS_UCHAR* pucDigest, GS_UINT32* pulDigestLen)
{
    const EVP_MD* evp_md = get_evp_md_by_id(ulAlgType);
    if (evp_md == NULL) {
        return 1;
    }
#ifndef WIN32
    if (!HMAC(evp_md, pucKey, (int)upucKeyLen, pucData, ulDataLen, pucDigest, pulDigestLen)) {
        return 1;
    }
#else
    if (!HMAC(evp_md, pucKey, (int)upucKeyLen, pucData, ulDataLen, pucDigest, (unsigned int*)pulDigestLen)) {
        return 1;
    }
#endif
    return 0;
}
