fix and enable SSL support when OpenSSL is available
Signed-off-by: Erik Boasson <eb@ilities.com>
This commit is contained in:
parent
acec84cf0b
commit
f31fba8766
7 changed files with 158 additions and 239 deletions
|
@ -19,7 +19,6 @@ FUNCTION(PREPEND var prefix)
|
||||||
SET(${var} "${listVar}" PARENT_SCOPE)
|
SET(${var} "${listVar}" PARENT_SCOPE)
|
||||||
ENDFUNCTION(PREPEND)
|
ENDFUNCTION(PREPEND)
|
||||||
|
|
||||||
|
|
||||||
option(DDSC_SHARED "Build DDSC as a shared library" ON)
|
option(DDSC_SHARED "Build DDSC as a shared library" ON)
|
||||||
|
|
||||||
if(DDSC_SHARED AND ((NOT DEFINED BUILD_SHARED_LIBS) OR BUILD_SHARED_LIBS))
|
if(DDSC_SHARED AND ((NOT DEFINED BUILD_SHARED_LIBS) OR BUILD_SHARED_LIBS))
|
||||||
|
@ -34,6 +33,14 @@ endif()
|
||||||
|
|
||||||
add_definitions(-DDDSI_INCLUDE_NETWORK_PARTITIONS -DDDSI_INCLUDE_SSM)
|
add_definitions(-DDDSI_INCLUDE_NETWORK_PARTITIONS -DDDSI_INCLUDE_SSM)
|
||||||
|
|
||||||
|
find_package(OpenSSL)
|
||||||
|
if(OPENSSL_FOUND)
|
||||||
|
add_definitions(-DDDSI_INCLUDE_SSL)
|
||||||
|
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||||
|
target_link_libraries(ddsc PRIVATE ${OPENSSL_LIBRARIES})
|
||||||
|
message(STATUS "Using OpenSSL ${OPENSSL_VERSION} at ${OPENSSL_INCLUDE_DIR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
include(ddsi/CMakeLists.txt)
|
include(ddsi/CMakeLists.txt)
|
||||||
include(ddsc/CMakeLists.txt)
|
include(ddsc/CMakeLists.txt)
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,13 @@
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
/* WinSock2 must be included before openssl headers
|
/* supposedly WinSock2 must be included before openssl headers otherwise winsock will be used */
|
||||||
otherwise winsock will be used */
|
|
||||||
#include <WinSock2.h>
|
#include <WinSock2.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
void ddsi_ssl_plugin (void);
|
struct ddsi_ssl_plugins;
|
||||||
|
void ddsi_ssl_config_plugin (struct ddsi_ssl_plugins *plugin);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -20,20 +20,17 @@
|
||||||
|
|
||||||
struct ddsi_ssl_plugins
|
struct ddsi_ssl_plugins
|
||||||
{
|
{
|
||||||
void (*config) (void);
|
bool (*init) (void);
|
||||||
c_bool (*init) (void);
|
|
||||||
void (*fini) (void);
|
void (*fini) (void);
|
||||||
void (*ssl_free) (SSL * ssl);
|
void (*ssl_free) (SSL *ssl);
|
||||||
void (*bio_vfree) (BIO * bio);
|
void (*bio_vfree) (BIO *bio);
|
||||||
os_ssize_t (*read) (SSL * ssl, void * buf, os_size_t len, int * err);
|
ssize_t (*read) (SSL *ssl, void *buf, size_t len, int *err);
|
||||||
os_ssize_t (*write) (SSL * ssl, const void * msg, os_size_t len, int * err);
|
ssize_t (*write) (SSL *ssl, const void *msg, size_t len, int *err);
|
||||||
SSL * (*connect) (os_socket sock);
|
SSL * (*connect) (os_socket sock);
|
||||||
BIO * (*listen) (os_socket sock);
|
BIO * (*listen) (os_socket sock);
|
||||||
SSL * (*accept) (BIO * bio, os_socket * sock);
|
SSL * (*accept) (BIO *bio, os_socket *sock);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ddsi_ssl_plugins ddsi_tcp_ssl_plugin;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int ddsi_tcp_init (void);
|
int ddsi_tcp_init (void);
|
||||||
|
|
|
@ -9,323 +9,242 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||||
*/
|
*/
|
||||||
|
#include "os/os.h"
|
||||||
|
#include "ddsi/ddsi_tcp.h"
|
||||||
#include "ddsi/ddsi_ssl.h"
|
#include "ddsi/ddsi_ssl.h"
|
||||||
#include "ddsi/q_config.h"
|
#include "ddsi/q_config.h"
|
||||||
#include "ddsi/q_log.h"
|
#include "ddsi/q_log.h"
|
||||||
#include "os/os_heap.h"
|
|
||||||
#include "ddsi/ddsi_tcp.h"
|
|
||||||
|
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <openssl/rand.h>
|
#include <openssl/rand.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/opensslconf.h>
|
||||||
|
|
||||||
static SSL_CTX * ddsi_ssl_ctx = NULL;
|
static SSL_CTX *ddsi_ssl_ctx = NULL;
|
||||||
|
|
||||||
static SSL * ddsi_ssl_new (void)
|
static SSL *ddsi_ssl_new (void)
|
||||||
{
|
{
|
||||||
return SSL_new (ddsi_ssl_ctx);
|
return SSL_new (ddsi_ssl_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ddsi_ssl_error (SSL * ssl, const char * str, int err)
|
static void ddsi_ssl_error (SSL *ssl, const char *str, int err)
|
||||||
{
|
{
|
||||||
char buff [128];
|
char buff[128];
|
||||||
ERR_error_string ((unsigned) SSL_get_error (ssl, err), buff);
|
ERR_error_string ((unsigned) SSL_get_error (ssl, err), buff);
|
||||||
DDS_ERROR("tcp/ssl %s %s %d\n", str, buff, err);
|
DDS_ERROR ("tcp/ssl %s %s %d\n", str, buff, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ddsi_ssl_verify (int ok, X509_STORE_CTX * store)
|
static int ddsi_ssl_verify (int ok, X509_STORE_CTX *store)
|
||||||
{
|
{
|
||||||
if (!ok)
|
if (!ok)
|
||||||
{
|
{
|
||||||
char issuer[256];
|
char issuer[256];
|
||||||
X509 * cert = X509_STORE_CTX_get_current_cert (store);
|
X509 *cert = X509_STORE_CTX_get_current_cert (store);
|
||||||
int err = X509_STORE_CTX_get_error (store);
|
int err = X509_STORE_CTX_get_error (store);
|
||||||
|
|
||||||
/* Check if allowing self-signed certificates */
|
/* Check if allowing self-signed certificates */
|
||||||
|
if (config.ssl_self_signed && ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)))
|
||||||
if
|
|
||||||
(
|
|
||||||
config.ssl_self_signed &&
|
|
||||||
((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
|
|
||||||
(err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN))
|
|
||||||
)
|
|
||||||
{
|
|
||||||
ok = 1;
|
ok = 1;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
X509_NAME_oneline (X509_get_issuer_name (cert), issuer, sizeof (issuer));
|
X509_NAME_oneline (X509_get_issuer_name (cert), issuer, sizeof (issuer));
|
||||||
DDS_ERROR
|
DDS_ERROR ("tcp/ssl failed to verify certificate from %s: %s\n", issuer, X509_verify_cert_error_string (err));
|
||||||
(
|
|
||||||
"tcp/ssl failed to verify certificate from %s : %s\n",
|
|
||||||
issuer,
|
|
||||||
X509_verify_cert_error_string (err)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static os_ssize_t ddsi_ssl_read (SSL * ssl, void * buf, os_size_t len, int * err)
|
static ssize_t ddsi_ssl_read (SSL *ssl, void *buf, size_t len, int *err)
|
||||||
{
|
{
|
||||||
int ret;
|
|
||||||
|
|
||||||
assert (len <= INT32_MAX);
|
assert (len <= INT32_MAX);
|
||||||
|
|
||||||
if (SSL_get_shutdown (ssl) != 0)
|
if (SSL_get_shutdown (ssl) != 0)
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns -1 on error or 0 on shutdown */
|
/* Returns -1 on error or 0 on shutdown */
|
||||||
|
const int ret = SSL_read (ssl, buf, (int) len);
|
||||||
ret = SSL_read (ssl, buf, (int) len);
|
|
||||||
switch (SSL_get_error (ssl, ret))
|
switch (SSL_get_error (ssl, ret))
|
||||||
{
|
{
|
||||||
case SSL_ERROR_NONE:
|
case SSL_ERROR_NONE:
|
||||||
{
|
return ret;
|
||||||
/* Success */
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SSL_ERROR_WANT_READ:
|
case SSL_ERROR_WANT_READ:
|
||||||
case SSL_ERROR_WANT_WRITE:
|
case SSL_ERROR_WANT_WRITE:
|
||||||
{
|
|
||||||
*err = os_sockEAGAIN;
|
*err = os_sockEAGAIN;
|
||||||
ret = -1;
|
return -1;
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SSL_ERROR_ZERO_RETURN:
|
case SSL_ERROR_ZERO_RETURN:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
/* Connection closed or error */
|
/* Connection closed or error */
|
||||||
|
|
||||||
*err = os_getErrno ();
|
*err = os_getErrno ();
|
||||||
ret = -1;
|
return -1;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static os_ssize_t ddsi_ssl_write (SSL * ssl, const void * buf, os_size_t len, int * err)
|
static ssize_t ddsi_ssl_write (SSL *ssl, const void *buf, size_t len, int *err)
|
||||||
{
|
{
|
||||||
int ret;
|
|
||||||
|
|
||||||
assert(len <= INT32_MAX);
|
assert(len <= INT32_MAX);
|
||||||
|
|
||||||
if (SSL_get_shutdown (ssl) != 0)
|
if (SSL_get_shutdown (ssl) != 0)
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns -1 on error or 0 on shutdown */
|
/* Returns -1 on error or 0 on shutdown */
|
||||||
|
const int ret = SSL_write (ssl, buf, (int) len);
|
||||||
ret = SSL_write (ssl, buf, (int) len);
|
|
||||||
switch (SSL_get_error (ssl, ret))
|
switch (SSL_get_error (ssl, ret))
|
||||||
{
|
{
|
||||||
case SSL_ERROR_NONE:
|
case SSL_ERROR_NONE:
|
||||||
{
|
return ret;
|
||||||
/* Success */
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SSL_ERROR_WANT_READ:
|
case SSL_ERROR_WANT_READ:
|
||||||
case SSL_ERROR_WANT_WRITE:
|
case SSL_ERROR_WANT_WRITE:
|
||||||
{
|
|
||||||
*err = os_sockEAGAIN;
|
*err = os_sockEAGAIN;
|
||||||
ret = -1;
|
return -1;
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SSL_ERROR_ZERO_RETURN:
|
case SSL_ERROR_ZERO_RETURN:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
/* Connection closed or error */
|
/* Connection closed or error */
|
||||||
|
|
||||||
*err = os_getErrno ();
|
*err = os_getErrno ();
|
||||||
ret = -1;
|
return -1;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Standard OpenSSL init and thread support routines. See O'Reilly. */
|
/* Standard OpenSSL init and thread support routines. See O'Reilly. */
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
static unsigned long ddsi_ssl_id (void)
|
static unsigned long ddsi_ssl_id (void)
|
||||||
{
|
{
|
||||||
return os_threadIdToInteger (os_threadIdSelf ());
|
return (unsigned long) os_threadIdToInteger (os_threadIdSelf ());
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct CRYPTO_dynlock_value
|
typedef struct CRYPTO_dynlock_value {
|
||||||
{
|
|
||||||
os_mutex m_mutex;
|
os_mutex m_mutex;
|
||||||
}
|
} CRYPTO_dynlock_value;
|
||||||
CRYPTO_dynlock_value;
|
|
||||||
|
|
||||||
static CRYPTO_dynlock_value * ddsi_ssl_locks = NULL;
|
static CRYPTO_dynlock_value *ddsi_ssl_locks = NULL;
|
||||||
|
|
||||||
static void ddsi_ssl_dynlock_lock (int mode, CRYPTO_dynlock_value * lock, const char * file, int line)
|
static void ddsi_ssl_dynlock_lock (int mode, CRYPTO_dynlock_value *lock, const char *file, int line)
|
||||||
{
|
{
|
||||||
(void) file;
|
(void) file;
|
||||||
(void) line;
|
(void) line;
|
||||||
if (mode & CRYPTO_LOCK)
|
if (mode & CRYPTO_LOCK)
|
||||||
{
|
|
||||||
os_mutexLock (&lock->m_mutex);
|
os_mutexLock (&lock->m_mutex);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
os_mutexUnlock (&lock->m_mutex);
|
os_mutexUnlock (&lock->m_mutex);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ddsi_ssl_lock (int mode, int n, const char * file, int line)
|
static void ddsi_ssl_lock (int mode, int n, const char *file, int line)
|
||||||
{
|
{
|
||||||
ddsi_ssl_dynlock_lock (mode, &ddsi_ssl_locks[n], file, line);
|
ddsi_ssl_dynlock_lock (mode, &ddsi_ssl_locks[n], file, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
static CRYPTO_dynlock_value * ddsi_ssl_dynlock_create (const char * file, int line)
|
static CRYPTO_dynlock_value *ddsi_ssl_dynlock_create (const char *file, int line)
|
||||||
{
|
{
|
||||||
CRYPTO_dynlock_value * val = os_malloc (sizeof (*val));
|
|
||||||
|
|
||||||
(void) file;
|
(void) file;
|
||||||
(void) line;
|
(void) line;
|
||||||
|
CRYPTO_dynlock_value *val = os_malloc (sizeof (*val));
|
||||||
os_mutexInit (&val->m_mutex);
|
os_mutexInit (&val->m_mutex);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ddsi_ssl_dynlock_destroy (CRYPTO_dynlock_value * lock, const char * file, int line)
|
static void ddsi_ssl_dynlock_destroy (CRYPTO_dynlock_value *lock, const char *file, int line)
|
||||||
{
|
{
|
||||||
(void) file;
|
(void) file;
|
||||||
(void) line;
|
(void) line;
|
||||||
os_mutexDestroy (&lock->m_mutex);
|
os_mutexDestroy (&lock->m_mutex);
|
||||||
os_free (lock);
|
os_free (lock);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int ddsi_ssl_password (char * buf, int num, int rwflag, void * udata)
|
static int ddsi_ssl_password (char *buf, int num, int rwflag, void *udata)
|
||||||
{
|
{
|
||||||
(void) rwflag;
|
(void) rwflag;
|
||||||
(void) udata;
|
(void) udata;
|
||||||
if ((unsigned int) num < strlen (config.ssl_key_pass) + 1)
|
if (num < 0 || (size_t) num < strlen (config.ssl_key_pass) + 1)
|
||||||
{
|
return 0;
|
||||||
return (0);
|
OS_WARNING_MSVC_OFF(4996);
|
||||||
}
|
|
||||||
strcpy (buf, config.ssl_key_pass);
|
strcpy (buf, config.ssl_key_pass);
|
||||||
|
OS_WARNING_MSVC_ON(4996);
|
||||||
return (int) strlen (config.ssl_key_pass);
|
return (int) strlen (config.ssl_key_pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SSL_CTX * ddsi_ssl_ctx_init (void)
|
static SSL_CTX *ddsi_ssl_ctx_init (void)
|
||||||
{
|
{
|
||||||
int i;
|
SSL_CTX *ctx = SSL_CTX_new (SSLv23_method ());
|
||||||
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
|
|
||||||
|
|
||||||
/* Load certificates */
|
/* Load certificates */
|
||||||
|
|
||||||
if (! SSL_CTX_use_certificate_file (ctx, config.ssl_keystore, SSL_FILETYPE_PEM))
|
if (! SSL_CTX_use_certificate_file (ctx, config.ssl_keystore, SSL_FILETYPE_PEM))
|
||||||
{
|
{
|
||||||
DDS_LOG
|
DDS_LOG (DDS_LC_ERROR | DDS_LC_CONFIG, "tcp/ssl failed to load certificate from file: %s\n", config.ssl_keystore);
|
||||||
(
|
|
||||||
DDS_LC_ERROR | DDS_LC_CONFIG,
|
|
||||||
"tcp/ssl failed to load certificate from file: %s\n",
|
|
||||||
config.ssl_keystore
|
|
||||||
);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set password and callback */
|
/* Set password and callback */
|
||||||
|
|
||||||
SSL_CTX_set_default_passwd_cb (ctx, ddsi_ssl_password);
|
SSL_CTX_set_default_passwd_cb (ctx, ddsi_ssl_password);
|
||||||
|
|
||||||
/* Get private key */
|
/* Get private key */
|
||||||
|
|
||||||
if (! SSL_CTX_use_PrivateKey_file (ctx, config.ssl_keystore, SSL_FILETYPE_PEM))
|
if (! SSL_CTX_use_PrivateKey_file (ctx, config.ssl_keystore, SSL_FILETYPE_PEM))
|
||||||
{
|
{
|
||||||
DDS_LOG
|
DDS_LOG (DDS_LC_ERROR | DDS_LC_CONFIG, "tcp/ssl failed to load private key from file: %s\n", config.ssl_keystore);
|
||||||
(
|
|
||||||
DDS_LC_ERROR | DDS_LC_CONFIG,
|
|
||||||
"tcp/ssl failed to load private key from file: %s\n",
|
|
||||||
config.ssl_keystore
|
|
||||||
);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load CAs */
|
/* Load CAs */
|
||||||
|
|
||||||
if (! SSL_CTX_load_verify_locations (ctx, config.ssl_keystore, 0))
|
if (! SSL_CTX_load_verify_locations (ctx, config.ssl_keystore, 0))
|
||||||
{
|
{
|
||||||
DDS_LOG
|
DDS_LOG (DDS_LC_ERROR | DDS_LC_CONFIG, "tcp/ssl failed to load CA from file: %s\n", config.ssl_keystore);
|
||||||
(
|
|
||||||
DDS_LC_ERROR | DDS_LC_CONFIG,
|
|
||||||
"tcp/ssl failed to load CA from file: %s\n",
|
|
||||||
config.ssl_keystore
|
|
||||||
);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set ciphers */
|
/* Set ciphers */
|
||||||
|
|
||||||
if (! SSL_CTX_set_cipher_list (ctx, config.ssl_ciphers))
|
if (! SSL_CTX_set_cipher_list (ctx, config.ssl_ciphers))
|
||||||
{
|
{
|
||||||
DDS_LOG
|
DDS_LOG (DDS_LC_ERROR | DDS_LC_CONFIG, "tcp/ssl failed to set ciphers: %s\n", config.ssl_ciphers);
|
||||||
(
|
|
||||||
DDS_LC_ERROR | DDS_LC_CONFIG,
|
|
||||||
"tcp/ssl failed to set ciphers: %s\n",
|
|
||||||
config.ssl_ciphers
|
|
||||||
);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load randomness from file (optional) */
|
/* Load randomness from file (optional) */
|
||||||
|
|
||||||
if (config.ssl_rand_file[0] != '\0')
|
if (config.ssl_rand_file[0] != '\0')
|
||||||
{
|
{
|
||||||
if (! RAND_load_file (config.ssl_rand_file, 4096))
|
if (! RAND_load_file (config.ssl_rand_file, 4096))
|
||||||
{
|
{
|
||||||
DDS_LOG
|
DDS_LOG (DDS_LC_ERROR | DDS_LC_CONFIG, "tcp/ssl failed to load random seed from file: %s\n", config.ssl_rand_file);
|
||||||
(
|
|
||||||
DDS_LC_ERROR | DDS_LC_CONFIG,
|
|
||||||
"tcp/ssl failed to load random seed from file: %s\n",
|
|
||||||
config.ssl_rand_file
|
|
||||||
);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set certificate verification policy from configuration */
|
/* Set certificate verification policy from configuration */
|
||||||
|
if (!config.ssl_verify)
|
||||||
if (config.ssl_verify)
|
SSL_CTX_set_verify (ctx, SSL_VERIFY_NONE, NULL);
|
||||||
{
|
|
||||||
i = SSL_VERIFY_PEER;
|
|
||||||
if (config.ssl_verify_client)
|
|
||||||
{
|
|
||||||
i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
||||||
}
|
|
||||||
SSL_CTX_set_verify (ctx, i, ddsi_ssl_verify);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SSL_CTX_set_verify (ctx, SSL_VERIFY_NONE, NULL);
|
int i = SSL_VERIFY_PEER;
|
||||||
|
if (config.ssl_verify_client)
|
||||||
|
i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
||||||
|
SSL_CTX_set_verify (ctx, i, ddsi_ssl_verify);
|
||||||
}
|
}
|
||||||
SSL_CTX_set_options (ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
|
SSL_CTX_set_options (ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
|
||||||
SSL_CTX_free (ctx);
|
SSL_CTX_free (ctx);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SSL * ddsi_ssl_connect (os_socket sock)
|
static void dds_report_tls_version (const SSL *ssl, const char *oper)
|
||||||
{
|
{
|
||||||
SSL * ssl;
|
if (ssl)
|
||||||
|
{
|
||||||
|
char issuer[256], subject[256];
|
||||||
|
X509_NAME_oneline (X509_get_issuer_name (SSL_get_peer_certificate (ssl)), issuer, sizeof (issuer));
|
||||||
|
X509_NAME_oneline (X509_get_subject_name (SSL_get_peer_certificate (ssl)), subject, sizeof (subject));
|
||||||
|
DDS_TRACE("tcp/ssl %s %s issued by %s [%s]\n", oper, subject, issuer, SSL_get_version (ssl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SSL *ddsi_ssl_connect (os_socket sock)
|
||||||
|
{
|
||||||
|
SSL *ssl;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/* Connect SSL over connected socket */
|
/* Connect SSL over connected socket */
|
||||||
|
|
||||||
ssl = ddsi_ssl_new ();
|
ssl = ddsi_ssl_new ();
|
||||||
SSL_set_fd (ssl, sock);
|
SSL_set_fd (ssl, sock);
|
||||||
err = SSL_connect (ssl);
|
err = SSL_connect (ssl);
|
||||||
|
@ -335,20 +254,21 @@ static SSL * ddsi_ssl_connect (os_socket sock)
|
||||||
SSL_free (ssl);
|
SSL_free (ssl);
|
||||||
ssl = NULL;
|
ssl = NULL;
|
||||||
}
|
}
|
||||||
|
dds_report_tls_version (ssl, "connected to");
|
||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BIO * ddsi_ssl_listen (os_socket sock)
|
static BIO *ddsi_ssl_listen (os_socket sock)
|
||||||
{
|
{
|
||||||
BIO * bio = BIO_new (BIO_s_accept ());
|
BIO * bio = BIO_new (BIO_s_accept ());
|
||||||
BIO_set_fd (bio, sock, BIO_NOCLOSE);
|
BIO_set_fd (bio, sock, BIO_NOCLOSE);
|
||||||
return bio;
|
return bio;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SSL * ddsi_ssl_accept (BIO * bio, os_socket * sock)
|
static SSL *ddsi_ssl_accept (BIO *bio, os_socket *sock)
|
||||||
{
|
{
|
||||||
SSL * ssl = NULL;
|
SSL *ssl = NULL;
|
||||||
BIO * nbio;
|
BIO *nbio;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (BIO_do_accept (bio) > 0)
|
if (BIO_do_accept (bio) > 0)
|
||||||
|
@ -365,23 +285,29 @@ static SSL * ddsi_ssl_accept (BIO * bio, os_socket * sock)
|
||||||
ssl = NULL;
|
ssl = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dds_report_tls_version (ssl, "accepted from");
|
||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
static c_bool ddsi_ssl_init (void)
|
static bool ddsi_ssl_init (void)
|
||||||
{
|
{
|
||||||
unsigned locks = (unsigned) CRYPTO_num_locks ();
|
|
||||||
unsigned i;
|
|
||||||
|
|
||||||
ddsi_ssl_locks = os_malloc (sizeof (CRYPTO_dynlock_value) * locks);
|
|
||||||
for (i = 0; i < locks; i++)
|
|
||||||
{
|
|
||||||
os_mutexInit (&ddsi_ssl_locks[i].m_mutex);
|
|
||||||
}
|
|
||||||
ERR_load_BIO_strings ();
|
ERR_load_BIO_strings ();
|
||||||
SSL_load_error_strings ();
|
SSL_load_error_strings ();
|
||||||
SSL_library_init ();
|
SSL_library_init ();
|
||||||
OpenSSL_add_all_algorithms ();
|
OpenSSL_add_all_algorithms ();
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
|
{
|
||||||
|
const int locks = CRYPTO_num_locks ();
|
||||||
|
assert (locks >= 0);
|
||||||
|
ddsi_ssl_locks = os_malloc (sizeof (CRYPTO_dynlock_value) * (size_t) locks);
|
||||||
|
for (int i = 0; i < locks; i++)
|
||||||
|
os_mutexInit (&ddsi_ssl_locks[i].m_mutex);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/* Leave these in place: OpenSSL 1.1 defines them as no-op macros that not even reference the symbol,
|
||||||
|
therefore leaving them in means we get compile time errors if we the library expects the callbacks
|
||||||
|
to be defined and we somehow failed to detect that previously */
|
||||||
CRYPTO_set_id_callback (ddsi_ssl_id);
|
CRYPTO_set_id_callback (ddsi_ssl_id);
|
||||||
CRYPTO_set_locking_callback (ddsi_ssl_lock);
|
CRYPTO_set_locking_callback (ddsi_ssl_lock);
|
||||||
CRYPTO_set_dynlock_create_callback (ddsi_ssl_dynlock_create);
|
CRYPTO_set_dynlock_create_callback (ddsi_ssl_dynlock_create);
|
||||||
|
@ -394,43 +320,36 @@ static c_bool ddsi_ssl_init (void)
|
||||||
|
|
||||||
static void ddsi_ssl_fini (void)
|
static void ddsi_ssl_fini (void)
|
||||||
{
|
{
|
||||||
unsigned locks = (unsigned) CRYPTO_num_locks ();
|
|
||||||
unsigned i;
|
|
||||||
|
|
||||||
SSL_CTX_free (ddsi_ssl_ctx);
|
SSL_CTX_free (ddsi_ssl_ctx);
|
||||||
CRYPTO_set_id_callback (NULL);
|
CRYPTO_set_id_callback (0);
|
||||||
CRYPTO_set_locking_callback (NULL);
|
CRYPTO_set_locking_callback (0);
|
||||||
CRYPTO_set_dynlock_create_callback (NULL);
|
CRYPTO_set_dynlock_create_callback (0);
|
||||||
CRYPTO_set_dynlock_lock_callback (NULL);
|
CRYPTO_set_dynlock_lock_callback (0);
|
||||||
CRYPTO_set_dynlock_destroy_callback (NULL);
|
CRYPTO_set_dynlock_destroy_callback (0);
|
||||||
ERR_free_strings ();
|
ERR_free_strings ();
|
||||||
EVP_cleanup ();
|
EVP_cleanup ();
|
||||||
for (i = 0; i < locks; i++)
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
{
|
{
|
||||||
os_mutexDestroy (&ddsi_ssl_locks[i].m_mutex);
|
const int locks = CRYPTO_num_locks ();
|
||||||
|
for (int i = 0; i < locks; i++)
|
||||||
|
os_mutexDestroy (&ddsi_ssl_locks[i].m_mutex);
|
||||||
|
os_free (ddsi_ssl_locks);
|
||||||
}
|
}
|
||||||
os_free (ddsi_ssl_locks);
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ddsi_ssl_config (void)
|
void ddsi_ssl_config_plugin (struct ddsi_ssl_plugins *plugin)
|
||||||
{
|
{
|
||||||
if (config.ssl_enable)
|
plugin->init = ddsi_ssl_init;
|
||||||
{
|
plugin->fini = ddsi_ssl_fini;
|
||||||
ddsi_tcp_ssl_plugin.init = ddsi_ssl_init;
|
plugin->ssl_free = SSL_free;
|
||||||
ddsi_tcp_ssl_plugin.fini = ddsi_ssl_fini;
|
plugin->bio_vfree = BIO_vfree;
|
||||||
ddsi_tcp_ssl_plugin.ssl_free = SSL_free;
|
plugin->read = ddsi_ssl_read;
|
||||||
ddsi_tcp_ssl_plugin.bio_vfree = BIO_vfree;
|
plugin->write = ddsi_ssl_write;
|
||||||
ddsi_tcp_ssl_plugin.read = ddsi_ssl_read;
|
plugin->connect = ddsi_ssl_connect;
|
||||||
ddsi_tcp_ssl_plugin.write = ddsi_ssl_write;
|
plugin->listen = ddsi_ssl_listen;
|
||||||
ddsi_tcp_ssl_plugin.connect = ddsi_ssl_connect;
|
plugin->accept = ddsi_ssl_accept;
|
||||||
ddsi_tcp_ssl_plugin.listen = ddsi_ssl_listen;
|
|
||||||
ddsi_tcp_ssl_plugin.accept = ddsi_ssl_accept;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ddsi_ssl_plugin (void)
|
|
||||||
{
|
|
||||||
ddsi_tcp_ssl_plugin.config = ddsi_ssl_config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* DDSI_INCLUDE_SSL */
|
#endif /* DDSI_INCLUDE_SSL */
|
||||||
|
|
|
@ -30,16 +30,11 @@ typedef struct ddsi_tran_factory * ddsi_tcp_factory_g_t;
|
||||||
static os_atomic_uint32_t ddsi_tcp_init_g = OS_ATOMIC_UINT32_INIT(0);
|
static os_atomic_uint32_t ddsi_tcp_init_g = OS_ATOMIC_UINT32_INIT(0);
|
||||||
|
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
struct ddsi_ssl_plugins ddsi_tcp_ssl_plugin =
|
static struct ddsi_ssl_plugins ddsi_tcp_ssl_plugin;
|
||||||
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const char * ddsi_name = "tcp";
|
static const char * ddsi_name = "tcp";
|
||||||
|
|
||||||
/* Stateless singleton instance handed out as client connection */
|
|
||||||
|
|
||||||
static struct ddsi_tran_conn ddsi_tcp_conn_client;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ddsi_tcp_conn: TCP connection for reading and writing. Mutex prevents concurrent
|
ddsi_tcp_conn: TCP connection for reading and writing. Mutex prevents concurrent
|
||||||
writes to socket. Is reference counted. Peer port is actually contained in peer
|
writes to socket. Is reference counted. Peer port is actually contained in peer
|
||||||
|
@ -74,6 +69,10 @@ typedef struct ddsi_tcp_listener
|
||||||
}
|
}
|
||||||
* ddsi_tcp_listener_t;
|
* ddsi_tcp_listener_t;
|
||||||
|
|
||||||
|
/* Stateless singleton instance handed out as client connection */
|
||||||
|
|
||||||
|
static struct ddsi_tcp_conn ddsi_tcp_conn_client;
|
||||||
|
|
||||||
static int ddsi_tcp_cmp_conn (const struct ddsi_tcp_conn *c1, const struct ddsi_tcp_conn *c2)
|
static int ddsi_tcp_cmp_conn (const struct ddsi_tcp_conn *c1, const struct ddsi_tcp_conn *c2)
|
||||||
{
|
{
|
||||||
const os_sockaddr *a1s = (os_sockaddr *)&c1->m_peer_addr;
|
const os_sockaddr *a1s = (os_sockaddr *)&c1->m_peer_addr;
|
||||||
|
@ -348,7 +347,7 @@ OS_WARNING_MSVC_ON(4267);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
static os_ssize_t ddsi_tcp_conn_read_ssl (ddsi_tcp_conn_t tcp, void * buf, os_size_t len, int * err)
|
static ssize_t ddsi_tcp_conn_read_ssl (ddsi_tcp_conn_t tcp, void * buf, size_t len, int * err)
|
||||||
{
|
{
|
||||||
return (ddsi_tcp_ssl_plugin.read) (tcp->m_ssl, buf, len, err);
|
return (ddsi_tcp_ssl_plugin.read) (tcp->m_ssl, buf, len, err);
|
||||||
}
|
}
|
||||||
|
@ -471,7 +470,7 @@ OS_WARNING_MSVC_OFF(4267);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
static os_ssize_t ddsi_tcp_conn_write_ssl (ddsi_tcp_conn_t conn, const void * buf, os_size_t len, int * err)
|
static ssize_t ddsi_tcp_conn_write_ssl (ddsi_tcp_conn_t conn, const void * buf, size_t len, int * err)
|
||||||
{
|
{
|
||||||
return (ddsi_tcp_ssl_plugin.write) (conn->m_ssl, buf, len, err);
|
return (ddsi_tcp_ssl_plugin.write) (conn->m_ssl, buf, len, err);
|
||||||
}
|
}
|
||||||
|
@ -541,7 +540,7 @@ static ssize_t ddsi_tcp_conn_write (ddsi_tran_conn_t base, const nn_locator_t *d
|
||||||
{
|
{
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
char msgbuf[4096]; /* stack buffer for merging smallish writes without requiring allocations */
|
char msgbuf[4096]; /* stack buffer for merging smallish writes without requiring allocations */
|
||||||
struct iovec iovec; /* iovec used for msgbuf */
|
os_iovec_t iovec; /* iovec used for msgbuf */
|
||||||
#endif
|
#endif
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -732,8 +731,7 @@ static ddsi_tran_conn_t ddsi_tcp_create_conn (uint32_t port, ddsi_tran_qos_t qos
|
||||||
{
|
{
|
||||||
(void) qos;
|
(void) qos;
|
||||||
(void) port;
|
(void) port;
|
||||||
|
return &ddsi_tcp_conn_client.m_base;
|
||||||
return (ddsi_tran_conn_t) &ddsi_tcp_conn_client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ddsi_tcp_listen (ddsi_tran_listener_t listener)
|
static int ddsi_tcp_listen (ddsi_tran_listener_t listener)
|
||||||
|
@ -936,7 +934,7 @@ static void ddsi_tcp_conn_delete (ddsi_tcp_conn_t conn)
|
||||||
|
|
||||||
static void ddsi_tcp_close_conn (ddsi_tran_conn_t tc)
|
static void ddsi_tcp_close_conn (ddsi_tran_conn_t tc)
|
||||||
{
|
{
|
||||||
if (tc != (ddsi_tran_conn_t) &ddsi_tcp_conn_client)
|
if (tc != &ddsi_tcp_conn_client.m_base)
|
||||||
{
|
{
|
||||||
char buff[DDSI_LOCSTRLEN];
|
char buff[DDSI_LOCSTRLEN];
|
||||||
nn_locator_t loc;
|
nn_locator_t loc;
|
||||||
|
@ -952,7 +950,7 @@ static void ddsi_tcp_close_conn (ddsi_tran_conn_t tc)
|
||||||
|
|
||||||
static void ddsi_tcp_release_conn (ddsi_tran_conn_t conn)
|
static void ddsi_tcp_release_conn (ddsi_tran_conn_t conn)
|
||||||
{
|
{
|
||||||
if (conn != (ddsi_tran_conn_t) &ddsi_tcp_conn_client)
|
if (conn != &ddsi_tcp_conn_client.m_base)
|
||||||
{
|
{
|
||||||
ddsi_tcp_conn_delete ((ddsi_tcp_conn_t) conn);
|
ddsi_tcp_conn_delete ((ddsi_tcp_conn_t) conn);
|
||||||
}
|
}
|
||||||
|
@ -964,13 +962,6 @@ static void ddsi_tcp_unblock_listener (ddsi_tran_listener_t listener)
|
||||||
os_socket sock;
|
os_socket sock;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
|
||||||
if (ddsi_tcp_ssl_plugin.bio_vfree)
|
|
||||||
{
|
|
||||||
(ddsi_tcp_ssl_plugin.bio_vfree) (tl->m_bio);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Connect to own listener socket to wake listener from blocking 'accept()' */
|
/* Connect to own listener socket to wake listener from blocking 'accept()' */
|
||||||
ddsi_tcp_sock_new (&sock, 0);
|
ddsi_tcp_sock_new (&sock, 0);
|
||||||
if (sock != OS_INVALID_SOCKET)
|
if (sock != OS_INVALID_SOCKET)
|
||||||
|
@ -1020,6 +1011,12 @@ static void ddsi_tcp_unblock_listener (ddsi_tran_listener_t listener)
|
||||||
static void ddsi_tcp_release_listener (ddsi_tran_listener_t listener)
|
static void ddsi_tcp_release_listener (ddsi_tran_listener_t listener)
|
||||||
{
|
{
|
||||||
ddsi_tcp_listener_t tl = (ddsi_tcp_listener_t) listener;
|
ddsi_tcp_listener_t tl = (ddsi_tcp_listener_t) listener;
|
||||||
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
|
if (ddsi_tcp_ssl_plugin.bio_vfree)
|
||||||
|
{
|
||||||
|
(ddsi_tcp_ssl_plugin.bio_vfree) (tl->m_bio);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
ddsi_tcp_sock_free (tl->m_sock, "listener");
|
ddsi_tcp_sock_free (tl->m_sock, "listener");
|
||||||
os_free (tl);
|
os_free (tl);
|
||||||
}
|
}
|
||||||
|
@ -1097,17 +1094,14 @@ int ddsi_tcp_init (void)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
memset (&ddsi_tcp_conn_client, 0, sizeof (ddsi_tcp_conn_client));
|
memset (&ddsi_tcp_conn_client, 0, sizeof (ddsi_tcp_conn_client));
|
||||||
ddsi_tcp_base_init (&ddsi_tcp_conn_client);
|
ddsi_tcp_base_init (&ddsi_tcp_conn_client.m_base);
|
||||||
|
|
||||||
#ifdef DDSI_INCLUDE_SSL
|
#ifdef DDSI_INCLUDE_SSL
|
||||||
if (ddsi_tcp_ssl_plugin.config)
|
if (config.ssl_enable)
|
||||||
{
|
|
||||||
(ddsi_tcp_ssl_plugin.config) ();
|
|
||||||
}
|
|
||||||
if (ddsi_tcp_ssl_plugin.init)
|
|
||||||
{
|
{
|
||||||
ddsi_name = "tcp/ssl";
|
ddsi_name = "tcp/ssl";
|
||||||
if (! (ddsi_tcp_ssl_plugin.init) ())
|
ddsi_ssl_config_plugin (&ddsi_tcp_ssl_plugin);
|
||||||
|
if (! ddsi_tcp_ssl_plugin.init ())
|
||||||
{
|
{
|
||||||
DDS_ERROR("Failed to initialize OpenSSL\n");
|
DDS_ERROR("Failed to initialize OpenSSL\n");
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -59,12 +59,15 @@ ddsi_tran_factory_t ddsi_factory_find (const char * type)
|
||||||
|
|
||||||
void ddsi_tran_factories_fini (void)
|
void ddsi_tran_factories_fini (void)
|
||||||
{
|
{
|
||||||
ddsi_tran_factory_t factory;
|
ddsi_tran_factory_t factory;
|
||||||
|
while ((factory = ddsi_tran_factories) != NULL)
|
||||||
while ((factory = ddsi_tran_factories) != NULL) {
|
{
|
||||||
ddsi_tran_factories = factory->m_factory;
|
/* Keep the factory in the list for the duration of "factory_free" so that
|
||||||
ddsi_factory_free(factory);
|
conversion of locator kind to factory remains possible. */
|
||||||
}
|
ddsi_tran_factory_t next = factory->m_factory;
|
||||||
|
ddsi_factory_free (factory);
|
||||||
|
ddsi_tran_factories = next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ddsi_tran_factory_t ddsi_factory_find_with_len (const char * type, size_t len)
|
static ddsi_tran_factory_t ddsi_factory_find_with_len (const char * type, size_t len)
|
||||||
|
|
|
@ -679,7 +679,7 @@ static const struct cfgelem ssl_cfgelems[] = {
|
||||||
"<p>This enables SSL/TLS for TCP.</p>" },
|
"<p>This enables SSL/TLS for TCP.</p>" },
|
||||||
{ LEAF("CertificateVerification"), 1, "true", ABSOFF(ssl_verify), 0, uf_boolean, 0, pf_boolean,
|
{ LEAF("CertificateVerification"), 1, "true", ABSOFF(ssl_verify), 0, uf_boolean, 0, pf_boolean,
|
||||||
"<p>If disabled this allows SSL connections to occur even if an X509 certificate fails verification.</p>" },
|
"<p>If disabled this allows SSL connections to occur even if an X509 certificate fails verification.</p>" },
|
||||||
{ LEAF("VerifyClient"), 1, "false", ABSOFF(ssl_verify_client), 0, uf_boolean, 0, pf_boolean,
|
{ LEAF("VerifyClient"), 1, "true", ABSOFF(ssl_verify_client), 0, uf_boolean, 0, pf_boolean,
|
||||||
"<p>This enables an SSL server checking the X509 certificate of a connecting client.</p>" },
|
"<p>This enables an SSL server checking the X509 certificate of a connecting client.</p>" },
|
||||||
{ LEAF("SelfSignedCertificates"), 1, "false", ABSOFF(ssl_self_signed), 0, uf_boolean, 0, pf_boolean,
|
{ LEAF("SelfSignedCertificates"), 1, "false", ABSOFF(ssl_self_signed), 0, uf_boolean, 0, pf_boolean,
|
||||||
"<p>This enables the use of self signed X509 certificates.</p>" },
|
"<p>This enables the use of self signed X509 certificates.</p>" },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue