* Copyright (c) 2021 Huawei Technologies Co.,Ltd.
*
* CM 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.
* -------------------------------------------------------------------------
*
* cs_ssl.cpp
*
*
* IDENTIFICATION
* src/cm_communication/cm_protocol/cs_ssl.cpp
*
* -------------------------------------------------------------------------
*/
#include "cs_ssl.h"
#include <fcntl.h>
#include <sys/stat.h>
#include "openssl/ssl.h"
#include "openssl/err.h"
#include "openssl/x509v3.h"
#include "cm_spinlock.h"
#include "cm_error.h"
#include "securec.h"
#include "cm_cipher.h"
#include "cm_elog.h"
#include "cm_misc_base.h"
#include "cm_text.h"
#include "cm_debug.h"
#ifdef __cplusplus
extern "C" {
#endif
const int SSL_VERIFY_DEPTH = 10;
status_t cm_cs_tcp_wait(tcp_link_t *link, uint32 wait_for, int32 timeout, bool *ready);
int32 cm_cs_tcp_poll(struct pollfd *fds, uint32 nfds, int32 timeout);
void cm_cs_tcp_disconnect(tcp_link_t *link);
static status_t CmRealpathFile(const char *filename, char *realfile, uint32 real_path_len);
#define CM_SSL_FREE_CTX_AND_RETURN(err, ctx, ret) \
do { \
CM_THROW_ERROR(ERR_SSL_INIT_FAILED, cm_cs_ssl_init_err_string(err)); \
SSL_CTX_free(ctx); \
return ret; \
} while (0)
#define CM_SSL_EMPTY_STR_TO_NULL(str) \
if ((str) != NULL && (str)[0] == '\0') { \
(str) = NULL; \
}
#define SSL_CTX_PTR(ctx) ((SSL_CTX*)(ctx))
#define SSL_SOCK(sock) ((SSL*)(sock))
static spinlock_t g_ssl_init_lock = 0;
static volatile bool g_ssl_initialized = false;
static spinlock_t g_get_pem_passwd_lock = 0;
static const char * const g_sslDefaultCipherList = "ECDHE-ECDSA-AES256-GCM-SHA384:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"ECDHE-RSA-AES128-GCM-SHA256";
static const char * const g_sslTls13DefaultCipherList = "TLS_AES_256_GCM_SHA384:"
"TLS_CHACHA20_POLY1305_SHA256:"
"TLS_AES_128_GCM_SHA256:"
"TLS_AES_128_CCM_8_SHA256:"
"TLS_AES_128_CCM_SHA256";
const char *g_sslCipherNames[] = {
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES128-GCM-SHA256",
NULL
};
const char *g_sslTls13CipherNames[] = {
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_128_GCM_SHA256",
"TLS_AES_128_CCM_8_SHA256",
"TLS_AES_128_CCM_SHA256",
NULL
};
typedef enum en_ssl_init_error {
SSL_INITERR_NONE = 0,
SSL_INITERR_CERT,
SSL_INITERR_KEY,
SSL_INITERR_KEYPWD,
SSL_INITERR_NOMATCH,
SSL_INITERR_LOAD_CA,
SSL_INITERR_LOAD_CRL,
SSL_INITERR_CIPHERS,
SSL_INITERR_MEMFAIL,
SSL_INITERR_NO_USABLE_CTX,
SSL_INITERR_DHFAIL,
SSL_INITERR_VERIFY,
SSL_INITERR_VERSION_INVALID,
SSL_INITERR_SIGNATURE_ALG,
SSL_INITERR_SET_PURPOSE,
SSL_INITERR_LASTERR
} ssl_init_error_t;
static const char *g_ssl_error_string[] = {
"No error",
"Unable to get certificate",
"Unable to get private key",
"Private key password is invalid",
"Private key does not match the certificate public key",
"Load CA certificate file failed",
"Load Certificate revocation list failed",
"Failed to set ciphers to use",
"Create new SSL_CTX failed",
"SSL context is not usable without certificate and private key",
"SSL_CTX_SET_TEMPDH failed",
"SSL set verify mode or depth failed",
"TLS version is invalid",
"Failed to set signature algorithms",
"SSL_CTX_set_purpose failed",
"",
};
#define cs_close_socket close
bool ReadContentFromFile(const char* filename, void* content, size_t csize)
{
FILE *pfRead = fopen(filename, "rb");
if (pfRead == NULL) {
write_runlog(ERROR, "could not open file \"%s\": %s\n", filename, gs_strerror(errno));
return false;
}
size_t cnt = fread(content, csize, 1, pfRead);
if (cnt == 0) {
(void)fclose(pfRead);
write_runlog(ERROR, "could not read file \"%s\": %s\n", filename, gs_strerror(errno));
return false;
}
if (fclose(pfRead)) {
write_runlog(ERROR, "could not close file \"%s\": %s\n", filename, gs_strerror(errno));
return false;
}
return true;
}
time_t CmCurrentTime()
{
return time(NULL);
}
static const char *cm_cs_ssl_init_err_string(ssl_init_error_t err)
{
if (err > SSL_INITERR_NONE && err < SSL_INITERR_LASTERR) {
return g_ssl_error_string[err];
}
return g_ssl_error_string[0];
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
Get the last SSL error code and reason
*/
static const char *cm_cs_ssl_last_err_string(char *buf, uint32 size)
{
buf[0] = '\0';
const char *fstr = NULL;
ulong err = ERR_get_error_all(NULL, NULL, &fstr, NULL, NULL);
if (err) {
const char *rstr = ERR_reason_error_string(err);
if (snprintf_s(buf, size, size - 1, "error code = %lu, reason code = %d, ssl function = %s:%s ",
err, ERR_GET_REASON(err), (fstr ? fstr : "<null>"), (rstr ? rstr : "<null>")) == -1) {
return buf;
}
}
return buf;
}
static EVP_PKEY *get_pkey(void)
{
EVP_PKEY *dh_key = NULL;
EVP_PKEY_CTX *gctx = NULL;
OSSL_PARAM params[2];
params[0] = OSSL_PARAM_construct_utf8_string("group", (char*)"ffdhe3072", 0);
params[1] = OSSL_PARAM_construct_end();
gctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
if (gctx == NULL) {
return NULL;
}
if (EVP_PKEY_keygen_init(gctx) <= 0) {
EVP_PKEY_CTX_free(gctx);
return NULL;
}
if (EVP_PKEY_CTX_set_params(gctx, params) <= 0) {
EVP_PKEY_CTX_free(gctx);
return NULL;
}
if (EVP_PKEY_keygen(gctx, &dh_key) <= 0) {
EVP_PKEY_CTX_free(gctx);
EVP_PKEY_free(dh_key);
return NULL;
}
EVP_PKEY_CTX_free(gctx);
return dh_key;
}
#else
Get the last SSL error code and reason
*/
static const char *cm_cs_ssl_last_err_string(char *buf, uint32 size)
{
buf[0] = '\0';
ulong err = ERR_get_error();
if (err) {
const char *fstr = ERR_func_error_string(err);
const char *rstr = ERR_reason_error_string(err);
if (snprintf_s(buf, size, size - 1, "error code = %lu, reason code = %d, ssl function = %s:%s ",
err, ERR_GET_REASON(err), (fstr ? fstr : "<null>"), (rstr ? rstr : "<null>")) == -1) {
return buf;
}
}
return buf;
}
Diffie-Hellman key.
Generated using: >openssl dhparam -5 -C 3072
*/
static unsigned char g_dh3072_p[] = {
0x8D,
0xAF,
0xE5,
0xD7,
0x9A,
0x0A,
0x6A,
0x9A,
0xF0,
0x7F,
0xF2,
0xBD,
0xC2,
0xE5,
0x4B,
0x56,
0x07,
0x3F,
0x81,
0x02,
0x0E,
0x64,
0xC9,
0xA4,
0xA0,
0x49,
0x78,
0xE8,
0x4C,
0xD0,
0x8E,
0xD7,
0x1F,
0x71,
0xC7,
0x97,
0x3F,
0x5D,
0x42,
0x7D,
0x9F,
0xC3,
0x1C,
0x69,
0x8C,
0x81,
0xA3,
0x5C,
0x18,
0xCA,
0xED,
0xBC,
0xA0,
0x82,
0xD8,
0x01,
0x78,
0x6E,
0x64,
0xAC,
0x4A,
0xB2,
0x2C,
0x74,
0xC1,
0x8C,
0x66,
0x13,
0xBE,
0xC8,
0x7F,
0x32,
0x3D,
0x68,
0xA5,
0x12,
0x98,
0x86,
0x86,
0x3E,
0xDA,
0x20,
0x62,
0x5F,
0x47,
0xDC,
0x8B,
0xF6,
0xF4,
0x37,
0xF6,
0x0A,
0x9C,
0xF9,
0x10,
0xE9,
0x5D,
0x82,
0xE3,
0x41,
0xC1,
0x9C,
0x7A,
0xA3,
0x77,
0x54,
0x28,
0x6F,
0x76,
0xF6,
0xD5,
0x29,
0xCB,
0x8D,
0xA8,
0x18,
0x51,
0xCA,
0xE5,
0xB3,
0xF2,
0xCF,
0xDA,
0xB5,
0x26,
0x6E,
0xA5,
0xB5,
0x22,
0x12,
0x2C,
0xFC,
0x53,
0xAA,
0x16,
0xD8,
0x74,
0x79,
0x17,
0x83,
0x54,
0xE8,
0x40,
0xC0,
0x1C,
0x9E,
0x95,
0x7D,
0x87,
0x46,
0x8D,
0x2F,
0xA4,
0x8C,
0x48,
0x43,
0x3C,
0xC8,
0x50,
0x7C,
0x14,
0x9A,
0x5B,
0x00,
0xFF,
0xA0,
0x7E,
0x76,
0xD2,
0x0E,
0x97,
0x56,
0x2E,
0xEB,
0x03,
0x20,
0xAC,
0x41,
0x61,
0x73,
0xB8,
0x7A,
0x9F,
0x07,
0xDB,
0xA5,
0x4F,
0x20,
0x3D,
0x9D,
0x01,
0x7C,
0x06,
0x56,
0x3E,
0xA1,
0x18,
0x22,
0xB9,
0x36,
0x1D,
0x80,
0xD3,
0xC5,
0x9B,
0x4F,
0x03,
0x99,
0x72,
0x1A,
0x86,
0xC6,
0x82,
0xC9,
0x87,
0x75,
0x9A,
0xF9,
0xFA,
0xC1,
0x6F,
0x71,
0x0E,
0x83,
0x80,
0x3B,
0x1E,
0x92,
0xA5,
0x7D,
0xB3,
0x82,
0xB0,
0xB9,
0x92,
0x08,
0x40,
0x32,
0x50,
0xEE,
0x95,
0x08,
0x48,
0x4C,
0x0A,
0x2D,
0x88,
0x82,
0x94,
0x1A,
0x47,
0x22,
0xE2,
0x98,
0x0B,
0x80,
0x22,
0xBB,
0x65,
0x7C,
0x45,
0x63,
0xC9,
0xF4,
0xC1,
0x90,
0x89,
0xBE,
0x61,
0x3A,
0x88,
0xF4,
0x3A,
0x24,
0xE2,
0x7E,
0x0D,
0xF1,
0x4C,
0xFF,
0x47,
0xF9,
0x7E,
0xFA,
0x1D,
0xE4,
0x59,
0x43,
0xFD,
0xDE,
0x0F,
0xF5,
0x36,
0x9E,
0x36,
0x63,
0x54,
0x9A,
0x6C,
0xB1,
0xDD,
0x65,
0x2F,
0x11,
0xF4,
0x89,
0xC6,
0xD2,
0x21,
0x1A,
0x2E,
0x5A,
0x2B,
0x8B,
0x26,
0xDF,
0x5B,
0x68,
0x6A,
0xF3,
0xFE,
0xA7,
0x3D,
0x2F,
0x1D,
0x45,
0xFB,
0xAE,
0xE2,
0x98,
0x78,
0x2F,
0xB8,
0x74,
0x94,
0x87,
0x3A,
0x6B,
0x1A,
0xB4,
0x45,
0xB5,
0xAA,
0x13,
0x3E,
0xDD,
0x70,
0x49,
0x6F,
0x97,
0x78,
0x9B,
0xDA,
0xED,
0xF1,
0x6B,
0x33,
0x76,
0x49,
0xEE,
0xB3,
0xFF,
0xF2,
0x14,
0x12,
0xB4,
0xE3,
0xEE,
0xE5,
0xB0,
0xA7,
0x0B,
0xDA,
0xFA,
0x5B,
0x22,
0xCF,
0x61,
0xBF,
0x26,
0x78,
0x72,
0x7B,
0x1B,
};
static unsigned char g_dh3072_g[] = {
0x05,
};
static DH *get_dh3072(void)
{
DH *dh = DH_new();
if (dh == NULL) {
return NULL;
}
BIGNUM *p = BN_bin2bn(g_dh3072_p, sizeof(g_dh3072_p), NULL);
BIGNUM *g = BN_bin2bn(g_dh3072_g, sizeof(g_dh3072_g), NULL);
if ((p == NULL) || (g == NULL) || !DH_set0_pqg(dh, p, NULL, g)) {
DH_free(dh);
BN_free(p);
BN_free(g);
return NULL;
}
return dh;
}
#endif
static void cm_spin_lock(spinlock_t *lock, spin_statis_t *stat)
{
uint32 spin_times = 0;
uint32 sleep_times = 0;
if (SECUREC_UNLIKELY(lock == NULL)) {
return;
}
for (;;) {
#if defined(__arm__) || defined(__aarch64__)
while (__atomic_load_n(lock, __ATOMIC_SEQ_CST) != 0) {
#else
while (*lock != 0) {
#endif
SPIN_STAT_INC(stat, spins);
spin_times++;
if (SECUREC_UNLIKELY(spin_times == GS_SPIN_COUNT)) {
cm_spin_sleep_and_stat(stat);
spin_times = 0;
}
}
if (SECUREC_LIKELY(cm_spin_set(lock, 1) == 0)) {
break;
}
SPIN_STAT_INC(stat, fails);
sleep_times++;
#ifndef WIN32
for (uint32 i = 0; i < sleep_times; i++) {
fas_cpu_pause();
}
#endif
}
}
* Callback function for get PEM info for SSL, add thread lock protect call for 'PEM_def_callback'.
*/
static int32 cm_cs_ssl_cb_get_pem_passwd(char *buf, int size, int rwflag, void *userdata)
{
int32 ret;
if (userdata == NULL) {
cm_spin_lock(&g_get_pem_passwd_lock, NULL);
ret = PEM_def_callback(buf, size, rwflag, userdata);
cm_spin_unlock(&g_get_pem_passwd_lock);
} else {
ret = PEM_def_callback(buf, size, rwflag, userdata);
}
return ret;
}
static status_t cm_cs_ssl_init()
{
if (g_ssl_initialized) {
return CM_SUCCESS;
}
cm_spin_lock(&g_ssl_init_lock, NULL);
if (g_ssl_initialized) {
cm_spin_unlock(&g_ssl_init_lock);
return CM_SUCCESS;
}
if (OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, NULL) == 0) {
cm_spin_unlock(&g_ssl_init_lock);
write_runlog(ERROR, "Init SSL library failed");
return CM_ERROR;
}
g_ssl_initialized = true;
cm_spin_unlock(&g_ssl_init_lock);
return CM_SUCCESS;
}
static void cm_cs_ssl_deinit()
{
if (!g_ssl_initialized) {
return;
}
}
* Obtain the equivalent system error status for the last SSL I/O operation.
*
* @param ssl_err The result code of the failed TLS/SSL I/O operation.
*/
static void cm_cs_ssl_set_sys_error(int32 ssl_err)
{
int32 error = 0;
switch (ssl_err) {
case SSL_ERROR_ZERO_RETURN:
error = ECONNRESET;
break;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
error = EWOULDBLOCK;
break;
case SSL_ERROR_SSL:
#ifdef EPROTO
error = EPROTO;
#else
error = ECONNRESET;
#endif
break;
default:
error = ECONNRESET;
break;
}
if (error != 0) {
CmSetSockError(error);
}
}
static status_t cm_cs_ssl_match_cipher(const text_t *left, char *cipher, uint32_t *offset, bool *support, bool is_tls13)
{
uint32 i, count;
errno_t errcode;
const char** cipher_list;
if (is_tls13) {
count = ELEMENT_COUNT(g_sslTls13CipherNames) - 1;
cipher_list = g_sslTls13CipherNames;
} else {
count = ELEMENT_COUNT(g_sslCipherNames) - 1;
cipher_list = g_sslCipherNames;
}
for (i = 0; i < count; i++) {
if (!CmTextStrEqualIns(left, cipher_list[i])) {
continue;
}
*support = true;
if (*offset > 0) {
errcode = strncpy_s(cipher + *offset, CM_MAX_SSL_CIPHER_LEN - *offset, ":", strlen(":"));
if (errcode != EOK) {
write_runlog(ERROR, "[cm_cs_ssl_match_cipher] system call error, offset > 0");
return CM_ERROR;
}
*offset += 1;
}
errcode = strncpy_s(cipher + *offset, CM_MAX_SSL_CIPHER_LEN - *offset, left->str, left->len);
if (errcode != EOK) {
write_runlog(ERROR, "[cm_cs_ssl_match_cipher] system call error");
return CM_ERROR;
}
*offset += left->len;
break;
}
return CM_SUCCESS;
}
static status_t cm_cs_ssl_distinguish_cipher(const char *cipher, char *tls12_cipher, uint32_t *tls12_offset,
char *tls13_cipher, uint32_t *tls13_offset)
{
bool support = false;
text_t text, left, right;
CmStr2Text((char *)cipher, &text);
CmSplitText(&text, ':', '\0', &left, &right);
text = right;
while (left.len > 0) {
support = false;
if (cm_cs_ssl_match_cipher(&left, tls12_cipher, tls12_offset, &support, false) != CM_SUCCESS) {
return CM_ERROR;
}
if (!support) {
if (cm_cs_ssl_match_cipher(&left, tls13_cipher, tls13_offset, &support, true) != CM_SUCCESS) {
return CM_ERROR;
}
}
if (!support) {
return CM_ERROR;
}
CmSplitText(&text, ':', '\0', &left, &right);
text = right;
}
return CM_SUCCESS;
}
static status_t cm_cs_ssl_set_cipher(SSL_CTX *ctx, const ssl_config_t *config, bool *is_using_tls13)
{
char tls12_cipher[CM_MAX_SSL_CIPHER_LEN] = { 0 };
char tls13_cipher[CM_MAX_SSL_CIPHER_LEN] = { 0 };
uint32_t tls12_len = 0;
uint32_t tls13_len = 0;
const char *tls12_cipher_str = NULL;
const char *tls13_cipher_str = NULL;
if (!CM_IS_EMPTY_STR(config->cipher)) {
if (cm_cs_ssl_distinguish_cipher(config->cipher, tls12_cipher, &tls12_len, tls13_cipher, &tls13_len) !=
CM_SUCCESS) {
return CM_ERROR;
}
if (tls12_len > 0) {
tls12_cipher_str = tls12_cipher;
} else {
tls12_cipher_str = g_sslDefaultCipherList;
}
if (tls13_len > 0) {
*is_using_tls13 = true;
tls13_cipher_str = tls13_cipher;
} else {
tls13_cipher_str = g_sslTls13DefaultCipherList;
}
} else {
tls12_cipher_str = g_sslDefaultCipherList;
tls13_cipher_str = g_sslTls13DefaultCipherList;
*is_using_tls13 = true;
}
if (tls12_cipher_str != NULL && SSL_CTX_set_cipher_list(ctx, tls12_cipher_str) != 1) {
return CM_ERROR;
}
if (tls13_cipher_str != NULL && SSL_CTX_set_ciphersuites(ctx, tls13_cipher_str) != 1) {
return CM_ERROR;
}
return CM_SUCCESS;
}
static inline void cm_cs_ssl_fetch_file_name(text_t *files, text_t *name)
{
if (!CmFetchText(files, ',', '\0', name)) {
return;
}
CmTrimText(name);
if (name->str[0] == '\'') {
name->str++;
if (name->len >= 2) {
name->len -= 2;
} else {
name->len = 0;
}
CmTrimText(name);
}
}
static status_t cm_cs_ssl_set_ca_chain(SSL_CTX *ctx, ssl_config_t *config, bool is_client)
{
text_t file_list, file_name;
char filepath[CM_FILE_NAME_BUFFER_SIZE] = {0};
if (config->ca_file == NULL) {
return CM_SUCCESS;
}
CmStr2Text((char *)config->ca_file, &file_list);
CmRemoveBrackets(&file_list);
cm_cs_ssl_fetch_file_name(&file_list, &file_name);
while (file_name.len > 0) {
CM_RETURN_IFERR(CmText2Str(&file_name, filepath, sizeof(filepath)));
if (cm_ssl_verify_file_stat(filepath) != CM_SUCCESS) {
if (!is_client) {
write_runlog(FATAL, "exit\n");
cm_exit(-1);
}
return CM_ERROR;
}
if (SSL_CTX_load_verify_locations(ctx, filepath, NULL) == 0) {
write_runlog(ERROR, "SSL_CTX_load_verify_locations failed\n");
return CM_ERROR;
}
cm_cs_ssl_fetch_file_name(&file_list, &file_name);
}
return CM_SUCCESS;
}
static status_t cm_cs_load_crl_file(SSL_CTX *ctx, const char *file)
{
long ret;
BIO *in = BIO_new(BIO_s_file());
if (in == NULL || BIO_read_filename(in, file) <= 0) {
return CM_ERROR;
}
X509_CRL *crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL);
if (crl == NULL) {
(void)BIO_free(in);
return CM_ERROR;
}
X509_STORE *st = SSL_CTX_get_cert_store(ctx);
if (!X509_STORE_add_crl(st, crl)) {
X509_CRL_free(crl);
(void)BIO_free(in);
return CM_ERROR;
}
ret = SSL_CTX_set1_verify_cert_store(ctx, st);
X509_CRL_free(crl);
(void)BIO_free(in);
return ret == 1 ? CM_SUCCESS : CM_ERROR;
}
static status_t cm_cs_ssl_set_crl_file(SSL_CTX *ctx, ssl_config_t *config)
{
text_t file_list, file_name;
char filepath[CM_FILE_NAME_BUFFER_SIZE];
if (config->crl_file != NULL) {
CmStr2Text((char *)config->crl_file, &file_list);
CmRemoveBrackets(&file_list);
cm_cs_ssl_fetch_file_name(&file_list, &file_name);
while (file_name.len > 0) {
CM_RETURN_IFERR(CmText2Str(&file_name, filepath, sizeof(filepath)));
if (cm_cs_load_crl_file(ctx, filepath) != CM_SUCCESS) {
return CM_ERROR;
}
cm_cs_ssl_fetch_file_name(&file_list, &file_name);
}
associated with an SSL_CTX structure ctx */
X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
(void)X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
if (!SSL_CTX_set1_param(ctx, param)) {
X509_VERIFY_PARAM_free(param);
return CM_ERROR;
}
X509_VERIFY_PARAM_free(param);
}
return CM_SUCCESS;
}
This function indicates whether the SSL I / O operation must be retried in the future,
and clear the SSL error queue, so the next SSL operation can be performed even after
the iPSI-SSL call fails.
@param ssl SSL connection.
@param ret a SSL I/O function.
@param [out] event The type of I/O event to wait/retry.
@param [out] ssl_err_holder The SSL error code.
@return Whether the SSL I / O operation should be delayed.
@retval true Temporary failure, retry operation.
@retval false Indeterminate failure.
*/
static bool cm_cs_ssl_should_retry(ssl_link_t *link, int32 ret, uint32 *wait_event, int32 *ssl_err_holder)
{
int32 ssl_err;
bool retry = true;
SSL *ssl = SSL_SOCK(link->ssl_sock);
ssl_err = SSL_get_error(ssl, ret);
switch (ssl_err) {
case SSL_ERROR_WANT_READ:
if (wait_event != NULL) {
*wait_event = CS_WAIT_FOR_READ;
}
break;
case SSL_ERROR_WANT_WRITE:
if (wait_event != NULL) {
*wait_event = CS_WAIT_FOR_WRITE;
}
break;
default:
write_runlog(DEBUG1, "SSL read/write failed. SSL error: %d\n", ssl_err);
retry = false;
break;
}
if (ssl_err_holder != NULL) {
(*ssl_err_holder) = ssl_err;
}
return retry;
}
static status_t cm_cs_ssl_wait_on_error(ssl_link_t *link, int32 ret, int32 timeout)
{
int32 ssl_err;
long v_result;
uint32 cs_event;
bool is_ready = false;
char err_buf[CM_BUFLEN_256] = {0};
const char *err_msg = NULL;
SSL *ssl = SSL_SOCK(link->ssl_sock);
ssl_err = SSL_get_error(ssl, ret);
switch (ssl_err) {
case SSL_ERROR_NONE:
return CM_SUCCESS;
case SSL_ERROR_WANT_READ:
cs_event = CS_WAIT_FOR_READ;
break;
case SSL_ERROR_WANT_WRITE:
cs_event = CS_WAIT_FOR_WRITE;
break;
default:
v_result = SSL_get_verify_result(ssl);
if (v_result != X509_V_OK) {
err_msg = X509_verify_cert_error_string(v_result);
write_runlog(ERROR, "SSL verify certificate failed: result code is %ld, %s", v_result, err_msg);
} else {
err_msg = cm_cs_ssl_last_err_string(err_buf, sizeof(err_buf));
write_runlog(ERROR, "SSL connect failed: SSL error %d, %s\n", ssl_err, err_msg);
}
ERR_clear_error();
cm_cs_ssl_set_sys_error(ssl_err);
return CM_ERROR;
}
CM_RETURN_IFERR(cm_cs_tcp_wait(&link->tcp, cs_event, timeout, &is_ready));
return (is_ready ? CM_SUCCESS : CM_TIMEDOUT);
}
static status_t cm_cs_ssl_resolve_file_name(const char *filename, char *buf, uint32 buf_len, const char **res_buf)
{
text_t text;
if (CM_IS_EMPTY_STR(filename) || filename[0] != '\'') {
*res_buf = filename;
return CM_SUCCESS;
}
CmStr2Text((char *)filename, &text);
CM_REMOVE_ENCLOSED_CHAR(&text);
CM_RETURN_IFERR(CmText2Str(&text, buf, buf_len));
*res_buf = buf;
return CM_SUCCESS;
}
static status_t cm_cs_ssl_set_cert_auth(SSL_CTX *ctx, const char *cert_file, const char *key_file,
const char *key_pwd)
{
char file_name[CM_FILE_NAME_BUFFER_SIZE];
if (cert_file == NULL && key_file != NULL) {
cert_file = key_file;
}
if (cert_file != NULL && key_file == NULL) {
key_file = cert_file;
}
if (cert_file != NULL) {
CM_RETURN_IFERR(cm_cs_ssl_resolve_file_name(cert_file, file_name, sizeof(file_name), &cert_file));
if (SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM) != 1) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_CERT, ctx, CM_ERROR);
}
}
if (key_file != NULL) {
CM_RETURN_IFERR(cm_cs_ssl_resolve_file_name(key_file, file_name, sizeof(file_name), &key_file));
if (!CM_IS_EMPTY_STR(key_pwd)) {
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *)key_pwd);
}
if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) != 1) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_KEY, ctx, CM_ERROR);
}
}
if (cert_file != NULL && SSL_CTX_check_private_key(ctx) != 1) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_NOMATCH, ctx, CM_ERROR);
}
return CM_SUCCESS;
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
static status_t cm_cs_ssl_set_tmp_dh(SSL_CTX *ctx)
{
EVP_PKEY *dhpkey = get_pkey();
if (dhpkey == NULL) {
return CM_ERROR;
}
if (!EVP_PKEY_up_ref(dhpkey)) {
EVP_PKEY_free(dhpkey);
return CM_ERROR;
}
if (SSL_CTX_set0_tmp_dh_pkey(ctx, dhpkey) == 0) {
EVP_PKEY_free(dhpkey);
return CM_ERROR;
}
EVP_PKEY_free(dhpkey);
return CM_SUCCESS;
}
#else
static status_t cm_cs_ssl_set_tmp_dh(SSL_CTX *ctx)
{
DH *dh = get_dh3072();
if (dh == NULL) {
return CM_ERROR;
}
if (SSL_CTX_set_tmp_dh(ctx, dh) == 0) {
DH_free(dh);
return CM_ERROR;
}
DH_free(dh);
return CM_SUCCESS;
}
#endif
* create a new ssl context object.
* @param [in] ca_file SSL CA file path
* @param [in] cert_file SSL certificate file path
* @param [in] key_file SSL private key file path
* @param [in] is_client setting for ssl
* @return pointer to SSL_CTX on success, NULL on failure
*/
static SSL_CTX *cm_ssl_create_context(ssl_config_t *config, bool is_client)
{
int purpose;
bool is_using_tls13 = false;
if (cm_cs_ssl_init() != CM_SUCCESS) {
return NULL;
}
CM_SSL_EMPTY_STR_TO_NULL(config->ca_file);
CM_SSL_EMPTY_STR_TO_NULL(config->cert_file);
CM_SSL_EMPTY_STR_TO_NULL(config->key_file);
CM_SSL_EMPTY_STR_TO_NULL(config->crl_file);
const SSL_METHOD *method = is_client ? TLS_client_method() : TLS_server_method();
SSL_CTX *ctx = SSL_CTX_new(method);
if (ctx == NULL) {
CM_THROW_ERROR(ERR_SSL_INIT_FAILED, cm_cs_ssl_init_err_string(SSL_INITERR_MEMFAIL));
return NULL;
}
purpose = is_client ? X509_PURPOSE_SSL_SERVER : X509_PURPOSE_SSL_CLIENT;
if (!SSL_CTX_set_purpose(ctx, purpose)) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_SET_PURPOSE, ctx, NULL);
}
(void)SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
Disable moving-write-buffer sanity check, because it may causes
unnecessary failures in non-blocking send cases.
*/
(void)SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_default_passwd_cb(ctx, cm_cs_ssl_cb_get_pem_passwd);
(void)SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
if (cm_cs_ssl_set_cipher(ctx, config, &is_using_tls13) != CM_SUCCESS) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_CIPHERS, ctx, NULL);
}
if (!is_using_tls13) {
(void)SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
}
if (cm_cs_ssl_set_ca_chain(ctx, config, is_client) != CM_SUCCESS) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_LOAD_CA, ctx, NULL);
}
if (cm_cs_ssl_set_crl_file(ctx, config) != CM_SUCCESS) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_LOAD_CRL, ctx, NULL);
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
SSL_CTX_set_security_level(ctx, 0);
#endif
if (cm_cs_ssl_set_cert_auth(ctx, config->cert_file, config->key_file, config->key_password) != CM_SUCCESS) {
return NULL;
}
if (!is_client && config->key_file == NULL && config->cert_file == NULL) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_NO_USABLE_CTX, ctx, NULL);
}
if (cm_cs_ssl_set_tmp_dh(ctx) != CM_SUCCESS) {
CM_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_DHFAIL, ctx, NULL);
}
return ctx;
}
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
* verification, but for now we'll see if the final error message
* contains enough information.
*
* This callback also allows us to override the default acceptance
* criteria(e.g. accepting self-signed or expired certs), but
* for now we accept the default checks.
*
*/
static int32 cm_cs_ssl_verify_cb(int32 ok, X509_STORE_CTX *ctx)
{
return ok;
}
int32 cm_ssl_get_expire_day(const ASN1_TIME *ctm, time_t *curr_time)
{
int day, sec;
ASN1_TIME *asn1_cmp_time = X509_time_adj(NULL, 0, curr_time);
if (asn1_cmp_time == NULL) {
return -1;
}
if (!ASN1_TIME_diff(&day, &sec, asn1_cmp_time, ctm)) {
return -1;
}
return day;
}
void cm_ssl_check_cert_expire(X509 *cert, int32 alert_day, cert_type_t type)
{
int32 expire_day;
const char* cert_type = (type == CERT_TYPE_SERVER_CERT) ? "server certificate" : "ca";
if (cert == NULL) {
return;
}
const ASN1_TIME *not_after = X509_get0_notAfter(cert);
if (X509_cmp_current_time(not_after) <= 0) {
write_runlog(ERROR, "[cm_ssl_check_cert_expire] The %s is expired\n", cert_type);
} else {
time_t curr_time = CmCurrentTime();
expire_day = cm_ssl_get_expire_day(not_after, &curr_time);
write_runlog(DEBUG5, "[cm_ssl_check_cert_expire] The %s expire day is %d\n", cert_type, expire_day);
if (expire_day >= 0 && alert_day >= expire_day) {
write_runlog(WARNING, "[cm_ssl_check_cert_expire] The %s will expire in %d days\n", cert_type, expire_day);
}
}
}
void cm_ssl_ca_cert_expire(const ssl_ctx_t *ssl_context, int32 alert_day)
{
SSL_CTX *ctx = SSL_CTX_PTR(ssl_context);
if (ssl_context == NULL) {
return;
}
X509 *cert = SSL_CTX_get0_certificate(ctx);
if (cert != NULL) {
cm_ssl_check_cert_expire(cert, alert_day, CERT_TYPE_SERVER_CERT);
}
X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
if (cert_store == NULL) {
return;
}
STACK_OF(X509_OBJECT)* objects = X509_STORE_get0_objects(cert_store);
for (int i = 0; i < sk_X509_OBJECT_num(objects); i++) {
X509_OBJECT *obj = sk_X509_OBJECT_value(objects, i);
if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
cert = X509_OBJECT_get0_X509(obj);
cm_ssl_check_cert_expire(cert, alert_day, CERT_TYPE_CA_CERT);
}
}
return;
}
ssl_ctx_t *cm_ssl_create_acceptor_fd(ssl_config_t *config)
{
int32 verify = SSL_VERIFY_PEER;
if (CM_IS_EMPTY_STR(config->ca_file)) {
verify = SSL_VERIFY_NONE;
} else if (config->verify_peer) {
verify |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
}
SSL_CTX *ssl_fd = cm_ssl_create_context(config, false);
if (ssl_fd == NULL) {
return NULL;
}
Set max number of cached sessions, returns the previous size */
(void)SSL_CTX_sess_set_cache_size(ssl_fd, CM_BUFLEN_128);
SSL_CTX_set_verify(ssl_fd, verify, cm_cs_ssl_verify_cb);
SSL_CTX_set_verify_depth(ssl_fd, SSL_VERIFY_DEPTH);
Set session_id - an identifier for this server session
*/
(void)SSL_CTX_set_session_id_context(ssl_fd, (const unsigned char *)&ssl_fd, sizeof(ssl_fd));
return (ssl_ctx_t *)ssl_fd;
}
ssl_ctx_t *cm_ssl_create_connector_fd(ssl_config_t *config)
{
int32 verify = SSL_VERIFY_PEER;
Turn off verification of servers certificate if both
ca_file and ca_path is set to NULL
*/
if (CM_IS_EMPTY_STR(config->ca_file)) {
verify = SSL_VERIFY_NONE;
}
SSL_CTX *ssl_fd = cm_ssl_create_context(config, true);
if (ssl_fd == NULL) {
return NULL;
}
SSL_CTX_set_verify(ssl_fd, verify, NULL);
SSL_CTX_set_verify_depth(ssl_fd, SSL_VERIFY_DEPTH);
return (ssl_ctx_t *)ssl_fd;
}
void cm_ssl_free_context(ssl_ctx_t *sslCtx)
{
if (sslCtx == NULL) {
cm_cs_ssl_deinit();
return;
}
SSL_CTX_free(SSL_CTX_PTR(sslCtx));
cm_cs_ssl_deinit();
}
static SSL *cm_cs_ssl_create_socket(SSL_CTX *ctx, socket_t sock)
{
SSL *ssl_sock = SSL_new(ctx);
if (ssl_sock == NULL) {
CM_THROW_ERROR(ERR_SSL_INIT_FAILED, "Create SSL socket failed");
return NULL;
}
(void)SSL_clear(ssl_sock);
if (SSL_set_fd(ssl_sock, (int)sock) == 0) {
SSL_free(ssl_sock);
return NULL;
}
return ssl_sock;
}
static char *cm_get_common_name(X509_NAME *cert_name, char *buf, uint32 len)
{
int32 cn_loc;
errno_t errcode;
cn_loc = X509_NAME_get_index_by_NID(cert_name, NID_commonName, -1);
if (cn_loc < 0) {
write_runlog(DEBUG1, "[MEC]failed to get CN location in the certificate subject");
return "NONE";
}
X509_NAME_ENTRY *cn_entry = X509_NAME_get_entry(cert_name, cn_loc);
if (cn_entry == NULL) {
write_runlog(DEBUG1, "[MEC]failed to get CN entry using CN location");
return "NONE";
}
ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
if (cn_asn1 == NULL) {
write_runlog(DEBUG1, "[MEC]failed to get CN from CN entry");
return "NONE";
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
char *name = (char *)ASN1_STRING_data(cn_asn1);
#else
char *name = (char *)ASN1_STRING_get0_data(cn_asn1);
#endif
if (name == NULL) {
write_runlog(DEBUG1, "[MEC]failed to get ASN1 data");
return "NONE";
}
if ((size_t)ASN1_STRING_length(cn_asn1) != strlen(name)) {
write_runlog(DEBUG1, "[MEC]NULL embedded in the certificate CN");
return "NONE";
}
errcode = strncpy_s(buf, len, name, strlen(name));
if (errcode != EOK) {
CM_THROW_ERROR(ERR_SYSTEM_CALL, (errcode));
return "NONE";
}
return buf;
}
static void cm_cs_ssl_show_certs(SSL *ssl)
{
char buf[CM_BUFLEN_512] = {0};
write_runlog(DEBUG5, "[MEC]SSL connection succeeded");
const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
write_runlog(DEBUG5, "[MEC]Using cipher: %s", (cipher == NULL) ? "NONE" : SSL_CIPHER_get_name(cipher));
write_runlog(DEBUG5, "[MEC]Peer certificate:");
X509 *cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) {
X509_NAME *cert_name = X509_get_subject_name(cert);
if (cert_name != NULL) {
write_runlog(DEBUG5, "\tSubject: %s", cm_get_common_name(cert_name, buf, sizeof(buf)));
}
cert_name = X509_get_issuer_name(cert);
if (cert_name != NULL) {
write_runlog(DEBUG5, "\tIssuer: %s", cm_get_common_name(cert_name, buf, sizeof(buf)));
}
X509_free(cert);
} else {
write_runlog(DEBUG5, "[MEC]Peer does not have certificate.");
}
write_runlog(DEBUG5, "\tSRV_TLS_VERSION: %s", SSL_get_version(ssl));
}
status_t cm_cs_ssl_accept_socket(ssl_link_t *link, socket_t sock, uint32 timeout)
{
int32 ret;
uint32 tv = 0;
status_t status;
SSL *ssl;
SSL_CTX *ctx = SSL_CTX_PTR(link->ssl_ctx);
if (ctx == NULL) {
return CM_ERROR;
}
if (link->ssl_sock == NULL) {
ssl = cm_cs_ssl_create_socket(ctx, sock);
if (ssl == NULL) {
return CM_ERROR;
}
link->ssl_sock = (ssl_sock_t *)ssl;
} else {
ssl = (SSL *)link->ssl_sock;
}
link->tcp.sock = sock;
do {
ret = SSL_accept(ssl);
if (ret == 1) {
status = CM_SUCCESS;
break;
}
status = cm_cs_ssl_wait_on_error(link, ret, (int32)CM_NETWORK_IO_TIMEOUT);
if (status == CM_ERROR) {
break;
} else if (status == CM_TIMEDOUT) {
tv += (uint32)CM_NETWORK_IO_TIMEOUT;
}
} while (tv < timeout && !SSL_is_init_finished(ssl));
if (status == CM_SUCCESS) {
cm_cs_ssl_show_certs(ssl);
return CM_SUCCESS;
}
if (status == CM_TIMEDOUT) {
write_runlog(DEBUG1, "ssl accept timeout(%u ms)\n", timeout);
}
return status;
}
status_t cm_ssl_connect_socket(ssl_link_t *link, socket_t sock, int32 timeout)
{
int32 ret;
int32 tv = 0;
status_t status = CM_SUCCESS;
SSL_CTX *ctx = SSL_CTX_PTR(link->ssl_ctx);
if (ctx == NULL) {
return CM_ERROR;
}
SSL *ssl = cm_cs_ssl_create_socket(ctx, sock);
if (ssl == NULL) {
return CM_ERROR;
}
link->tcp.sock = sock;
link->ssl_sock = (ssl_sock_t *)ssl;
do {
ret = SSL_connect(ssl);
status = cm_cs_ssl_wait_on_error(link, ret, (int32)CM_NETWORK_IO_TIMEOUT);
if (status == CM_ERROR) {
break;
} else if (status == CM_TIMEDOUT) {
tv += (int32)CM_NETWORK_IO_TIMEOUT;
}
} while (tv < timeout && !SSL_is_init_finished(ssl));
if (status == CM_SUCCESS) {
return CM_SUCCESS;
}
SSL_free(ssl);
link->ssl_sock = NULL;
return CM_ERROR;
}
status_t cm_cs_ssl_send(ssl_link_t *link, const char *buf, uint32 size, int32 *send_size)
{
int32 ret, err;
SSL *ssl = SSL_SOCK(link->ssl_sock);
if (size == 0) {
*send_size = 0;
return CM_SUCCESS;
}
CmSetSockError(0);
ERR_clear_error();
ret = SSL_write(ssl, buf, (int)size);
if (ret > 0) {
(*send_size) = ret;
return CM_SUCCESS;
}
if (!cm_cs_ssl_should_retry(link, ret, NULL, &err)) {
if (CmGetSockError() == EWOULDBLOCK) {
(*send_size) = 0;
return CM_SUCCESS;
}
CM_THROW_ERROR(ERR_PEER_CLOSED_REASON, "ssl", err);
return CM_ERROR;
}
(*send_size) = 0;
return CM_SUCCESS;
}
status_t cm_cs_ssl_send_timed(ssl_link_t *link, const char *buf, uint32 size, uint32 timeout)
{
uint32 remain_size;
uint32 offset = 0;
int32 writen_size = 0;
uint32 wait_interval = 0;
bool ready = false;
if (link->ssl_sock == NULL) {
CM_THROW_ERROR(ERR_PEER_CLOSED, "ssl");
return CM_ERROR;
}
if (cm_cs_ssl_send(link, buf, size, &writen_size) != CM_SUCCESS) {
return CM_ERROR;
}
remain_size = size;
if (writen_size > 0) {
remain_size = size - (uint32)writen_size;
offset = (uint32)writen_size;
}
while (remain_size > 0) {
if (cm_cs_ssl_wait(link, CS_WAIT_FOR_WRITE, (int32)CM_POLL_WAIT, &ready) != CM_SUCCESS) {
return CM_ERROR;
}
if (!ready) {
wait_interval += CM_POLL_WAIT;
if (wait_interval >= timeout) {
CM_THROW_ERROR(ERR_TCP_TIMEOUT, "send data");
return CM_ERROR;
}
continue;
}
if (cm_cs_ssl_send(link, buf + offset, remain_size, &writen_size) != CM_SUCCESS) {
return CM_ERROR;
}
if (writen_size > 0) {
remain_size -= (uint32)writen_size;
offset += (uint32)writen_size;
}
}
return CM_SUCCESS;
}
status_t cm_cs_ssl_recv(ssl_link_t *link, char *buf, uint32 size, int32 *recv_size, uint32 *wait_event)
{
int32 ret, err;
SSL *ssl = SSL_SOCK(link->ssl_sock);
if (size == 0) {
(*recv_size) = 0;
return CM_SUCCESS;
}
for (;;) {
CmSetSockError(0);
ERR_clear_error();
ret = SSL_read(ssl, (void *)buf, (int32)size);
if (ret > 0) {
break;
}
if (!cm_cs_ssl_should_retry(link, ret, wait_event, &err)) {
err = CmGetSockError();
if (err == EINTR || err == EAGAIN) {
continue;
}
if (err == ECONNRESET) {
CM_THROW_ERROR(ERR_PEER_CLOSED, "ssl");
}
return CM_ERROR;
}
*recv_size = 0;
return CM_SUCCESS;
}
*recv_size = ret;
return CM_SUCCESS;
}
status_t cm_cs_ssl_recv_remain(ssl_link_t *link, char *buf, uint32 offset, uint32 remain_size,
uint32 wait_event, uint32 timeout)
{
int32 recv_size;
uint32 wait_interval = 0;
bool ready = false;
while (remain_size > 0) {
CM_RETURN_IFERR(cm_cs_ssl_wait(link, wait_event, (int32)CM_POLL_WAIT, &ready));
if (!ready) {
wait_interval += CM_POLL_WAIT;
if (wait_interval >= timeout) {
CM_THROW_ERROR(ERR_TCP_TIMEOUT, "recv data");
return CM_ERROR;
}
continue;
}
CM_RETURN_IFERR(cm_cs_ssl_recv(link, buf + offset, remain_size, &recv_size, &wait_event));
remain_size -= (uint32)recv_size;
offset += (uint32)recv_size;
}
return CM_SUCCESS;
}
status_t cm_cs_ssl_wait(ssl_link_t *link, uint32 wait_for, int32 timeout, bool *ready)
{
return cm_cs_tcp_wait(&link->tcp, wait_for, timeout, ready);
}
static status_t CmRealpathFile(const char *filename, char *realfile, uint32 real_path_len)
{
#ifdef WIN32
if (!_fullpath(realfile, filename, real_path_len - 1)) {
CM_THROW_ERROR(ERR_OPEN_FILE, filename, errno);
return CM_ERROR;
}
#else
errno_t errcode;
char resolved_path[PATH_MAX] = {0};
if (!realpath(filename, resolved_path)) {
if (errno != ENOENT && errno != EACCES) {
CM_THROW_ERROR(ERR_OPEN_FILE, filename, errno);
return CM_ERROR;
}
}
errcode = strncpy_s(realfile, real_path_len, resolved_path, real_path_len - 1);
if (errcode != EOK) {
CM_THROW_ERROR(ERR_SYSTEM_CALL, (errcode));
return CM_ERROR;
}
#endif
return CM_SUCCESS;
}
status_t cm_ssl_verify_file_stat(const char *file_name)
{
char real_path[CM_FILE_NAME_BUFFER_SIZE] = { 0 };
CM_RETURN_IFERR(CmRealpathFile(file_name, real_path, CM_FILE_NAME_BUFFER_SIZE));
#ifndef WIN32
struct stat stat_buf;
if (file_name && stat(file_name, &stat_buf) == 0) {
if (!S_ISREG(stat_buf.st_mode) || stat_buf.st_mode & (S_IRWXG | S_IRWXO | S_IXUSR | S_IWUSR)) {
CM_THROW_ERROR(ERR_SSL_FILE_PERMISSION, file_name);
write_runlog(ERROR, "[cm_ssl_verify_file_stat] SSL server certificate file \"%s\" has execute, "
"group or world access permission.\n", real_path);
return CM_ERROR;
}
}
#endif
return CM_SUCCESS;
}
void cm_cs_tcp_disconnect(tcp_link_t *link)
{
if (link->closed) {
return;
}
(void)cs_close_socket(link->sock);
link->closed = CM_TRUE;
link->sock = CS_INVALID_SOCKET;
}
int32 cm_cs_tcp_poll(struct pollfd *fds, uint32 nfds, int32 timeout)
{
#ifndef WIN32
int32 ret = poll(fds, nfds, timeout);
if (ret < 0 && errno == EINTR) {
return 0;
}
return ret;
#else
int32 ret = 0;
fd_set wfds;
fd_set rfds;
fd_set efds;
uint32 i = 0;
struct pollfd *pfds = fds;
struct timeval tv, *tvptr = NULL;
if (nfds >= FD_SETSIZE) {
CM_THROW_ERROR_EX(ERR_ASSERT_ERROR, "nfds(%u) < FD_SETSIZE(%u)", nfds, (uint32)FD_SETSIZE);
return CM_ERROR;
}
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
if (timeout >= 0) {
tv.tv_sec = timeout / CM_TIME_THOUSAND_UN;
tv.tv_usec = (timeout % CM_TIME_THOUSAND_UN) * CM_TIME_THOUSAND_UN;
tvptr = &tv;
}
cs_tcp_poll_set_fd(pfds, nfds, &wfds, &rfds, &efds);
ret = select(0, &rfds, &wfds, &efds, tvptr);
if (ret <= 0) {
return (ret < 0 && EINTR == errno) ? 0 : ret;
}
pfds = fds;
cs_tcp_poll_set_event(pfds, nfds, &wfds, &rfds, &efds);
return ret;
#endif
}
status_t cm_cs_ssl_accept(ssl_ctx_t *fd, cs_pipe_t *pipe)
{
status_t status;
ssl_link_t *link = &pipe->link.ssl;
link->ssl_ctx = fd;
status = cm_cs_ssl_accept_socket(link, pipe->link.tcp.sock, CM_SSL_ACCEPT_TIMEOUT);
if (status != CM_SUCCESS) {
return status;
}
pipe->type = CS_TYPE_SSL;
return status;
}
status_t cm_cs_tcp_wait(tcp_link_t *link, uint32 wait_for, int32 timeout, bool *ready)
{
struct pollfd fd;
int32 ret;
int32 tv;
if (ready != NULL) {
*ready = false;
}
if (link->closed) {
CM_THROW_ERROR(ERR_PEER_CLOSED, "tcp");
return CM_ERROR;
}
tv = (timeout < 0 ? -1 : timeout);
fd.fd = link->sock;
fd.revents = 0;
if (wait_for == CS_WAIT_FOR_WRITE) {
fd.events = POLLOUT;
} else {
fd.events = POLLIN;
}
ret = cm_cs_tcp_poll(&fd, 1, tv);
if (ret >= 0) {
if (ready != NULL) {
*ready = ((ret == 0 && errno == EINTR) || ret > 0);
}
return CM_SUCCESS;
}
if (errno != EINTR) {
link->closed = CM_TRUE;
CM_THROW_ERROR(ERR_PEER_CLOSED, "tcp");
return CM_ERROR;
}
return CM_SUCCESS;
}
status_t cm_cs_ssl_connect(ssl_ctx_t *fd, cs_pipe_t *pipe)
{
ssl_link_t *link = &pipe->link.ssl;
link->ssl_ctx = fd;
if (cm_ssl_connect_socket(link, pipe->link.tcp.sock, (int32)CM_SSL_IO_TIMEOUT) != CM_SUCCESS) {
return CM_ERROR;
}
pipe->type = CS_TYPE_SSL;
return CM_SUCCESS;
}
void ReconstructCipherContent(cipher_t *cipher_content, const RandkeyFile *randkey, const CipherkeyFile *cipherKey)
{
int rc = memcpy_s(cipher_content->cipher_text, CM_PASSWORD_BUFFER_SIZE, cipherKey->cipherkey, CIPHER_LEN);
securec_check_errno(rc, (void)rc);
const uint32 pulCLen = 16;
cipher_content->cipher_len = pulCLen;
rc = memcpy_s(cipher_content->IV, (RANDOM_LEN + 1), cipherKey->vectorSalt, RANDOM_LEN);
securec_check_errno(rc, (void)rc);
rc = memcpy_s(cipher_content->salt, (RANDOM_LEN + 1), cipherKey->keySalt, RANDOM_LEN);
securec_check_errno(rc, (void)rc);
rc = memcpy_s(cipher_content->rand, (RANDOM_LEN + 1), randkey->randkey, RANDOM_LEN);
securec_check_errno(rc, (void)rc);
return;
}
static status_t InitKeyFile(char *randKeyFilePath, char *cipherKeyFilePath, uint32 filePathLen, CipherMode mode)
{
int rcs = 0;
char filePath[MAX_PATH_LEN] = {0};
if (GetHomePath(filePath, sizeof(filePath)) != 0) {
return CM_ERROR;
}
canonicalize_path(filePath);
char certFilePath[MAX_PATH_LEN] = {0};
const char *keyRandFile;
const char *keyCipherFile;
switch (mode) {
case SERVER_CIPHER:
keyRandFile = SERVER_KEY_RAND_FILE;
keyCipherFile = SERVER_KEY_CIPHER_FILE;
rcs = snprintf_s(certFilePath, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/share/sslcert/cm", filePath);
securec_check_intval(rcs, (void)rcs);
break;
case CLIENT_CIPHER:
keyRandFile = CLIENT_KEY_RAND_FILE;
keyCipherFile = CLIENT_KEY_CIPHER_FILE;
rcs = snprintf_s(certFilePath, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/share/sslcert/cm", filePath);
securec_check_intval(rcs, (void)rcs);
break;
case HADR_CIPHER:
keyRandFile = HADR_KEY_RAND_FILE;
keyCipherFile = HADR_KEY_CIPHER_FILE;
rcs = snprintf_s(certFilePath, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/bin", filePath);
securec_check_intval(rcs, (void)rcs);
break;
default:
write_runlog(LOG, "KeyMode is unknown !\n");
return CM_ERROR;
}
rcs = snprintf_s(randKeyFilePath, filePathLen, filePathLen - 1, "%s/%s", certFilePath, keyRandFile);
securec_check_intval(rcs, (void)rcs);
rcs = snprintf_s(cipherKeyFilePath, filePathLen, filePathLen - 1, "%s/%s", certFilePath, keyCipherFile);
securec_check_intval(rcs, (void)rcs);
return CM_SUCCESS;
}
status_t cm_verify_ssl_key_pwd(char *plain, uint32 size, CipherMode mode)
{
int rcs = 0;
status_t ret;
cipher_t cipher = {{0}};
char randKeyFilePath[MAX_PATH_LEN] = {0};
char cipherKeyFilePath[MAX_PATH_LEN] = {0};
ret = InitKeyFile(randKeyFilePath, cipherKeyFilePath, MAX_PATH_LEN, mode);
if (ret != CM_SUCCESS) {
write_runlog(ERROR, "Init key file failed\n");
}
RandkeyFile randKey;
(void)ReadContentFromFile(randKeyFilePath, &randKey, sizeof(RandkeyFile));
CipherkeyFile cipherKey;
(void)ReadContentFromFile(cipherKeyFilePath, &cipherKey, sizeof(CipherkeyFile));
ReconstructCipherContent(&cipher, &randKey, &cipherKey);
if (CmDecryptPwd(&cipher, (unsigned char *)plain, &size) != CM_SUCCESS) {
rcs = memset_s(&cipher, sizeof(cipher), 0, sizeof(cipher));
securec_check_errno(rcs, (void)rcs);
return CM_ERROR;
}
rcs = memset_s(&cipher, sizeof(cipher), 0, sizeof(cipher));
securec_check_errno(rcs, (void)rcs);
return CM_SUCCESS;
}
static void CsTcpDisconnect(tcp_link_t *link, int32 type, int32 *socket)
{
if (link == NULL) {
write_runlog(ERROR, "[CsTcpDisconnect] type is %d: link is NULL.\n", type);
return;
}
if (link->closed) {
CM_ASSERT(link->sock == CS_INVALID_SOCKET);
return;
}
if (*socket < 0) {
write_runlog(DEBUG1, "socket has been closed [%d]. type is %d: disconnect tcp socket.\n", link->sock, type);
link->closed = CM_TRUE;
link->sock = CS_INVALID_SOCKET;
return;
}
write_runlog(DEBUG5, "socket is [%d]. type is %d: disconnect tcp socket.\n", link->sock, type);
(void)cs_close_socket(link->sock);
link->closed = CM_TRUE;
link->sock = CS_INVALID_SOCKET;
if (socket != NULL) {
(*socket) = CS_INVALID_SOCKET;
}
}
static void CsSslDisconnect(ssl_link_t *link, int32 type, int32 *socket)
{
if (link == NULL) {
write_runlog(ERROR, "[CsSslDisconnect] type is %d: link is NULL.\n", type);
return;
}
if (link->tcp.closed) {
CM_ASSERT(link->tcp.sock == CS_INVALID_SOCKET);
} else {
CsTcpDisconnect(&(link->tcp), type, socket);
}
SSL *ssl = SSL_SOCK(link->ssl_sock);
if (ssl == NULL) {
return;
}
SSL_set_quiet_shutdown(ssl, 1);
if (SSL_shutdown(ssl) != 1) {
write_runlog(ERROR, "type is %d: shutdown SSL failed.\n", type);
}
SSL_free(ssl);
link->ssl_sock = NULL;
write_runlog(DEBUG5, "type is %d: disconnect ssl socket.\n", type);
}
void CsDisconnect(cs_pipe_t *pipe, int32 type, int32 *socket)
{
write_runlog(DEBUG5, "type is %d: begin to disconnect pipe.\n", type);
if (pipe == NULL) {
write_runlog(ERROR, "type is %d: pip is NULL.\n", type);
return;
}
if (pipe->type == CS_TYPE_TCP) {
CsTcpDisconnect(&(pipe->link.tcp), type, socket);
}
CsSslDisconnect(&(pipe->link.ssl), type, socket);
write_runlog(DEBUG5, "type is %d: end to disconnect pipe.\n", type);
}
#ifdef __cplusplus
}
#endif