Files
openGauss-server/src/common/backend/libpq/be-secure.cpp

1352 lines
45 KiB
C++

/* -------------------------------------------------------------------------
*
* be-secure.cpp
* functions related to setting up a secure connection to the frontend.
* Secure connections are expected to provide confidentiality,
* message integrity and endpoint authentication.
*
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/common/backend/libpq/be-secure.cpp
*
* Since the server static private key ($DataDir/server.key)
* will normally be stored unencrypted so that the database
* backend can restart automatically, it is important that
* we select an algorithm that continues to provide confidentiality
* even if the attacker has the server's private key. Ephemeral
* DH (EDH) keys provide this, and in fact provide Perfect Forward
* Secrecy (PFS) except for situations where the session can
* be hijacked during a periodic handshake/renegotiation.
* Right now client certificates are always used (since the
* imposter will be unable to successfully complete renegotiation).
*
* N.B., the static private key should still be protected to
* the largest extent possible, to minimize the risk of
* impersonations.
*
* Another benefit of EDH is that it allows the backend and
* clients to use DSA keys. DSA keys can only provide digital
* signatures, not encryption, and are often acceptable in
* jurisdictions where RSA keys are unacceptable.
*
* The downside to EDH is that it makes it impossible to
* use ssldump(1) if there's a problem establishing an SSL
* session. In this case you'll need to temporarily disable
* EDH by commenting out the callback.
*
* ...
*
* Because the risk of cryptanalysis increases as large
* amounts of data are sent with the same session key, the
* session keys are periodically renegotiated.
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#include <arpa/inet.h>
#endif
#ifdef USE_SSL
#include "openssl/err.h"
#include "openssl/ssl.h"
#include "openssl/rand.h"
#include "openssl/ossl_typ.h"
#include "openssl/sslerr.h"
#include "openssl/obj_mac.h"
#include "openssl/dh.h"
#include "openssl/bn.h"
#include "openssl/x509.h"
#include "openssl/x509_vfy.h"
#include "openssl/opensslconf.h"
#include "openssl/crypto.h"
#include "openssl/bio.h"
#endif /* USE_SSL */
#include "libpq/libpq.h"
#include "tcop/tcopprot.h"
#include "utils/memutils.h"
#include "libcomm/libcomm.h"
#include "miscadmin.h"
#include "cipher.h"
#include "pgstat.h"
#include "workload/workload.h"
#include "communication/commproxy_interface.h"
#ifdef USE_SSL
typedef enum DHKeyLength {
DHKey768 = 1,
DHKey1024,
DHKey1536,
DHKey2048,
DHKey3072,
DHKey4096,
DHKey6144,
DHKey8192
} DHKeyLength;
static int verify_cb(int ok, X509_STORE_CTX* ctx);
static void info_cb(const SSL* ssl, int type, int args);
static const char* SSLerrmessage(void);
static void set_user_config_ssl_ciphers(const char* sslciphers);
static void set_default_ssl_ciphers();
static void initialize_SSL(void);
static void secure_initialize(void);
static int open_server_SSL(Port*);
static void close_SSL(Port*);
static const char* SSLerrmessage(void);
static void init_server_ssl_passwd(SSL_CTX* pstContext);
static void check_permission_cipher_file(const char* parent_dir);
extern bool StreamThreadAmI();
static BIO_METHOD* my_BIO_s_socket(void);
static int SSL_set_fd_ex(Port* port, int fd);
static int my_sock_write(BIO* h, const char* buf, int size);
static int my_sock_read(BIO* h, char* buf, int size);
static char* ssl_cipher_list2string(const char* ciphers[], const int num);
static int SSL_CTX_set_cipher_list_ex(SSL_CTX* ctx, const char* ciphers[], const int num);
static DH* genDHKeyPair(DHKeyLength dhType);
extern THR_LOCAL unsigned char disable_pqlocking;
/* security ciphers suites in SSL connection */
static const char* ssl_ciphers_map[] = {
TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256, /* TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 */
TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384, /* TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 */
TLS1_TXT_DHE_RSA_WITH_AES_128_CCM, /* TLS_DHE_RSA_WITH_AES_128_CCM */
TLS1_TXT_DHE_RSA_WITH_AES_256_CCM, /* TLS_DHE_RSA_WITH_AES_256_CCM */
TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 */
TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 */
TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 */
TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 */
NULL};
#endif
const char* ssl_cipher_file = "server.key.cipher";
const char* ssl_rand_file = "server.key.rand";
/* ------------------------------------------------------------ */
/* Hardcoded values */
/* ------------------------------------------------------------ */
/*
* Hardcoded DH parameters, used in ephemeral DH keying.
* As discussed above, EDH protects the confidentiality of
* sessions even if the static private key is compromised,
* so we are *highly* motivated to ensure that we can use
* EDH even if the DBA... or an attacker... deletes the
* $DataDir/dh*.pem files.
*
* We could refuse SSL connections unless a good DH parameter
* file exists, but some clients may quietly renegotiate an
* unsecured connection without fully informing the user.
* Very uncool.
*
* Alternatively, the backend could attempt to load these files
* on startup if SSL is enabled - and refuse to start if any
* do not exist - but this would tend to piss off DBAs.
*
* If you want to create your own hardcoded DH parameters
* for fun and profit, review "Assigned Number for SKIP
* Protocols" (http://www.skip-vpn.org/spec/numbers.html)
* for suggestions.
*/
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
/* ------------------------------------------------------------ */
/*
* Initialize global context
*/
static void secure_initialize(void)
{
#ifdef USE_SSL
initialize_SSL();
#endif
return ;
}
/*
* Indicate if we have loaded the root CA store to verify certificates
*/
bool secure_loaded_verify_locations(void)
{
#ifdef USE_SSL
return u_sess->libpq_cxt.ssl_loaded_verify_locations;
#endif
return false;
}
/*
* Attempt to negotiate secure session.
*/
int secure_open_server(Port* port)
{
int r = 0;
#ifdef USE_SSL
secure_initialize();
r = open_server_SSL(port);
#endif
return r;
}
/*
* Close secure session.
*/
void secure_close(Port* port)
{
#ifdef USE_SSL
/*
* Free all resources about SSL, even though there is no SSL
* connections. Since we always allocating resources at the
* beginning of thread initialization.
*/
close_SSL(port);
#endif
}
/*
* Read data from a secure connection.
*/
ssize_t secure_read(Port* port, void* ptr, size_t len)
{
ssize_t n;
#ifdef USE_SSL
if (port->ssl != NULL) {
int err;
rloop:
errno = 0;
ERR_clear_error();
n = SSL_read(port->ssl, ptr, len);
err = SSL_get_error(port->ssl, n);
switch (err) {
case SSL_ERROR_NONE:
port->count += n;
break;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
if (port->noblock) {
errno = EWOULDBLOCK;
n = -1;
break;
}
#ifdef WIN32
pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
(err == SSL_ERROR_WANT_READ) ? (FD_READ | FD_CLOSE) : (FD_WRITE | FD_CLOSE),
INFINITE);
#endif
goto rloop;
case SSL_ERROR_SYSCALL:
/* leave it to caller to ereport the value of errno */
if (n != -1) {
errno = ECONNRESET;
n = -1;
}
break;
case SSL_ERROR_SSL:
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL error: %s, remote nodename %s", SSLerrmessage(), port->remote_hostname)));
/* fall through */
case SSL_ERROR_ZERO_RETURN:
errno = ECONNRESET;
n = -1;
break;
default:
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("unrecognized SSL error code: %d, remote nodename %s",
err, port->remote_hostname)));
n = -1;
break;
}
} else
#endif
{
if (port->is_logic_conn) {
prepare_for_logic_conn_read();
int producer;
retry:
NetWorkTimePollStart(t_thrd.pgxc_cxt.GlobalNetInstr);
n = gs_wait_poll(&(port->gs_sock), 1, &producer, -1, false);
NetWorkTimePollEnd(t_thrd.pgxc_cxt.GlobalNetInstr);
/* no data but wake up, retry */
if (n == 0) {
logic_conn_read_check_ended();
goto retry;
}
if (n > 0) {
n = gs_recv(&(port->gs_sock), ptr, len);
LIBCOMM_DEBUG_LOG("secure_read to node %s[nid:%d,sid:%d] with msg:%c, len:%d.",
port->remote_hostname,
port->gs_sock.idx,
port->gs_sock.sid,
((char*)ptr)[0],
(int)len);
}
logic_conn_read_check_ended();
} else {
prepare_for_client_read();
PGSTAT_INIT_TIME_RECORD();
PGSTAT_START_TIME_RECORD();
/* CommProxy Interface Support */
n = comm_recv(port->sock, ptr, len, 0);
END_NET_RECV_INFO(n);
client_read_ended();
}
}
/* for log printing, dn receive message */
IPC_PERFORMANCE_LOG_COLLECT(port->msgLog, ptr, n, port->remote_hostname, &port->gs_sock, SECURE_READ);
return n;
}
/*
* Write data to a secure connection.
*/
ssize_t secure_write(Port* port, void* ptr, size_t len)
{
ssize_t n;
StreamTimeSendStart(t_thrd.pgxc_cxt.GlobalNetInstr);
#ifdef USE_SSL
if (port->ssl != NULL) {
int err;
wloop:
errno = 0;
ERR_clear_error();
n = SSL_write(port->ssl, ptr, len);
err = SSL_get_error(port->ssl, n);
switch (err) {
case SSL_ERROR_NONE:
port->count += n;
break;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
if (port->noblock) {
errno = EWOULDBLOCK;
n = -1;
break;
}
#ifdef WIN32
pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
(err == SSL_ERROR_WANT_READ) ? (FD_READ | FD_CLOSE) : (FD_WRITE | FD_CLOSE),
INFINITE);
#endif
goto wloop;
case SSL_ERROR_SYSCALL:
/* leave it to caller to ereport the value of errno */
if (n != -1) {
errno = ECONNRESET;
n = -1;
}
break;
case SSL_ERROR_SSL:
ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("SSL error: %s", SSLerrmessage())));
/* fall through */
case SSL_ERROR_ZERO_RETURN:
errno = ECONNRESET;
n = -1;
break;
default:
ereport(
COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("unrecognized SSL error code: %d", err)));
n = -1;
break;
}
} else
#endif
/*
* for stream connection, when send msgs
* to multiple connections(broadcasting),
* gs_broadcast_send is called
* in this function the remaining connections
* will not be blocked when one connection is waitting quota.
*/
if (StreamThreadAmI()) {
if(port->libcomm_addrinfo->parallel_send_mode) {
n = gs_broadcast_send(port->libcomm_addrinfo, (char*)ptr, len, -1);
IPC_PERFORMANCE_LOG_COLLECT(port->msgLog, ptr, n, "all datanodes", NULL, SECURE_WRITE);
} else {
n = gs_send(&(port->libcomm_addrinfo->gs_sock), (char*)ptr, len, -1, TRUE);
IPC_PERFORMANCE_LOG_COLLECT(port->msgLog, ptr, n, port->libcomm_addrinfo->nodename,
&(port->libcomm_addrinfo->gs_sock), SECURE_WRITE);
}
}
/*
* for logic connection, gs_send is called
* as only one connection needed to send.
*/
else if (port->is_logic_conn) {
n = gs_send(&(port->gs_sock), (char*)ptr, len, -1, TRUE);
LIBCOMM_DEBUG_LOG("secure_write to node[nid:%d,sid:%d] with msg:%c, len:%d.",
port->gs_sock.idx,
port->gs_sock.sid,
((char*)ptr)[0],
(int)len);
/* for log printing, send message */
IPC_PERFORMANCE_LOG_COLLECT(port->msgLog, ptr, n, port->remote_hostname, &port->gs_sock, SECURE_WRITE);
} else {
PGSTAT_INIT_TIME_RECORD();
PGSTAT_START_TIME_RECORD();
/* CommProxy Interface Support */
n = comm_send(port->sock, ptr, len, 0);
PGSTAT_END_TIME_RECORD(NET_SEND_TIME);
END_NET_SEND_INFO(n);
/* for log printing, send message */
IPC_PERFORMANCE_LOG_COLLECT(port->msgLog, ptr, n, port->remote_hostname, NULL, SECURE_WRITE);
}
StreamTimeSendEnd(t_thrd.pgxc_cxt.GlobalNetInstr);
return n;
}
/* ------------------------------------------------------------ */
/* SSL specific code */
/* ------------------------------------------------------------ */
#ifdef USE_SSL
/*
* 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 int verify_cb(int ok, X509_STORE_CTX* ctx)
{
int cert_error = X509_STORE_CTX_get_error(ctx);
if (!ok)
{
ereport(LOG, (errmsg("verify error:num=%d:%s \n", cert_error,
X509_verify_cert_error_string(cert_error))));
switch (cert_error)
{
case X509_V_ERR_CRL_HAS_EXPIRED:
ok = 1;
break;
case X509_V_ERR_UNABLE_TO_GET_CRL:
ok = 1;
break;
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
ok = 1;
break;
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
ok = 1;
break;
case X509_V_ERR_CRL_NOT_YET_VALID:
ok = 1;
break;
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
ok = 1;
break;
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
ok = 1;
break;
case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER:
ok = 1;
break;
case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN:
ok = 1;
break;
case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION:
ok = 1;
break;
case X509_V_ERR_DIFFERENT_CRL_SCOPE:
ok = 1;
break;
case X509_V_ERR_CRL_PATH_VALIDATION_ERROR:
ok = 1;
break;
default:
break;
}
}
return ok;
}
/*
* This callback is used to copy SSL information messages
* into the openGauss log.
*/
static void info_cb(const SSL* ssl, int type, int args)
{
switch (type) {
case SSL_CB_HANDSHAKE_START:
ereport(DEBUG4, (errmsg_internal("SSL: handshake start")));
break;
case SSL_CB_HANDSHAKE_DONE:
ereport(DEBUG4, (errmsg_internal("SSL: handshake done")));
break;
case SSL_CB_ACCEPT_LOOP:
ereport(DEBUG4, (errmsg_internal("SSL: accept loop")));
break;
case SSL_CB_ACCEPT_EXIT:
ereport(DEBUG4, (errmsg_internal("SSL: accept exit (%d)", args)));
break;
case SSL_CB_CONNECT_LOOP:
ereport(DEBUG4, (errmsg_internal("SSL: connect loop")));
break;
case SSL_CB_CONNECT_EXIT:
ereport(DEBUG4, (errmsg_internal("SSL: connect exit (%d)", args)));
break;
case SSL_CB_READ_ALERT:
ereport(DEBUG4, (errmsg_internal("SSL: read alert (0x%04x)", args)));
break;
case SSL_CB_WRITE_ALERT:
ereport(DEBUG4, (errmsg_internal("SSL: write alert (0x%04x)", args)));
break;
default:
break;
}
}
/*
* Close SSL connection.
*/
static void close_SSL(Port* port)
{
if (port->ssl != NULL) {
SSL_shutdown(port->ssl);
SSL_free(port->ssl);
port->ssl = NULL;
}
if (port->peer != NULL) {
X509_free(port->peer);
port->peer = NULL;
}
if (port->peer_cn != NULL) {
pfree(port->peer_cn);
port->peer_cn = NULL;
}
/* Free SSL context */
if (u_sess->libpq_cxt.SSL_server_context != NULL) {
SSL_CTX_free(u_sess->libpq_cxt.SSL_server_context);
u_sess->libpq_cxt.SSL_server_context = NULL;
}
}
/*
* Brief :set_default_ssl_ciphers,set default ssl ciphers
* Description : SEC.CNF.004
*/
static void set_default_ssl_ciphers()
{
int default_ciphers_count = 0;
for (int i = 0; ssl_ciphers_map[i] != NULL; i++) {
default_ciphers_count++;
}
if (SSL_CTX_set_cipher_list_ex(u_sess->libpq_cxt.SSL_server_context, ssl_ciphers_map, default_ciphers_count) != 1) {
ereport(FATAL, (errmsg("could not set the cipher list (no valid ciphers available)")));
}
}
/*
* Brief : set_user_config_ssl_ciphers,set the specified ssl ciphers by user
* Description : SEC.CNF.004
*/
static void set_user_config_ssl_ciphers(const char* sslciphers)
{
char* cipherStr = NULL;
char* cipherStr_tmp = NULL;
char* token = NULL;
int counter = 1;
char** ciphers_list = NULL;
bool find_ciphers_in_list = false;
int i = 0;
char* ptok = NULL;
if (sslciphers == NULL) {
ereport(ERROR, (errmsg("sslciphers can not be null")));
} else {
cipherStr = (char*)strchr(sslciphers, ';'); /*if the sslciphers does not contain character ';',the count is 1*/
while (cipherStr != NULL) {
counter++;
cipherStr++;
if (*cipherStr == '\0') {
break;
}
cipherStr = strchr(cipherStr, ';');
}
ciphers_list = (char**)palloc(counter * sizeof(char*));
Assert(ciphers_list != NULL);
if (ciphers_list == NULL) {
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("malloc failed")));
}
cipherStr_tmp = pstrdup(sslciphers);
if (cipherStr_tmp == NULL) {
if (ciphers_list != NULL)
pfree(ciphers_list);
ciphers_list = NULL;
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("malloc failed")));
}
token = strtok_r(cipherStr_tmp, ";", &ptok);
while (token != NULL) {
for (int j = 0; ssl_ciphers_map[j] != NULL; j++) {
if (strlen(ssl_ciphers_map[j]) == strlen(token) &&
strncmp(ssl_ciphers_map[j], token, strlen(token)) == 0) {
ciphers_list[i] = (char*)ssl_ciphers_map[j];
find_ciphers_in_list = true;
break;
}
}
if (!find_ciphers_in_list) {
errno_t errorno = EOK;
const int maxCipherStrLen = 64;
char errormessage[maxCipherStrLen] = {0};
errorno = strncpy_s(errormessage, sizeof(errormessage), token, sizeof(errormessage) - 1);
securec_check(errorno, cipherStr_tmp, ciphers_list, "\0");
errormessage[maxCipherStrLen - 1] = '\0';
if (cipherStr_tmp != NULL) {
pfree(cipherStr_tmp);
cipherStr_tmp = NULL;
}
if (ciphers_list != NULL) {
pfree(ciphers_list);
ciphers_list = NULL;
}
ereport(ERROR, (errmsg("unrecognized ssl ciphers name: \"%s\"", errormessage)));
}
token = strtok_r(NULL, ";", &ptok);
i++;
find_ciphers_in_list = false;
}
}
if (SSL_CTX_set_cipher_list_ex(u_sess->libpq_cxt.SSL_server_context, (const char**)ciphers_list, counter) != 1) {
if (cipherStr_tmp != NULL) {
pfree(cipherStr_tmp);
cipherStr_tmp = NULL;
}
if (ciphers_list != NULL) {
pfree(ciphers_list);
ciphers_list = NULL;
}
ereport(FATAL, (errmsg("could not set the cipher list (no valid ciphers available)")));
}
if (cipherStr_tmp != NULL) {
pfree(cipherStr_tmp);
cipherStr_tmp = NULL;
}
if (ciphers_list != NULL) {
pfree(ciphers_list);
ciphers_list = NULL;
}
}
/*
* Brief : static void initialize_SSL(void)
* Description : Initialize global SSL context.
*/
static void initialize_SSL(void)
{
/* Already initialized SSL, return here */
if (u_sess->libpq_cxt.ssl_initialized) {
return;
}
#define MAX_CERTIFICATE_DEPTH_SUPPORTED 20 /* The max certificate depth supported. */
struct stat buf;
STACK_OF(X509_NAME)* root_cert_list = NULL;
errno_t errorno = EOK;
if (!u_sess->libpq_cxt.SSL_server_context) {
/* Sets the SSL library for thread safe running*/
(void)OPENSSL_init_ssl(0, NULL);
SSL_load_error_strings();
u_sess->libpq_cxt.SSL_server_context = SSL_CTX_new(TLS_method());
if (!u_sess->libpq_cxt.SSL_server_context) {
ereport(FATAL, (errmsg("could not create SSL context : %s.)", SSLerrmessage())));
}
/*
* Disable moving-write-buffer sanity check, because it
* causes unnecessary failures in nonblocking send cases.
*/
SSL_CTX_set_mode(u_sess->libpq_cxt.SSL_server_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
/* set the default password for certificate/private key loading */
init_server_ssl_passwd(u_sess->libpq_cxt.SSL_server_context);
/* Load and verify server's certificate and private key*/
if (SSL_CTX_use_certificate_chain_file(
u_sess->libpq_cxt.SSL_server_context, g_instance.attr.attr_security.ssl_cert_file) != 1) {
ereport(FATAL,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not load server certificate file \"%s\": %s",
g_instance.attr.attr_security.ssl_cert_file,
SSLerrmessage())));
}
/* check certificate file permission */
#if !defined(WIN32) && !defined(__CYGWIN__)
if (stat(g_instance.attr.attr_security.ssl_cert_file, &buf) == 0){
if (!S_ISREG(buf.st_mode) || (buf.st_mode & (S_IRWXG | S_IRWXO)) || ((buf.st_mode & S_IRWXU) == S_IRWXU)) {
ereport(FATAL,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("certificate file \"%s\" has group or world access",
g_instance.attr.attr_security.ssl_cert_file),
errdetail("Permissions should be u=rw (0600) or less.")));
}
}
#endif
if (stat(g_instance.attr.attr_security.ssl_key_file, &buf) != 0) {
ereport(FATAL,
(errcode_for_file_access(),
errmsg(
"could not access private key file \"%s\": %m", g_instance.attr.attr_security.ssl_key_file)));
}
/*
* Require no public access to key file.
*
* XXX temporarily suppress check when on Windows, because there may
* not be proper support for Unix-y file permissions. Need to think
* of a reasonable check to apply on Windows. (See also the data
* directory permission check in postmaster.c)
*/
#if !defined(WIN32) && !defined(__CYGWIN__)
if (!S_ISREG(buf.st_mode) || (buf.st_mode & (S_IRWXG | S_IRWXO)) || ((buf.st_mode & S_IRWXU) == S_IRWXU)) {
ereport(FATAL,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("private key file \"%s\" has group or world access",
g_instance.attr.attr_security.ssl_key_file),
errdetail("Permissions should be u=rw (0600) or less.")));
}
#endif
if (SSL_CTX_use_PrivateKey_file(u_sess->libpq_cxt.SSL_server_context,
g_instance.attr.attr_security.ssl_key_file,
SSL_FILETYPE_PEM) != 1) {
ereport(FATAL,
(errmsg("could not load private key file \"%s\": %s",
g_instance.attr.attr_security.ssl_key_file,
SSLerrmessage())));
}
if (SSL_CTX_check_private_key(u_sess->libpq_cxt.SSL_server_context) != 1) {
ereport(FATAL,
(errmsg("check of private key \"%s\"failed: %s",
g_instance.attr.attr_security.ssl_key_file,
SSLerrmessage())));
}
}
/* check ca certificate file permission */
#if !defined(WIN32) && !defined(__CYGWIN__)
if (stat(g_instance.attr.attr_security.ssl_ca_file, &buf) == 0){
if (!S_ISREG(buf.st_mode) || (buf.st_mode & (S_IRWXG | S_IRWXO)) || ((buf.st_mode & S_IRWXU) == S_IRWXU)) {
ereport(FATAL,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("ca certificate file \"%s\" has group or world access",
g_instance.attr.attr_security.ssl_ca_file),
errdetail("Permissions should be u=rw (0600) or less.")));
}
}
#endif
/* Check the signature algorithm.*/
if (check_certificate_signature_algrithm(u_sess->libpq_cxt.SSL_server_context)) {
ereport(WARNING, (errmsg("The server certificate contain a Low-intensity signature algorithm")));
}
/* Check the certificate expires time.*/
long leftspan = check_certificate_time(u_sess->libpq_cxt.SSL_server_context,
u_sess->attr.attr_security.ssl_cert_notify_time);
if (leftspan > 0) {
int leftdays = (leftspan / 86400 > 0) ? (leftspan / 86400) : 1;
if (leftdays > 1) {
ereport(WARNING, (errmsg("The server certificate will expire in %d days", leftdays)));
} else {
ereport(WARNING, (errmsg("The server certificate will expire in %d day", leftdays)));
}
}
/* set up ephemeral DH keys, and disallow SSL v2 while at it
* free the dh directly safe as there is reference counts in DH
*/
DH* dhkey = genDHKeyPair(DHKey3072);
if (dhkey == NULL) {
ereport(ERROR, (errmsg("DH: generating parameters (3072 bits) failed")));
}
SSL_CTX_set_tmp_dh(u_sess->libpq_cxt.SSL_server_context, dhkey);
DH_free(dhkey);
/* SSL2.0/SSL3.0/TLS1.0/TLS1.1 is forbidden here. */
SSL_CTX_set_options(u_sess->libpq_cxt.SSL_server_context,
SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
/* set up the allowed cipher list */
if (strcasecmp(g_instance.attr.attr_security.SSLCipherSuites, "ALL") == 0) {
set_default_ssl_ciphers();
} else {
set_user_config_ssl_ciphers(g_instance.attr.attr_security.SSLCipherSuites);
}
/* Load CA store, so we can verify client certificates if needed.*/
if (g_instance.attr.attr_security.ssl_ca_file[0]) {
if (SSL_CTX_load_verify_locations(
u_sess->libpq_cxt.SSL_server_context, g_instance.attr.attr_security.ssl_ca_file, NULL) != 1) {
ereport(FATAL, (errmsg("could not load the ca certificate file")));
}
root_cert_list = SSL_load_client_CA_file(g_instance.attr.attr_security.ssl_ca_file);
if (root_cert_list == NULL) {
ereport(FATAL,
(errmsg("could not load root certificate file \"%s\": %s",
g_instance.attr.attr_security.ssl_ca_file,
SSLerrmessage())));
}
}
/* Load the Certificate Revocation List (CRL).*/
if (g_instance.attr.attr_security.ssl_crl_file[0]) {
X509_STORE* cvstore = SSL_CTX_get_cert_store(u_sess->libpq_cxt.SSL_server_context);
if (cvstore != NULL) {
/* Set the flags to check against the complete CRL chain */
if (1 == X509_STORE_load_locations(cvstore, g_instance.attr.attr_security.ssl_crl_file, NULL)) {
(void)X509_STORE_set_flags(cvstore, X509_V_FLAG_CRL_CHECK);
} else {
ereport(WARNING,
(errmsg("could not load SSL certificate revocation list file \"%s\": %s",
g_instance.attr.attr_security.ssl_crl_file,
SSLerrmessage())));
}
}
}
if (g_instance.attr.attr_security.ssl_ca_file[0]) {
/*
* Always ask for SSL client cert, but don't fail if it's not
* presented. We might fail such connections later, depending on
* what we find in pg_hba.conf.
*/
SSL_CTX_set_verify(u_sess->libpq_cxt.SSL_server_context, (SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE), verify_cb);
/* Increase the depth to support multi-level certificate. */
SSL_CTX_set_verify_depth(u_sess->libpq_cxt.SSL_server_context, (MAX_CERTIFICATE_DEPTH_SUPPORTED - 2));
/* Set flag to remember CA store is successfully loaded */
u_sess->libpq_cxt.ssl_loaded_verify_locations = true;
/*
* send the list of root certs we trust to clients in
* CertificateRequests. This lets a client with a keystore select the
* appropriate client certificate to send to us.
*/
SSL_CTX_set_client_CA_list(u_sess->libpq_cxt.SSL_server_context, root_cert_list);
}
/*clear the sensitive info in server_key*/
errorno = memset_s(u_sess->libpq_cxt.server_key, CIPHER_LEN + 1, 0, CIPHER_LEN + 1);
securec_check(errorno, "\0", "\0");
u_sess->libpq_cxt.ssl_initialized = true;
}
/*
* Brief : static int open_server_SSL(Port *port)
* Description : Attempt to negotiate SSL connection.
*/
static int open_server_SSL(Port* port)
{
int r;
int err;
Assert(port->ssl == NULL);
Assert(port->peer == NULL);
port->ssl = SSL_new(u_sess->libpq_cxt.SSL_server_context);
if (port->ssl == NULL) {
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("could not initialize SSL connection: %s", SSLerrmessage())));
close_SSL(port);
return -1;
}
if (1 != SSL_set_fd_ex(port, (int)((intptr_t)(port->sock)))) {
ereport(
COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("could not set SSL socket: %s", SSLerrmessage())));
close_SSL(port);
return -1;
}
aloop:
ERR_clear_error();
r = SSL_accept(port->ssl);
if (r != 1) {
err = SSL_get_error(port->ssl, r);
switch (err) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
#ifdef WIN32
pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
(err == SSL_ERROR_WANT_READ) ? (FD_READ | FD_CLOSE | FD_ACCEPT) : (FD_WRITE | FD_CLOSE),
INFINITE);
#endif
goto aloop;
case SSL_ERROR_SYSCALL:
if (r < 0)
ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not accept SSL connection: %m")));
else
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("could not accept SSL connection: EOF detected")));
break;
case SSL_ERROR_SSL:
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("could not accept SSL connection: %s", SSLerrmessage())));
break;
case SSL_ERROR_ZERO_RETURN:
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("could not accept SSL connection: EOF detected")));
break;
default:
ereport(
COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("unrecognized SSL error code: %d", err)));
break;
}
close_SSL(port);
return -1;
}
port->count = 0;
/* Get client certificate, if available. */
port->peer = SSL_get_peer_certificate(port->ssl);
/* and extract the Common Name from it. */
port->peer_cn = NULL;
if (port->peer != NULL) {
int rt;
int len;
char* peer_cn = NULL;
/* First find out the name's length and allocate a buffer for it. */
len = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer), NID_commonName, NULL, 0);
if (len != -1) {
peer_cn = (char*)palloc(len + 1);
rt = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer), NID_commonName, peer_cn, len + 1);
if (rt != len) {
/* shouldn't happen */
pfree(peer_cn);
close_SSL(port);
return -1;
}
/*
* Reject embedded NULLs in certificate common name to prevent
* attacks like CVE-2009-4034.
*/
if ((size_t)(unsigned)len != strlen(peer_cn)) {
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL certificate's common name contains embedded null")));
pfree(peer_cn);
close_SSL(port);
return -1;
}
port->peer_cn = peer_cn;
}
}
ereport(DEBUG2, (errmsg("SSL connection from \"%s\"", port->peer_cn ? port->peer_cn : "(anonymous)")));
/* set up debugging/info callback */
if (port->peer != NULL)
SSL_set_info_callback(port->ssl, info_cb);
return 0;
}
/*
* Obtain reason string for last SSL error
*
* Some caution is needed here since ERR_reason_error_string will
* return NULL if it doesn't recognize the error code. We don't
* want to return NULL ever.
*/
static const char* SSLerrmessage(void)
{
unsigned long errcode;
const char* errreason = NULL;
static THR_LOCAL char errbuf[32];
errcode = ERR_get_error();
if (errcode == 0)
return _("no SSL error reported");
errreason = ERR_reason_error_string(errcode);
if (errreason != NULL)
return errreason;
int rcs = snprintf_s(errbuf, sizeof(errbuf), sizeof(errbuf) - 1, _("SSL error code %lu"), errcode);
securec_check_ss(rcs, "\0", "\0");
return errbuf;
}
/* set the default password for certificate/private key loading */
static void init_server_ssl_passwd(SSL_CTX* pstContext)
{
char* parentdir = NULL;
KeyMode keymode = SERVER_MODE;
if (is_absolute_path(g_instance.attr.attr_security.ssl_key_file)) {
parentdir = pstrdup(g_instance.attr.attr_security.ssl_key_file);
get_parent_directory(parentdir);
decode_cipher_files(keymode, NULL, parentdir, u_sess->libpq_cxt.server_key);
} else {
decode_cipher_files(keymode, NULL, t_thrd.proc_cxt.DataDir, u_sess->libpq_cxt.server_key);
parentdir = pstrdup(t_thrd.proc_cxt.DataDir);
}
check_permission_cipher_file(parentdir);
pfree_ext(parentdir);
SSL_CTX_set_default_passwd_cb_userdata(pstContext, (char*)u_sess->libpq_cxt.server_key);
}
/* Check permissions of cipher file and rand file in server */
static void check_permission_cipher_file(const char* parent_dir)
{
char cipher_file[MAXPGPATH] = {0};
char rand_file[MAXPGPATH] = {0};
struct stat cipherbuf;
struct stat randbuf;
int rcs = snprintf_s(cipher_file, MAXPGPATH, MAXPGPATH - 1, "%s/server%s", parent_dir, CIPHER_KEY_FILE);
securec_check_ss(rcs, "\0", "\0");
rcs = snprintf_s(rand_file, MAXPGPATH, MAXPGPATH - 1, "%s/server%s", parent_dir, RAN_KEY_FILE);
securec_check_ss(rcs, "\0", "\0");
if (lstat(cipher_file, &cipherbuf) != 0 || lstat(rand_file, &randbuf) != 0)
return;
#if !defined(WIN32) && !defined(__CYGWIN__)
if (!S_ISREG(cipherbuf.st_mode) || (cipherbuf.st_mode & (S_IRWXG | S_IRWXO)) || ((cipherbuf.st_mode & S_IRWXU) == S_IRWXU))
ereport(FATAL,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("cipher file \"%s\" has group or world access", cipher_file),
errdetail("Permissions should be u=rw (0600) or less.")));
if (!S_ISREG(randbuf.st_mode) || (randbuf.st_mode & (S_IRWXG | S_IRWXO)) || ((randbuf.st_mode & S_IRWXU) == S_IRWXU))
ereport(FATAL,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("rand file \"%s\" has group or world access", rand_file),
errdetail("Permissions should be u=rw (0600) or less.")));
#endif
}
/*
* Private substitute BIO: this does the sending and receiving using send() and
* recv() instead. This is so that we can enable and disable interrupts
* just while calling recv(). We cannot have interrupts occurring while
* the bulk of openssl runs, because it uses malloc() and possibly other
* non-reentrant libc facilities. We also need to call send() and recv()
* directly so it gets passed through the socket/signals layer on Win32.
*
* They are closely modelled on the original socket implementations in OpenSSL.
*/
static BIO_METHOD* my_bio_methods = NULL;
ssize_t secure_raw_read(Port* port, void* ptr, size_t len)
{
ssize_t n;
/*
* Try to read from the socket without blocking. If it succeeds we're
* done, otherwise we'll wait for the socket using the latch mechanism.
*/
#ifdef WIN32
pgwin32_noblock = true;
#endif
PGSTAT_INIT_TIME_RECORD();
PGSTAT_START_TIME_RECORD();
/* CommProxy Interface Support */
n = comm_recv(port->sock, ptr, len, 0);
END_NET_RECV_INFO(n);
#ifdef WIN32
pgwin32_noblock = false;
#endif
return n;
}
ssize_t secure_raw_write(Port* port, const void* ptr, size_t len)
{
ssize_t n;
#ifdef WIN32
pgwin32_noblock = true;
#endif
PGSTAT_INIT_TIME_RECORD();
PGSTAT_START_TIME_RECORD();
/* CommProxy Interface Support */
n = comm_send(port->sock, ptr, len, 0);
END_NET_SEND_INFO(n);
#ifdef WIN32
pgwin32_noblock = false;
#endif
return n;
}
static int my_sock_read(BIO* h, char* buf, int size)
{
int res = 0;
if (buf != NULL) {
Port* myPort = (Port*)BIO_get_data(h);
if (myPort == NULL) {
return 0;
}
prepare_for_client_read();
res = secure_raw_read(myPort, buf, size);
client_read_ended();
BIO_clear_retry_flags(h);
if (res <= 0) {
/* If we were interrupted, tell caller to retry */
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
BIO_set_retry_read(h);
}
}
}
return res;
}
static int my_sock_write(BIO* h, const char* buf, int size)
{
int res = 0;
Port* myPort = (Port*)BIO_get_data(h);
if (myPort == NULL) {
return 0;
}
res = secure_raw_write(myPort, (void*)buf, size);
BIO_clear_retry_flags(h);
if (res <= 0) {
/* If we were interrupted, tell caller to retry */
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
BIO_set_retry_write(h);
}
}
return res;
}
static BIO_METHOD* my_BIO_s_socket(void)
{
if (my_bio_methods == NULL) {
int my_bio_index;
my_bio_index = BIO_get_new_index();
if (my_bio_index == -1)
return NULL;
BIO_METHOD* biom = (BIO_METHOD*)BIO_s_socket();
my_bio_methods = BIO_meth_new(my_bio_index, "socket");
if (my_bio_methods == NULL) {
return NULL;
}
if (!BIO_meth_set_write(my_bio_methods, my_sock_write) || !BIO_meth_set_read(my_bio_methods, my_sock_read) ||
!BIO_meth_set_gets(my_bio_methods, BIO_meth_get_gets(biom)) ||
!BIO_meth_set_puts(my_bio_methods, BIO_meth_get_puts(biom)) ||
!BIO_meth_set_ctrl(my_bio_methods, BIO_meth_get_ctrl(biom)) ||
!BIO_meth_set_create(my_bio_methods, BIO_meth_get_create(biom)) ||
!BIO_meth_set_destroy(my_bio_methods, BIO_meth_get_destroy(biom)) ||
!BIO_meth_set_callback_ctrl(my_bio_methods, BIO_meth_get_callback_ctrl(biom))) {
BIO_meth_free(my_bio_methods);
my_bio_methods = NULL;
return NULL;
}
}
return my_bio_methods;
}
/* This should exactly match openssl's SSL_set_fd except for using my BIO */
static int SSL_set_fd_ex(Port* port, int fd)
{
BIO* bio = NULL;
BIO_METHOD* bio_method = NULL;
bio_method = my_BIO_s_socket();
if (bio_method == NULL) {
SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
return 0;
}
bio = BIO_new(bio_method);
if (bio == NULL) {
SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
return 0;
}
BIO_set_data(bio, port);
BIO_set_fd(bio, fd, BIO_NOCLOSE);
SSL_set_bio(port->ssl, bio, bio);
return 1;
}
static char* ssl_cipher_list2string(const char* ciphers[], const int num)
{
int i;
int catlen = 0;
char* cipher_buf = NULL;
errno_t errorno = EOK;
size_t CIPHER_BUF_SIZE = 0;
for (i = 0; i < num; i++) {
CIPHER_BUF_SIZE += (strlen(ciphers[i]) + 1);
}
cipher_buf = (char*)OPENSSL_zalloc(CIPHER_BUF_SIZE);
if (cipher_buf == NULL) {
return NULL;
}
for (i = 0; i < num; i++) {
errorno = memcpy_s(cipher_buf + catlen, strlen(ciphers[i]), ciphers[i], strlen(ciphers[i]));
securec_check(errorno, "\0", "\0");
catlen += strlen(ciphers[i]);
if (i < num - 1) {
errorno = memcpy_s(cipher_buf + catlen, CIPHER_BUF_SIZE - catlen, ":", 1);
securec_check(errorno, "\0", "\0");
catlen += 1;
}
}
cipher_buf[catlen] = 0;
return cipher_buf;
}
/*
* Brief : static int SSL_CTX_set_cipher_list_ex(SSL_CTX *ctx, const char* ciphers[], const int num)
* Description : set ssl ciphers.
*/
static int SSL_CTX_set_cipher_list_ex(SSL_CTX* ctx, const char* ciphers[], const int num)
{
int ret = 0;
char* cipher_buf = NULL;
if (ctx == NULL) {
return 0;
}
cipher_buf = ssl_cipher_list2string(ciphers, num);
if (cipher_buf == NULL) {
return 0;
}
ret = SSL_CTX_set_cipher_list(ctx, cipher_buf);
OPENSSL_free(cipher_buf);
return ret;
}
/*
* Brief : DH* genDHKeyPair(DHKeyLength dhType)
* Notes : function to generate DH key pair
*/
DH* genDHKeyPair(DHKeyLength dhType)
{
int ret = 0;
DH* dh = NULL;
BIGNUM* bn_prime = NULL;
unsigned char GENERATOR_2[] = {DH_GENERATOR_2};
BIGNUM* bn_genenrator_2 = BN_bin2bn(GENERATOR_2, sizeof(GENERATOR_2), NULL);
if (bn_genenrator_2 == NULL) {
return NULL;
}
switch (dhType) {
case DHKey768:
bn_prime = BN_get_rfc2409_prime_768(NULL);
break;
case DHKey1024:
bn_prime = BN_get_rfc2409_prime_1024(NULL);
break;
case DHKey1536:
bn_prime = BN_get_rfc3526_prime_1536(NULL);
break;
case DHKey2048:
bn_prime = BN_get_rfc3526_prime_2048(NULL);
break;
case DHKey3072:
bn_prime = BN_get_rfc3526_prime_3072(NULL);
break;
case DHKey4096:
bn_prime = BN_get_rfc3526_prime_4096(NULL);
break;
case DHKey6144:
bn_prime = BN_get_rfc3526_prime_6144(NULL);
break;
case DHKey8192:
bn_prime = BN_get_rfc3526_prime_8192(NULL);
break;
default:
break;
}
if (bn_prime == NULL) {
BN_free(bn_genenrator_2);
return NULL;
}
dh = DH_new();
if (dh == NULL) {
BN_free(bn_prime);
BN_free(bn_genenrator_2);
return NULL;
}
ret = DH_set0_pqg(dh, bn_prime, NULL, bn_genenrator_2);
if (!ret) {
BN_free(bn_prime);
BN_free(bn_genenrator_2);
DH_free(dh);
return NULL;
}
ret = DH_generate_key(dh);
if (!ret) {
BN_free(bn_prime);
BN_free(bn_genenrator_2);
DH_free(dh);
return NULL;
}
return dh;
}
#endif /* USE_SSL */