* This file is part of the oGRAC project.
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
*
* oGRAC 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.c
*
*
* IDENTIFICATION
* src/protocol/cs_ssl.c
*
* -------------------------------------------------------------------------
*/
#include "cs_protocol_module.h"
#include "cs_ssl.h"
#include "err.h"
#include "cs_pipe.h"
#include "cm_signal.h"
#include "cm_file.h"
#include "x509v3.h"
#ifdef __cplusplus
extern "C" {
#endif
#define OG_SSL_FREE_CTX_AND_RETURN(err, ogx, ret) \
do { \
OG_THROW_ERROR(ERR_SSL_INIT_FAILED, cs_ssl_init_err_string(err)); \
SSL_CTX_free(ogx); \
return ret; \
} while (0)
#define OG_SSL_EMPTY_STR_TO_NULL(str) \
if ((str) != NULL && (str)[0] == '\0') { \
(str) = NULL; \
}
#define SSL_CTX_PTR(ogx) ((SSL_CTX*)(ogx))
#define SSL_SOCK(sock) ((SSL*)(sock))
#define SSL_VERIFY_DEPTH 10
static spinlock_t g_ssl_init_lock = 0;
static volatile bool32 g_ssl_initialized = 0;
static spinlock_t g_get_pem_passwd_lock = 0;
const char *g_ssl_default_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"DHE-RSA-AES256-GCM-SHA384:"
"DHE-RSA-AES128-GCM-SHA256:"
"DHE-DSS-AES256-GCM-SHA384:"
"DHE-DSS-AES128-GCM-SHA256:"
"DHE-RSA-AES256-CCM:"
"DHE-RSA-AES128-CCM";
const char *g_ssl_tls13_default_cipher_list = "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_ssl_cipher_names[] = {
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES128-GCM-SHA256",
"DHE-RSA-AES256-GCM-SHA384",
"DHE-RSA-AES128-GCM-SHA256",
"DHE-DSS-AES256-GCM-SHA384",
"DHE-DSS-AES128-GCM-SHA256",
"DHE-RSA-AES256-CCM",
"DHE-RSA-AES128-CCM",
NULL
};
const char *g_ssl_tls13_cipher_names[] = {
"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 *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",
"",
};
static const char *cs_ssl_init_err_string(ssl_init_error_t err)
{
if (err > SSL_INITERR_NONE && err < SSL_INITERR_LASTERR) {
return ssl_error_string[err];
}
return ssl_error_string[0];
}
Get the last SSL error code and reason
*/
static const char *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;
}
EVP_PKEY *get_dh3072(void)
{
OSSL_PARAM params[2];
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
params[0] = OSSL_PARAM_construct_utf8_string("group", "ffdhe3072", 0);
params[1] = OSSL_PARAM_construct_end();
EVP_PKEY_keygen_init(pctx);
EVP_PKEY_CTX_set_params(pctx, params);
EVP_PKEY_generate(pctx, &pkey);
EVP_PKEY_CTX_free(pctx);
return pkey;
}
* Callback function for get PEM info for SSL, add thread lock protect call for 'PEM_def_callback'.
*/
static int32 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 cs_ssl_init(void)
{
if (g_ssl_initialized) {
return OG_SUCCESS;
}
cm_spin_lock(&g_ssl_init_lock, NULL);
if (g_ssl_initialized) {
cm_spin_unlock(&g_ssl_init_lock);
return OG_SUCCESS;
}
if (OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, NULL) == 0) {
cm_spin_unlock(&g_ssl_init_lock);
OG_THROW_ERROR(ERR_SSL_INIT_FAILED, "Init SSL library failed");
return OG_ERROR;
}
g_ssl_initialized = OG_TRUE;
cm_spin_unlock(&g_ssl_init_lock);
return OG_SUCCESS;
}
static void cs_ssl_deinit(void)
{
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 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) {
cm_set_sock_error(error);
}
}
static status_t cs_ssl_match_cipher(text_t left, char *cipher, uint32_t *offset, bool32 *support, bool32 is_tls13)
{
uint32 i;
uint32 count;
errno_t errcode;
const char** cipher_list;
if (is_tls13) {
count = ELEMENT_COUNT(g_ssl_tls13_cipher_names) - 1;
cipher_list = g_ssl_tls13_cipher_names;
} else {
count = ELEMENT_COUNT(g_ssl_cipher_names) - 1;
cipher_list = g_ssl_cipher_names;
}
for (i = 0; i < count; i++) {
if (cm_text_str_equal_ins(&left, cipher_list[i])) {
*support = OG_TRUE;
if (*offset > 0) {
errcode = strncpy_s(cipher + *offset, OG_MAX_SSL_CIPHER_LEN - *offset, ":", strlen(":"));
if (errcode != EOK) {
OG_THROW_ERROR(ERR_SYSTEM_CALL, (errcode));
return OG_ERROR;
}
*offset += 1;
}
errcode = strncpy_s(cipher + *offset, OG_MAX_SSL_CIPHER_LEN - *offset, left.str, left.len);
if (errcode != EOK) {
OG_THROW_ERROR(ERR_SYSTEM_CALL, (errcode));
return OG_ERROR;
}
*offset += left.len;
break;
}
}
return OG_SUCCESS;
}
static status_t cs_ssl_distinguish_cipher(const char *cipher, char *tls12_cipher, uint32_t *tls12_offset,
char *tls13_cipher, uint32_t *tls13_offset)
{
bool32 support = OG_FALSE;
text_t text;
text_t left;
text_t right;
cm_str2text((char *)cipher, &text);
cm_split_text(&text, ':', '\0', &left, &right);
text = right;
while (left.len > 0) {
support = OG_FALSE;
if (cs_ssl_match_cipher(left, tls12_cipher, tls12_offset, &support, OG_FALSE) != OG_SUCCESS) {
return OG_ERROR;
}
if (!support) {
if (cs_ssl_match_cipher(left, tls13_cipher, tls13_offset, &support, OG_TRUE) != OG_SUCCESS) {
return OG_ERROR;
}
}
if (support != OG_TRUE) {
return OG_ERROR;
}
cm_split_text(&text, ':', '\0', &left, &right);
text = right;
}
return OG_SUCCESS;
}
static status_t cs_ssl_set_cipher(SSL_CTX *ogx, ssl_config_t *config, bool32* is_using_tls13)
{
char tls12_cipher[OG_MAX_SSL_CIPHER_LEN] = { 0 };
char tls13_cipher[OG_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 (cs_ssl_distinguish_cipher(config->cipher, tls12_cipher, &tls12_len, tls13_cipher, &tls13_len) !=
OG_SUCCESS) {
return OG_ERROR;
}
if (tls12_len > 0) {
tls12_cipher_str = tls12_cipher;
} else {
tls12_cipher_str = g_ssl_default_cipher_list;
}
if (tls13_len > 0) {
*is_using_tls13 = OG_TRUE;
tls13_cipher_str = tls13_cipher;
} else {
tls13_cipher_str = g_ssl_tls13_default_cipher_list;
}
} else {
tls12_cipher_str = g_ssl_default_cipher_list;
tls13_cipher_str = g_ssl_tls13_default_cipher_list;
*is_using_tls13 = OG_TRUE;
}
if (tls12_cipher_str != NULL && 1 != SSL_CTX_set_cipher_list(ogx, tls12_cipher_str)) {
return OG_ERROR;
}
if (tls13_cipher_str != NULL && 1 != SSL_CTX_set_ciphersuites(ogx, tls13_cipher_str)) {
return OG_ERROR;
}
return OG_SUCCESS;
}
static inline void cs_ssl_fetch_file_name(text_t *files, text_t *name)
{
if (!cm_fetch_text(files, ',', '\0', name)) {
return;
}
cm_trim_text(name);
if (name->str[0] == '\'') {
name->str++;
name->len -= 2;
cm_trim_text(name);
}
}
static status_t cs_ssl_set_ca_chain(SSL_CTX *ogx, ssl_config_t *config, bool32 is_client)
{
text_t file_list;
text_t file_name;
char filepath[OG_FILE_NAME_BUFFER_SIZE];
if (config->ca_file != NULL) {
cm_str2text((char *)config->ca_file, &file_list);
cm_remove_brackets(&file_list);
cs_ssl_fetch_file_name(&file_list, &file_name);
while (file_name.len > 0) {
OG_RETURN_IFERR(cm_text2str(&file_name, filepath, sizeof(filepath)));
if (cs_ssl_verify_file_stat(filepath) != OG_SUCCESS) {
if (!is_client) {
CM_ABORT(0, "SSL CA certificate file \"%s\" has execute, group or world access permission", filepath);
}
return OG_ERROR;
}
if (SSL_CTX_load_verify_locations(ogx, (const char *)filepath, NULL) == 0) {
return OG_ERROR;
}
cs_ssl_fetch_file_name(&file_list, &file_name);
}
}
return OG_SUCCESS;
}
static status_t cs_load_crl_file(SSL_CTX *ogx, const char *file)
{
long ret;
BIO *in = NULL;
X509_CRL *crl = NULL;
X509_STORE *st = NULL;
in = BIO_new(BIO_s_file());
if (in == NULL) {
return OG_ERROR;
}
if (BIO_read_filename(in, file) <= 0) {
(void)BIO_free(in);
return OG_ERROR;
}
crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL);
if (crl == NULL) {
(void)BIO_free(in);
return OG_ERROR;
}
st = SSL_CTX_get_cert_store(ogx);
if (!X509_STORE_add_crl(st, crl)) {
X509_CRL_free(crl);
(void)BIO_free(in);
return OG_ERROR;
}
ret = SSL_CTX_set1_verify_cert_store(ogx, st);
X509_CRL_free(crl);
(void)BIO_free(in);
return ret == 1 ? OG_SUCCESS : OG_ERROR;
}
static status_t cs_ssl_set_crl_file(SSL_CTX *ogx, ssl_config_t *config)
{
text_t file_list;
text_t file_name;
char filepath[OG_FILE_NAME_BUFFER_SIZE];
if (config->crl_file != NULL) {
cm_str2text((char *)config->crl_file, &file_list);
cm_remove_brackets(&file_list);
cs_ssl_fetch_file_name(&file_list, &file_name);
while (file_name.len > 0) {
OG_RETURN_IFERR(cm_text2str(&file_name, filepath, sizeof(filepath)));
if (cs_load_crl_file(ogx, filepath) != OG_SUCCESS) {
return OG_ERROR;
}
cs_ssl_fetch_file_name(&file_list, &file_name);
}
associated with an SSL_CTX structure ogx */
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(ogx, param)) {
X509_VERIFY_PARAM_free(param);
return OG_ERROR;
}
X509_VERIFY_PARAM_free(param);
}
return OG_SUCCESS;
}
static void cs_ssl_throw_error(int32 ssl_err)
{
char err_buf1[OG_MESSAGE_BUFFER_SIZE] = { 0 };
char err_buf2[OG_MESSAGE_BUFFER_SIZE] = { 0 };
uint32 err_len = 0;
ulong ret_code;
const char *file = NULL;
const char *data = NULL;
int32 line = 0;
int32 flags = 0;
int32 ret = 0;
cs_ssl_set_sys_error(ssl_err);
while ((ret_code = ERR_get_error_all(&file, &line, NULL, &data, &flags))) {
ret = snprintf_s(err_buf1 + err_len, OG_MESSAGE_BUFFER_SIZE - err_len, OG_MESSAGE_BUFFER_SIZE - 1 - err_len,
"OpenSSL:%s-%s-%d-%s", ERR_error_string(ret_code, err_buf2), file, line,
((uint32)flags & ERR_TXT_STRING) ? data : "");
if (ret == -1) {
break;
}
err_len = (uint32)strlen(err_buf1);
}
if (ret == -1) {
OG_THROW_ERROR(ERR_SSL_RECV_FAILED, ssl_err, cm_get_sock_error(), err_buf1);
return;
}
do {
if (err_len == 0) {
const char *err_msg = cs_ssl_last_err_string(err_buf2, sizeof(err_buf2));
ret = snprintf_s(err_buf1, OG_MESSAGE_BUFFER_SIZE, OG_MESSAGE_BUFFER_SIZE - 1, "%s", err_msg);
if (ret == -1) {
break;
}
}
} while (0);
OG_THROW_ERROR(ERR_SSL_RECV_FAILED, ssl_err, cm_get_sock_error(), err_buf1);
}
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 bool32 cs_ssl_should_retry(ssl_link_t *link, int32 ret, uint32 *wait_event, int32 *ssl_err_holder)
{
int32 ssl_err;
bool32 retry = OG_TRUE;
SSL *ssl = SSL_SOCK(link->ssl_sock);
ssl_err = SSL_get_error(ssl, ret);
switch (ssl_err) {
case SSL_ERROR_WANT_READ:
*wait_event = CS_WAIT_FOR_READ;
break;
case SSL_ERROR_WANT_WRITE:
*wait_event = CS_WAIT_FOR_WRITE;
break;
default:
OG_LOG_DEBUG_ERR("SSL read/write failed. SSL error: %d", ssl_err);
cs_ssl_throw_error(ssl_err);
ERR_clear_error();
retry = OG_FALSE;
break;
}
if (ssl_err_holder != NULL) {
(*ssl_err_holder) = ssl_err;
}
return retry;
}
static status_t cs_ssl_wait_on_error(ssl_link_t *link, int32 ret, int32 timeout)
{
int32 ssl_err;
long v_result;
uint32 cs_event;
bool32 is_ready = OG_FALSE;
char err_buf[OG_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 OG_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);
OG_LOG_RUN_ERR("SSL verify certificate failed: result code is %ld, %s", v_result, err_msg);
OG_THROW_ERROR(ERR_SSL_VERIFY_CERT, err_msg);
} else {
err_msg = cs_ssl_last_err_string(err_buf, sizeof(err_buf));
OG_THROW_ERROR(ERR_SSL_CONNECT_FAILED, err_msg);
OG_LOG_RUN_ERR("SSL connect failed: SSL error %d, %s", ssl_err, err_msg);
}
(void)ERR_clear_error();
cs_ssl_set_sys_error(ssl_err);
return OG_ERROR;
}
OG_RETURN_IFERR(cs_tcp_wait(&link->tcp, cs_event, timeout, &is_ready));
return (is_ready ? OG_SUCCESS : OG_TIMEDOUT);
}
static status_t 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 OG_SUCCESS;
}
cm_str2text((char *)filename, &text);
CM_REMOVE_ENCLOSED_CHAR(&text);
OG_RETURN_IFERR(cm_text2str(&text, buf, buf_len));
*res_buf = buf;
return OG_SUCCESS;
}
static status_t cs_ssl_set_cert_auth(SSL_CTX *ogx, const char *cert_file, const char *key_file,
const char *key_pwd)
{
char file_name[OG_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) {
OG_RETURN_IFERR(cs_ssl_resolve_file_name(cert_file, file_name, sizeof(file_name), &cert_file));
if (SSL_CTX_use_certificate_file(ogx, cert_file, SSL_FILETYPE_PEM) != 1) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_CERT, ogx, OG_ERROR);
}
}
if (key_file != NULL) {
OG_RETURN_IFERR(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(ogx, (void *)key_pwd);
}
if (SSL_CTX_use_PrivateKey_file(ogx, key_file, SSL_FILETYPE_PEM) != 1) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_KEY, ogx, OG_ERROR);
}
}
if (cert_file != NULL && SSL_CTX_check_private_key(ogx) != 1) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_NOMATCH, ogx, OG_ERROR);
}
return OG_SUCCESS;
}
static status_t cs_ssl_set_tmp_dh(SSL_CTX *ogx)
{
EVP_PKEY *pkey = get_dh3072();
if (pkey == NULL) {
return OG_ERROR;
}
if (!EVP_PKEY_up_ref(pkey) || !SSL_CTX_set0_tmp_dh_pkey(ogx, pkey)) {
EVP_PKEY_free(pkey);
return OG_ERROR;
}
EVP_PKEY_free(pkey);
return OG_SUCCESS;
}
* 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 *cs_ssl_create_context(ssl_config_t *config, bool32 is_client)
{
int purpose;
bool32 is_using_tls13 = OG_FALSE;
OG_RETVALUE_IFTRUE(cs_ssl_init() != OG_SUCCESS, NULL);
OG_SSL_EMPTY_STR_TO_NULL(config->ca_file);
OG_SSL_EMPTY_STR_TO_NULL(config->cert_file);
OG_SSL_EMPTY_STR_TO_NULL(config->key_file);
OG_SSL_EMPTY_STR_TO_NULL(config->crl_file);
SSL_CTX *ogx = NULL;
const SSL_METHOD *method = NULL;
method = is_client ? TLS_client_method() : TLS_server_method();
ogx = SSL_CTX_new(method);
if (ogx == NULL) {
OG_THROW_ERROR(ERR_SSL_INIT_FAILED, 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(ogx, purpose)) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_SET_PURPOSE, ogx, NULL);
}
(void)SSL_CTX_set_min_proto_version(ogx, 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(ogx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_default_passwd_cb(ogx, cs_ssl_cb_get_pem_passwd);
(void)SSL_CTX_set_options(ogx, SSL_OP_CIPHER_SERVER_PREFERENCE);
if (cs_ssl_set_cipher(ogx, config, &is_using_tls13) != OG_SUCCESS) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_CIPHERS, ogx, NULL);
}
if (!is_using_tls13) {
(void)SSL_CTX_set_options(ogx, SSL_OP_NO_TLSv1_3);
}
if (cs_ssl_set_ca_chain(ogx, config, is_client) != OG_SUCCESS) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_LOAD_CA, ogx, NULL);
}
if (cs_ssl_set_crl_file(ogx, config) != OG_SUCCESS) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_LOAD_CRL, ogx, NULL);
}
OG_RETVALUE_IFTRUE(
cs_ssl_set_cert_auth(ogx, config->cert_file, config->key_file, config->key_password) != OG_SUCCESS, NULL);
if (!is_client && config->key_file == NULL && config->cert_file == NULL) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_NO_USABLE_CTX, ogx, NULL);
}
if (cs_ssl_set_tmp_dh(ogx) != OG_SUCCESS) {
OG_SSL_FREE_CTX_AND_RETURN(SSL_INITERR_DHFAIL, ogx, NULL);
}
return ogx;
}
* 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 cs_ssl_verify_cb(int32 ok, X509_STORE_CTX *ogx)
{
return ok;
}
static int32 ssl_get_expire_day(const ASN1_TIME *ogm, time_t *curr_time)
{
int day;
int sec;
ASN1_TIME *asn1_cmp_time = NULL;
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, ogm)) {
return -1;
}
return day;
}
static void ssl_check_cert_expire(X509 *cert, int32 alert_day, cert_type_t type)
{
const ASN1_TIME *not_after = NULL;
int32 expire_day;
if (cert == NULL) {
return;
}
not_after = X509_get0_notAfter(cert);
if (X509_cmp_current_time(not_after) <= 0) {
OG_LOG_RUN_WAR("The %s is expired", type == CERT_TYPE_SERVER_CERT ? "server certificate" : "ca");
} else {
time_t curr_time = cm_current_time();
expire_day = ssl_get_expire_day(not_after, &curr_time);
if (expire_day >= 0 && alert_day >= expire_day) {
OG_LOG_RUN_WAR("The %s will expire in %d days", type == CERT_TYPE_SERVER_CERT ? "server certificate" : "ca",
expire_day);
}
}
}
void ssl_ca_cert_expire(const ssl_ctx_t *ssl_context, int32 alert_day)
{
SSL_CTX *ogx = SSL_CTX_PTR(ssl_context);
X509 *cert = NULL;
X509_STORE *cert_store = NULL;
X509_OBJECT *obj = NULL;
if (ssl_context == NULL) {
return;
}
cert = SSL_CTX_get0_certificate(ogx);
if (cert != NULL) {
ssl_check_cert_expire(cert, alert_day, CERT_TYPE_SERVER_CERT);
}
cert_store = SSL_CTX_get_cert_store(ogx);
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++) {
obj = sk_X509_OBJECT_value(objects, i);
if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
cert = X509_OBJECT_get0_X509(obj);
ssl_check_cert_expire(cert, alert_day, CERT_TYPE_CA_CERT);
}
}
return;
}
ssl_ctx_t *cs_ssl_create_acceptor_fd(ssl_config_t *config)
{
SSL_CTX *ssl_fd = NULL;
int32 verify = SSL_VERIFY_PEER;
if (CM_IS_EMPTY_STR(config->ca_file)) {
verify = SSL_VERIFY_NONE;
} else if (config->verify_peer) {
verify = (uint32)verify | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
}
ssl_fd = cs_ssl_create_context(config, OG_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, OG_BUFLEN_128);
SSL_CTX_set_verify(ssl_fd, verify, 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 uchar *)&ssl_fd, sizeof(ssl_fd));
return (ssl_ctx_t *)ssl_fd;
}
ssl_ctx_t *cs_ssl_create_connector_fd(ssl_config_t *config)
{
SSL_CTX *ssl_fd = NULL;
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;
} else if (config->verify_peer) {
verify = (uint32)verify | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
}
ssl_fd = cs_ssl_create_context(config, OG_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 cs_ssl_free_context(ssl_ctx_t *ogx)
{
SSL_CTX_free(SSL_CTX_PTR(ogx));
cs_ssl_deinit();
}
static SSL *cs_ssl_create_socket(SSL_CTX *ogx, socket_t sock)
{
SSL *ssl_sock = SSL_new(ogx);
if (ssl_sock == NULL) {
OG_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 *get_common_name(X509_NAME *cert_name, char *buf, uint32 len)
{
char *name = NULL;
int32 cn_loc;
ASN1_STRING *cn_asn1 = NULL;
X509_NAME_ENTRY *cn_entry = NULL;
errno_t errcode;
cn_loc = X509_NAME_get_index_by_NID(cert_name, NID_commonName, -1);
if (cn_loc < 0) {
OG_LOG_DEBUG_ERR("[SSL] failed to get CN location in the certificate subject");
return "NONE";
}
cn_entry = X509_NAME_get_entry(cert_name, cn_loc);
if (cn_entry == NULL) {
OG_LOG_DEBUG_ERR("[SSL] failed to get CN entry using CN location");
return "NONE";
}
cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
if (cn_asn1 == NULL) {
OG_LOG_DEBUG_ERR("[SSL] failed to get CN from CN entry");
return "NONE";
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
name = (char *)ASN1_STRING_data(cn_asn1);
#else
name = (char *)ASN1_STRING_get0_data(cn_asn1);
#endif
if (name == NULL) {
OG_LOG_DEBUG_ERR("[SSL] failed to get ASN1 data");
return "NONE";
}
if ((size_t)ASN1_STRING_length(cn_asn1) != strlen(name)) {
OG_LOG_DEBUG_ERR("[SSL] NULL embedded in the certificate CN");
return "NONE";
}
errcode = strncpy_s(buf, len, name, strlen(name));
if (errcode != EOK) {
OG_THROW_ERROR(ERR_SYSTEM_CALL, (errcode));
return "NONE";
}
return buf;
}
static void cs_ssl_show_certs(SSL *ssl)
{
#if defined(_DEBUG) || defined(DEBUG) || defined(DB_DEBUG_VERSION)
char buf[OG_BUFLEN_512] = {0};
const SSL_CIPHER *cipher = NULL;
X509 *cert = NULL;
X509_NAME *cert_name = NULL;
OG_LOG_DEBUG_INF("SSL connection succeeded");
cipher = SSL_get_current_cipher(ssl);
OG_LOG_DEBUG_INF("Using cipher: %s", (cipher == NULL) ? "NONE" : SSL_CIPHER_get_name(cipher));
OG_LOG_DEBUG_INF("Peer certificate:");
cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) {
cert_name = X509_get_subject_name(cert);
if (cert_name != NULL) {
OG_LOG_DEBUG_INF("\tSubject: %s", get_common_name(cert_name, buf, sizeof(buf)));
}
cert_name = X509_get_issuer_name(cert);
if (cert_name != NULL) {
OG_LOG_DEBUG_INF("\tIssuer: %s", get_common_name(cert_name, buf, sizeof(buf)));
}
X509_free(cert);
} else {
OG_LOG_DEBUG_INF("Peer does not have certificate.");
}
OG_LOG_DEBUG_INF("\tSRV_TLS_VERSION: %s", SSL_get_version(ssl));
#endif
}
status_t cs_ssl_accept_socket(ssl_link_t *link, socket_t sock, int32 timeout)
{
int32 ret;
int32 tv = 0;
SSL_CTX *ogx = NULL;
SSL *ssl = NULL;
status_t status = OG_SUCCESS;
ogx = SSL_CTX_PTR(link->ssl_ctx);
OG_RETVALUE_IFTRUE(ogx == NULL, OG_ERROR);
ssl = cs_ssl_create_socket(ogx, sock);
OG_RETVALUE_IFTRUE(ssl == NULL, OG_ERROR);
link->tcp.sock = sock;
link->ssl_sock = (ssl_sock_t *)ssl;
do {
ret = SSL_accept(ssl);
if (ret == 1) {
status = OG_SUCCESS;
break;
}
status = cs_ssl_wait_on_error(link, ret, OG_NETWORK_IO_TIMEOUT);
if (status == OG_ERROR) {
break;
} else if (status == OG_TIMEDOUT) {
tv += OG_NETWORK_IO_TIMEOUT;
}
} while (tv < timeout && !SSL_is_init_finished(ssl));
if (status == OG_SUCCESS) {
cs_ssl_show_certs(ssl);
return OG_SUCCESS;
}
if (status == OG_TIMEDOUT) {
OG_LOG_RUN_ERR("ssl accept timeout(%d ms)", OG_SSL_IO_TIMEOUT);
}
SSL_free(ssl);
link->ssl_sock = NULL;
return OG_ERROR;
}
status_t cs_ssl_connect_socket(ssl_link_t *link, socket_t sock, int32 timeout)
{
int32 ret;
int32 tv = 0;
SSL_CTX *ogx = NULL;
SSL *ssl = NULL;
status_t status = OG_SUCCESS;
ogx = SSL_CTX_PTR(link->ssl_ctx);
OG_RETVALUE_IFTRUE(ogx == NULL, OG_ERROR);
ssl = cs_ssl_create_socket(ogx, sock);
OG_RETVALUE_IFTRUE(ssl == NULL, OG_ERROR);
link->tcp.sock = sock;
link->ssl_sock = (ssl_sock_t *)ssl;
do {
ret = SSL_connect(ssl);
status = cs_ssl_wait_on_error(link, ret, OG_NETWORK_IO_TIMEOUT);
if (status == OG_ERROR) {
break;
} else if (status == OG_TIMEDOUT) {
tv += OG_NETWORK_IO_TIMEOUT;
}
} while (tv < timeout && !SSL_is_init_finished(ssl));
if (status == OG_SUCCESS) {
return OG_SUCCESS;
}
SSL_free(ssl);
link->ssl_sock = NULL;
return OG_ERROR;
}
void cs_ssl_disconnect(ssl_link_t *link)
{
if (link->tcp.closed) {
CM_ASSERT(link->tcp.sock == CS_INVALID_SOCKET);
return;
}
SSL *ssl = SSL_SOCK(link->ssl_sock);
OG_RETVOID_IFTRUE(ssl == NULL);
SSL_set_quiet_shutdown(ssl, 1);
if (SSL_shutdown(ssl) != 1) {
OG_LOG_DEBUG_WAR("shutdown SSL failed.");
}
cs_tcp_disconnect(&link->tcp);
SSL_free(ssl);
link->ssl_sock = NULL;
}
status_t cs_ssl_send(ssl_link_t *link, const char *buf, uint32 size, int32 *send_size)
{
int32 ret;
int32 err;
uint32 wait_event;
SSL *ssl = SSL_SOCK(link->ssl_sock);
if (size == 0) {
*send_size = 0;
return OG_SUCCESS;
}
cm_set_sock_error(0);
ERR_clear_error();
ret = SSL_write(ssl, buf, size);
if (ret > 0) {
(*send_size) = ret;
return OG_SUCCESS;
}
if (!cs_ssl_should_retry(link, ret, &wait_event, &err)) {
if (cm_get_sock_error() == EWOULDBLOCK) {
(*send_size) = 0;
return OG_SUCCESS;
}
OG_THROW_ERROR(ERR_PEER_CLOSED_REASON, "ssl", err);
return OG_ERROR;
}
(*send_size) = 0;
return OG_SUCCESS;
}
status_t 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;
bool32 ready = OG_FALSE;
if (link->ssl_sock == NULL) {
OG_THROW_ERROR(ERR_PEER_CLOSED, "ssl");
return OG_ERROR;
}
if (cs_ssl_send(link, buf, size, &writen_size) != OG_SUCCESS) {
return OG_ERROR;
}
remain_size = size;
if (writen_size > 0) {
remain_size = size - writen_size;
offset = (uint32)writen_size;
}
while (remain_size > 0) {
if (cs_ssl_wait(link, CS_WAIT_FOR_WRITE, OG_POLL_WAIT, &ready) != OG_SUCCESS) {
return OG_ERROR;
}
if (!ready) {
wait_interval += OG_POLL_WAIT;
if (wait_interval >= timeout) {
OG_THROW_ERROR(ERR_TCP_TIMEOUT, "send data");
return OG_ERROR;
}
continue;
}
if (cs_ssl_send(link, buf + offset, remain_size, &writen_size) != OG_SUCCESS) {
return OG_ERROR;
}
if (writen_size > 0) {
remain_size -= writen_size;
offset += writen_size;
}
}
return OG_SUCCESS;
}
status_t cs_ssl_recv(ssl_link_t *link, char *buf, uint32 size, int32 *recv_size, uint32 *wait_event)
{
int32 ret;
int32 err;
SSL *ssl = SSL_SOCK(link->ssl_sock);
if (size == 0) {
(*recv_size) = 0;
return OG_SUCCESS;
}
for (;;) {
cm_set_sock_error(0);
ERR_clear_error();
ret = SSL_read(ssl, (void *)buf, (int32)size);
if (ret > 0) {
break;
}
if (!cs_ssl_should_retry(link, ret, wait_event, &err)) {
err = cm_get_sock_error();
if (err == EINTR || err == EAGAIN) {
continue;
}
if (err == ECONNRESET) {
OG_THROW_ERROR(ERR_PEER_CLOSED, "ssl");
}
return OG_ERROR;
}
*recv_size = 0;
return OG_SUCCESS;
}
*recv_size = ret;
return OG_SUCCESS;
}
static status_t cs_ssl_recv_remain(ssl_link_t *link, char *buf, uint32 offset_left, uint32 remain_size_left,
uint32 wait_event, uint32 timeout)
{
int32 recv_size;
uint32 wait_interval = 0;
bool32 ready = OG_FALSE;
int remain_size = remain_size_left;
int offset = offset_left;
while (remain_size > 0) {
OG_RETURN_IFERR(cs_ssl_wait(link, wait_event, OG_POLL_WAIT, &ready) != OG_SUCCESS);
if (!ready) {
wait_interval += OG_POLL_WAIT;
if (wait_interval >= timeout) {
OG_THROW_ERROR(ERR_TCP_TIMEOUT, "recv data");
return OG_ERROR;
}
continue;
}
OG_RETURN_IFERR(cs_ssl_recv(link, buf + offset, remain_size, &recv_size, &wait_event) != OG_SUCCESS);
remain_size -= recv_size;
offset += recv_size;
}
return OG_SUCCESS;
}
status_t cs_ssl_recv_timed(ssl_link_t *link, char *buf, uint32 size, uint32 timeout)
{
uint32 remain_size;
uint32 offset;
int32 recv_size;
uint32 wait_event = 0;
remain_size = size;
offset = 0;
OG_RETURN_IFERR(cs_ssl_recv(link, buf + offset, remain_size, &recv_size, &wait_event) != OG_SUCCESS);
remain_size -= recv_size;
offset += recv_size;
wait_event = (wait_event == 0) ? CS_WAIT_FOR_READ : wait_event;
return cs_ssl_recv_remain(link, buf, offset, remain_size, wait_event, timeout);
}
status_t cs_ssl_wait(ssl_link_t *link, uint32 wait_for, int32 timeout, bool32 *ready)
{
return cs_tcp_wait(&link->tcp, wait_for, timeout, ready);
}
status_t cs_ssl_read_buffer(ssl_link_t *link, char *buf, uint32 size, int32 *recv_size)
{
if (SSL_has_pending(SSL_SOCK(link->ssl_sock)) != 1) {
return OG_ERROR;
}
uint32 pending_size = SSL_pending(SSL_SOCK(link->ssl_sock));
uint32 wait_event = CS_WAIT_FOR_READ;
if (pending_size >= size) {
return cs_ssl_recv(link, buf, size, recv_size, &wait_event);
} else {
return OG_ERROR;
}
}
status_t cs_ssl_verify_certificate(ssl_link_t *link, ssl_verify_t vmode, const char *name, const char **errptr)
{
status_t ret = OG_SUCCESS;
SSL *ssl = SSL_SOCK(link->ssl_sock);
X509 *cert = NULL;
X509_NAME *attr = NULL;
char buf[OG_BUFLEN_512];
if (ssl == NULL) {
(*errptr) = "No SSL pointer found";
return OG_ERROR;
}
cert = SSL_get_peer_certificate(ssl);
if (cert == NULL && vmode != VERIFY_SSL) {
(*errptr) = "Cannot get peer certificate";
return OG_ERROR;
}
switch (vmode) {
case VERIFY_SSL:
case VERIFY_CERT:
ret = OG_SUCCESS;
break;
case VERIFY_ISSUER:
attr = X509_get_issuer_name(cert);
if ((attr != NULL) && (name != NULL) &&
cm_str_equal(name, get_common_name(attr, buf, sizeof(buf)))) {
ret = OG_SUCCESS;
} else {
(*errptr) = "SSL certificate issuer validation failure";
ret = OG_ERROR;
}
break;
case VERIFY_SUBJECT:
attr = X509_get_subject_name(cert);
if ((attr != NULL) && (name != NULL) &&
cm_str_equal(name, get_common_name(attr, buf, sizeof(buf)))) {
ret = OG_SUCCESS;
} else {
(*errptr) = "SSL certificate subject validation failure";
ret = OG_ERROR;
}
break;
default:
break;
}
X509_free(cert);
return ret;
}
const char **cs_ssl_get_default_cipher_list(void)
{
return g_ssl_cipher_names;
}
const char **cs_ssl_tls13_get_default_cipher_list(void)
{
return g_ssl_tls13_cipher_names;
}
status_t cs_ssl_verify_file_stat(const char *file_name)
{
#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))) {
OG_THROW_ERROR(ERR_SSL_FILE_PERMISSION, file_name);
return OG_ERROR;
}
}
#endif
return OG_SUCCESS;
}
#ifdef __cplusplus
}
#endif