Browse Source

Support for TLS between Hush Nodes

pull/68/head
Kent Sommer 7 years ago
parent
commit
ff769cfbd8
  1. 34
      depends/packages/openssl.mk
  2. 17
      qa/rpc-tests/nodehandling.py
  3. 2
      src/Makefile.am
  4. 23
      src/init.cpp
  5. 4
      src/metrics.cpp
  6. 794
      src/net.cpp
  7. 29
      src/net.h
  8. 7
      src/rpcnet.cpp
  9. 491
      src/utiltls.cpp
  10. 53
      src/utiltls.h

34
depends/packages/openssl.mk

@ -13,40 +13,40 @@ $(package)_config_opts+=no-async
$(package)_config_opts+=no-bf
$(package)_config_opts+=no-blake2
$(package)_config_opts+=no-camellia
$(package)_config_opts+=no-capieng
# $(package)_config_opts+=no-capieng
$(package)_config_opts+=no-cast
$(package)_config_opts+=no-chacha
$(package)_config_opts+=no-cmac
$(package)_config_opts+=no-cms
$(package)_config_opts+=no-comp
# $(package)_config_opts+=no-comp
$(package)_config_opts+=no-crypto-mdebug
$(package)_config_opts+=no-crypto-mdebug-backtrace
$(package)_config_opts+=no-ct
# $(package)_config_opts+=no-ct
$(package)_config_opts+=no-des
$(package)_config_opts+=no-dgram
$(package)_config_opts+=no-dsa
# $(package)_config_opts+=no-dsa
$(package)_config_opts+=no-dso
$(package)_config_opts+=no-dtls
$(package)_config_opts+=no-dtls1
$(package)_config_opts+=no-dtls1-method
$(package)_config_opts+=no-dynamic-engine
$(package)_config_opts+=no-ec2m
$(package)_config_opts+=no-ec_nistp_64_gcc_128
# $(package)_config_opts+=no-ec2m
# $(package)_config_opts+=no-ec_nistp_64_gcc_128
$(package)_config_opts+=no-egd
$(package)_config_opts+=no-engine
$(package)_config_opts+=no-err
# $(package)_config_opts+=no-err
$(package)_config_opts+=no-gost
$(package)_config_opts+=no-heartbeats
$(package)_config_opts+=no-idea
# $(package)_config_opts+=no-idea
$(package)_config_opts+=no-md2
$(package)_config_opts+=no-md4
$(package)_config_opts+=no-mdc2
$(package)_config_opts+=no-multiblock
$(package)_config_opts+=no-nextprotoneg
$(package)_config_opts+=no-ocb
$(package)_config_opts+=no-ocsp
# $(package)_config_opts+=no-ocsp
$(package)_config_opts+=no-poly1305
$(package)_config_opts+=no-posix-io
# $(package)_config_opts+=no-posix-io
$(package)_config_opts+=no-psk
$(package)_config_opts+=no-rc2
$(package)_config_opts+=no-rc4
@ -58,24 +58,24 @@ $(package)_config_opts+=no-scrypt
$(package)_config_opts+=no-sctp
$(package)_config_opts+=no-seed
$(package)_config_opts+=no-shared
$(package)_config_opts+=no-sock
# $(package)_config_opts+=no-sock
$(package)_config_opts+=no-srp
$(package)_config_opts+=no-srtp
$(package)_config_opts+=no-ssl
$(package)_config_opts+=no-ssl3
$(package)_config_opts+=no-ssl3-method
$(package)_config_opts+=no-ssl-trace
$(package)_config_opts+=no-stdio
$(package)_config_opts+=no-tls
$(package)_config_opts+=no-tls1
$(package)_config_opts+=no-tls1-method
# $(package)_config_opts+=no-stdio
# $(package)_config_opts+=no-tls
# $(package)_config_opts+=no-tls1
# $(package)_config_opts+=no-tls1-method
$(package)_config_opts+=no-ts
$(package)_config_opts+=no-ui
$(package)_config_opts+=no-unit-test
$(package)_config_opts+=no-weak-ssl-ciphers
$(package)_config_opts+=no-whirlpool
$(package)_config_opts+=no-zlib
$(package)_config_opts+=no-zlib-dynamic
# $(package)_config_opts+=no-zlib
# $(package)_config_opts+=no-zlib-dynamic
$(package)_config_opts+=$($(package)_cflags) $($(package)_cppflags)
$(package)_config_opts+=-DPURIFY
$(package)_config_opts_linux=-fPIC -Wa,--noexecstack

17
qa/rpc-tests/nodehandling.py

@ -62,5 +62,22 @@ class NodeHandlingTest (BitcoinTestFramework):
found = True
assert(found)
###########################
# Connection to self (takes approx 5 min)
# Stress test for network layer. Trying to connect to self every 0.5 sec.
# Helps to discover different multi-threading problems.
###########################
url = urlparse.urlparse(self.nodes[0].url)
print "Connection to self stress test. " \
"Constantly trying to connect to self every 0.5 sec. " \
"The whole test takes approx 5 mins"
for x in xrange(600):
connect_nodes(self.nodes[0], 0)
time.sleep(0.5)
# self-connection should be disconnected during the version checking
for node in self.nodes[0].getpeerinfo():
assert(node['addr'] != url.hostname+":"+str(p2p_port(0)))
if __name__ == '__main__':
NodeHandlingTest ().main ()

2
src/Makefile.am

@ -182,6 +182,7 @@ BITCOIN_CORE_H = \
utilmoneystr.h \
utilstrencodings.h \
utiltime.h \
utiltls.h \
validationinterface.h \
version.h \
wallet/asyncrpcoperation_sendmany.h \
@ -240,6 +241,7 @@ libbitcoin_server_a_SOURCES = \
torcontrol.cpp \
txdb.cpp \
txmempool.cpp \
utiltls.cpp \
validationinterface.cpp \
$(BITCOIN_CORE_H) \
$(LIBZCASH_H)

23
src/init.cpp

@ -394,6 +394,11 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-timeout=<n>", strprintf(_("Specify connection timeout in milliseconds (minimum: 1, default: %d)"), DEFAULT_CONNECT_TIMEOUT));
strUsage += HelpMessageOpt("-torcontrol=<ip>:<port>", strprintf(_("Tor control port to use if onion listening enabled (default: %s)"), DEFAULT_TOR_CONTROL));
strUsage += HelpMessageOpt("-torpassword=<pass>", _("Tor control port password (default: empty)"));
strUsage += HelpMessageOpt("-tlsvalidate=<0 or 1>", _("Connect to peers only with valid certificates (default: 0)"));
strUsage += HelpMessageOpt("-tlskeypath=<path>", _("Full path to a private key"));
strUsage += HelpMessageOpt("-tlskeypwd=<password>", _("Password for a private key encryption (default: not set, i.e. private key will be stored unencrypted)"));
strUsage += HelpMessageOpt("-tlscertpath=<path>", _("Full path to a certificate"));
strUsage += HelpMessageOpt("-tlstrustdir=<path>", _("Full path to a trusted certificates directory"));
strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6"));
strUsage += HelpMessageOpt("-whitelist=<netmask>", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") +
" " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway"));
@ -1263,6 +1268,24 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
BOOST_FOREACH(const std::string& strDest, mapMultiArgs["-seednode"])
AddOneShot(strDest);
if (mapArgs.count("-tlskeypath")) {
boost::filesystem::path pathTLSKey(GetArg("-tlskeypath", ""));
if (!boost::filesystem::exists(pathTLSKey))
return InitError(strprintf(_("Cannot find TLS key file: '%s'"), pathTLSKey.string()));
}
if (mapArgs.count("-tlscertpath")) {
boost::filesystem::path pathTLSCert(GetArg("-tlscertpath", ""));
if (!boost::filesystem::exists(pathTLSCert))
return InitError(strprintf(_("Cannot find TLS cert file: '%s'"), pathTLSCert.string()));
}
if (mapArgs.count("-tlstrustdir")) {
boost::filesystem::path pathTLSTrustredDir(GetArg("-tlstrustdir", ""));
if (!boost::filesystem::exists(pathTLSTrustredDir))
return InitError(strprintf(_("Cannot find trusted certificates directory: '%s'"), pathTLSTrustredDir.string()));
}
#if ENABLE_ZMQ
pzmqNotificationInterface = CZMQNotificationInterface::CreateWithArguments(mapArgs);

4
src/metrics.cpp

@ -202,12 +202,14 @@ int printStats(bool mining)
int height;
int64_t tipmediantime;
size_t connections;
size_t tlsConnections;
int64_t netsolps;
{
LOCK2(cs_main, cs_vNodes);
height = chainActive.Height();
tipmediantime = chainActive.Tip()->GetMedianTimePast();
connections = vNodes.size();
tlsConnections = std::count_if(vNodes.begin(), vNodes.end(), [](CNode* n) {return n->ssl != NULL;});
netsolps = GetNetworkHashPS(120, -1);
}
auto localsolps = GetLocalSolPS();
@ -219,7 +221,7 @@ int printStats(bool mining)
} else {
std::cout << " " << _("Block height") << " | " << height << std::endl;
}
std::cout << " " << _("Connections") << " | " << connections << std::endl;
std::cout << " " << _("Connections") << " | " << connections << " (TLS: " << tlsConnections << ")" << std::endl;
std::cout << " " << _("Network solution rate") << " | " << netsolps << " Sol/s" << std::endl;
if (mining && miningTimer.running()) {
std::cout << " " << _("Local solution rate") << " | " << strprintf("%.4f Sol/s", localsolps) << std::endl;

794
src/net.cpp

@ -16,6 +16,7 @@
#include "scheduler.h"
#include "ui_interface.h"
#include "crypto/common.h"
#include "utiltls.h"
#ifdef WIN32
#include <string.h>
@ -26,6 +27,10 @@
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <openssl/conf.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
// Dump addresses to peers.dat every 15 minutes (900s)
#define DUMP_ADDRESSES_INTERVAL 900
@ -44,6 +49,12 @@
#endif
#endif
#define USE_TLS
#define COMPAT_NON_TLS // enables compatibility with nodes, that still doesn't support TLS connections
typedef enum {sslAccept, sslConnect, sslShutdown} SSLConnectionRoutine;
typedef enum {clientContext, serverContext} TLSContextType;
using namespace std;
namespace {
@ -99,6 +110,28 @@ boost::condition_variable messageHandlerCondition;
static CNodeSignals g_signals;
CNodeSignals& GetNodeSignals() { return g_signals; }
// OpenSSL server and client contexts
SSL_CTX *tls_ctx_server, *tls_ctx_client;
typedef struct _NODE_ADDR
{
std::string ipAddr;
int64_t time; // time in msec, of an attempt to connect via TLS
_NODE_ADDR(std::string _ipAddr, int64_t _time = 0) : ipAddr(_ipAddr), time(_time){}
} NODE_ADDR, *PNODE_ADDR;
static bool operator==(_NODE_ADDR a, _NODE_ADDR b)
{
return (a.ipAddr == b.ipAddr);
}
static std::vector<NODE_ADDR> vNonTLSNodesInbound;
static CCriticalSection cs_vNonTLSNodesInbound;
static std::vector<NODE_ADDR> vNonTLSNodesOutbound;
static CCriticalSection cs_vNonTLSNodesOutbound;
void AddOneShot(const std::string& strDest)
{
LOCK(cs_vOneShots);
@ -351,6 +384,130 @@ CNode* FindNode(const CService& addr)
return NULL;
}
#ifdef USE_TLS
static int WaitFor(SSLConnectionRoutine eRoutine, SOCKET hSocket, SSL *ssl, int timeoutSec)
{
int nErr = 0;
while (true)
{
switch (eRoutine)
{
case sslConnect:
nErr = SSL_connect(ssl);
break;
case sslAccept:
nErr = SSL_accept(ssl);
break;
case sslShutdown:
nErr = SSL_shutdown(ssl);
break;
default:
return -1;
}
if (eRoutine == sslShutdown)
{
if (nErr >= 0)
break;
}
else
{
if (nErr == 1)
break;
}
int sslErr = SSL_get_error(ssl, nErr);
if (sslErr != SSL_ERROR_WANT_READ && sslErr != SSL_ERROR_WANT_WRITE)
{
LogPrint("net", "TLS: WARNING: %s: %s: ssl_err_code: %s; errno: %s\n", __FILE__, __func__, ERR_error_string(sslErr, NULL), strerror(errno));
nErr = -1;
break;
}
fd_set socketSet;
FD_ZERO(&socketSet);
FD_SET(hSocket, &socketSet);
struct timeval timeout = { timeoutSec, 0 };
if (sslErr == SSL_ERROR_WANT_READ)
{
int result = select(hSocket + 1, &socketSet, NULL, NULL, &timeout);
if (result == 0)
{
LogPrint("net", "TLS: ERROR: %s: %s: WANT_READ timeout\n", __FILE__, __func__);
nErr = -1;
break;
}
else if (result == -1)
{
LogPrint("net", "TLS: ERROR: %s: %s: WANT_READ ssl_err_code: %s; errno: %s\n", __FILE__, __func__, ERR_error_string(sslErr, NULL), strerror(errno));
nErr = -1;
break;
}
}
else
{
int result = select(hSocket + 1, NULL, &socketSet, NULL, &timeout);
if (result == 0)
{
LogPrint("net", "TLS: ERROR: %s: %s: WANT_WRITE timeout\n", __FILE__, __func__);
nErr = -1;
break;
}
else if (result == -1)
{
LogPrint("net", "TLS: ERROR: %s: %s: WANT_WRITE ssl_err_code: %s; errno: %s\n", __FILE__, __func__, ERR_error_string(sslErr, NULL), strerror(errno));
nErr = -1;
break;
}
}
}
return nErr;
}
static SSL* TLSConnect(SOCKET hSocket, const CAddress &addrConnect)
{
LogPrint("net", "TLS: establishing connection (tid = %X), (peerid = %s)\n", pthread_self(), addrConnect.ToString());
SSL *ssl = NULL;
bool bConnectedTLS = false;
if ((ssl = SSL_new(tls_ctx_client)))
{
if (SSL_set_fd(ssl, hSocket))
{
if (WaitFor(sslConnect, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000)) == 1)
bConnectedTLS = true;
}
}
if (bConnectedTLS)
{
LogPrintf ("TLS: connection to %s has been established. Using cipher: %s\n", addrConnect.ToString(), SSL_get_cipher(ssl));
}
else
{
LogPrintf ("TLS: %s: %s: TLS connection to %s failed\n", __FILE__, __func__, addrConnect.ToString());
if (ssl)
{
SSL_free(ssl);
ssl = NULL;
}
}
return ssl;
}
#endif // USE_TLS
CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
{
if (pszDest == NULL) {
@ -385,8 +542,67 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
addrman.Attempt(addrConnect);
SSL *ssl = NULL;
#ifdef USE_TLS
/* TCP connection is ready. Do client side SSL. */
#ifdef COMPAT_NON_TLS
{
LOCK(cs_vNonTLSNodesOutbound);
NODE_ADDR nodeAddr(addrConnect.ToStringIP());
bool bUseTLS = (find(vNonTLSNodesOutbound.begin(),
vNonTLSNodesOutbound.end(),
nodeAddr) == vNonTLSNodesOutbound.end());
if (bUseTLS)
{
ssl = TLSConnect(hSocket, addrConnect);
if (!ssl)
{
// Further reconnection will be made in non-TLS (unencrypted) mode
vNonTLSNodesOutbound.push_back(NODE_ADDR(addrConnect.ToStringIP(), GetTimeMillis()));
CloseSocket(hSocket);
return NULL;
}
}
else
{
LogPrintf ("Connection to %s will be unencrypted\n", addrConnect.ToString());
vNonTLSNodesOutbound.erase(
remove(
vNonTLSNodesOutbound.begin(),
vNonTLSNodesOutbound.end(),
nodeAddr),
vNonTLSNodesOutbound.end());
}
}
#else
ssl = TLSConnect(hSocket, addrConnect);
if(!ssl)
{
CloseSocket(hSocket);
return NULL;
}
#endif // COMPAT_NON_TLS
if (GetBoolArg("-tlsvalidate", false))
{
if (ssl && !ValidatePeerCertificate(ssl))
{
LogPrintf ("TLS: ERROR: Wrong server certificate from %s. Connection will be closed.\n", addrConnect.ToString());
SSL_shutdown(ssl);
CloseSocket(hSocket);
SSL_free(ssl);
return NULL;
}
}
#endif // USE_TLS
// Add node
CNode* pnode = new CNode(hSocket, addrConnect, pszDest ? pszDest : "", false);
CNode* pnode = new CNode(hSocket, addrConnect, pszDest ? pszDest : "", false, ssl);
pnode->AddRef();
{
@ -409,10 +625,24 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
void CNode::CloseSocketDisconnect()
{
fDisconnect = true;
if (hSocket != INVALID_SOCKET)
{
LogPrint("net", "disconnecting peer=%d\n", id);
CloseSocket(hSocket);
LOCK(cs_hSocket);
if (hSocket != INVALID_SOCKET)
{
LogPrint("net", "disconnecting peer=%d\n", id);
if (ssl)
{
WaitFor(sslShutdown, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000));
SSL_free(ssl);
ssl = NULL;
}
CloseSocket(hSocket);
}
}
// in case this fails, we'll empty the recv buffer when the CNode is deleted
@ -570,6 +800,13 @@ void CNode::copyStats(CNodeStats &stats)
// Leave string empty if addrLocal invalid (not filled in yet)
stats.addrLocal = addrLocal.IsValid() ? addrLocal.ToString() : "";
// If ssl != NULL it means TLS connection was established successfully
{
LOCK(cs_hSocket);
stats.fTLSEstablished = (ssl != NULL) && (SSL_get_state(ssl) == TLS_ST_OK);
stats.fTLSVerified = (ssl != NULL) && ValidatePeerCertificate(ssl);
}
}
#undef X
@ -659,52 +896,97 @@ int CNetMessage::readData(const char *pch, unsigned int nBytes)
return nCopy;
}
// requires LOCK(cs_vSend)
void SocketSendData(CNode *pnode)
{
std::deque<CSerializeData>::iterator it = pnode->vSendMsg.begin();
while (it != pnode->vSendMsg.end()) {
while (it != pnode->vSendMsg.end())
{
const CSerializeData &data = *it;
assert(data.size() > pnode->nSendOffset);
int nBytes = send(pnode->hSocket, &data[pnode->nSendOffset], data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT);
if (nBytes > 0) {
bool bIsSSL = false;
int nBytes = 0, nRet = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET)
{
LogPrint("net", "Send: connection with %s is already closed\n", pnode->addr.ToString());
break;
}
bIsSSL = (pnode->ssl != NULL);
if (bIsSSL)
{
nBytes = SSL_write(pnode->ssl, &data[pnode->nSendOffset], data.size() - pnode->nSendOffset);
nRet = SSL_get_error(pnode->ssl, nBytes);
}
else
{
nBytes = send(pnode->hSocket, &data[pnode->nSendOffset], data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT);
nRet = WSAGetLastError();
}
}
if (nBytes > 0)
{
pnode->nLastSend = GetTime();
pnode->nSendBytes += nBytes;
pnode->nSendOffset += nBytes;
pnode->RecordBytesSent(nBytes);
if (pnode->nSendOffset == data.size()) {
if (pnode->nSendOffset == data.size())
{
pnode->nSendOffset = 0;
pnode->nSendSize -= data.size();
it++;
} else {
}
else
{
// could not send full message; stop sending more
break;
}
} else {
if (nBytes < 0) {
}
else
{
if (nBytes <= 0)
{
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
//
if (bIsSSL)
{
LogPrintf("socket send error %s\n", NetworkErrorString(nErr));
pnode->CloseSocketDisconnect();
if (nRet != SSL_ERROR_WANT_READ && nRet != SSL_ERROR_WANT_WRITE)
{
LogPrintf("ERROR: SSL_write %s; closing connection\n", ERR_error_string(nRet, NULL));
pnode->CloseSocketDisconnect();
}
else
{
// preventive measure from exhausting CPU usage
//
MilliSleep(1); // 1 msec
}
}
else
{
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS)
{
LogPrintf("ERROR: send %s; closing connection\n", NetworkErrorString(nRet));
pnode->CloseSocketDisconnect();
}
}
}
// couldn't send anything at all
break;
}
}
if (it == pnode->vSendMsg.end()) {
if (it == pnode->vSendMsg.end())
{
assert(pnode->nSendOffset == 0);
assert(pnode->nSendSize == 0);
}
@ -867,6 +1149,44 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
return true;
}
#ifdef USE_TLS
static SSL* TLSAccept(SOCKET hSocket, const CAddress &addr)
{
LogPrint("net", "TLS: accepting connection from %s (tid = %X)\n", addr.ToString(), pthread_self());
SSL *ssl = NULL;
bool bAcceptedTLS = false;
if ((ssl = SSL_new(tls_ctx_server)))
{
if (SSL_set_fd(ssl, hSocket))
{
if (WaitFor(sslAccept, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000)) == 1)
bAcceptedTLS = true;
}
}
if (bAcceptedTLS)
{
LogPrintf ("TLS: connection from %s has been accepted. Using cipher: %s\n", addr.ToString(), SSL_get_cipher(ssl));
}
else
{
LogPrintf ("TLS: ERROR: %s: %s: TLS connection from %s failed\n", __FILE__, __func__, addr.ToString());
if (ssl)
{
SSL_free(ssl);
ssl = NULL;
}
}
return ssl;
}
#endif
static void AcceptConnection(const ListenSocket& hListenSocket) {
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
@ -928,18 +1248,124 @@ static void AcceptConnection(const ListenSocket& hListenSocket) {
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (void*)&set, sizeof(int));
#endif
CNode* pnode = new CNode(hSocket, addr, "", true);
SSL *ssl = NULL;
SetSocketNonBlocking(hSocket, true);
#ifdef USE_TLS
/* TCP connection is ready. Do server side SSL. */
#ifdef COMPAT_NON_TLS
{
LOCK(cs_vNonTLSNodesInbound);
NODE_ADDR nodeAddr(addr.ToStringIP());
bool bUseTLS = (find(vNonTLSNodesInbound.begin(),
vNonTLSNodesInbound.end(),
nodeAddr) == vNonTLSNodesInbound.end());
if (bUseTLS)
{
ssl = TLSAccept(hSocket, addr);
if(!ssl)
{
// Further reconnection will be made in non-TLS (unencrypted) mode
vNonTLSNodesInbound.push_back(NODE_ADDR(addr.ToStringIP(), GetTimeMillis()));
CloseSocket(hSocket);
return;
}
}
else
{
LogPrintf ("TLS: Connection from %s will be unencrypted\n", addr.ToString());
vNonTLSNodesInbound.erase(
remove(
vNonTLSNodesInbound.begin(),
vNonTLSNodesInbound.end(),
nodeAddr
),
vNonTLSNodesInbound.end());
}
}
#else
ssl = TLSAccept(hSocket, addr);
if(!ssl)
{
CloseSocket(hSocket);
return;
}
#endif // COMPAT_NON_TLS
if (GetBoolArg("-tlsvalidate", false))
{
if (ssl && !ValidatePeerCertificate(ssl))
{
LogPrintf ("TLS: ERROR: Wrong client certificate from %s. Connection will be closed.\n", addr.ToString());
SSL_shutdown(ssl);
CloseSocket(hSocket);
SSL_free(ssl);
return;
}
}
#endif // USE_TLS
CNode* pnode = new CNode(hSocket, addr, "", true, ssl);
pnode->AddRef();
pnode->fWhitelisted = whitelisted;
LogPrint("net", "connection from %s accepted\n", addr.ToString());
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
}
#if defined(USE_TLS) && defined(COMPAT_NON_TLS)
static bool IsNonTLSAddr(const string &strAddr, const vector<NODE_ADDR> &vPool, CCriticalSection &cs)
{
LOCK(cs);
return (find(vPool.begin(), vPool.end(), NODE_ADDR(strAddr)) != vPool.end());
}
static void CleanNonTLSPool(std::vector<NODE_ADDR> &vPool, CCriticalSection &cs)
{
LOCK(cs);
vector<NODE_ADDR> vDeleted;
BOOST_FOREACH(NODE_ADDR nodeAddr, vPool)
{
if ((GetTimeMillis() - nodeAddr.time) >= 900000)
{
vDeleted.push_back(nodeAddr);
LogPrint("net", "TLS: Node %s is deleted from the non-TLS pool\n", nodeAddr.ipAddr);
}
}
BOOST_FOREACH(NODE_ADDR nodeAddrDeleted, vDeleted)
{
vPool.erase(
remove(
vPool.begin(),
vPool.end(),
nodeAddrDeleted),
vPool.end());
}
}
void ThreadNonTLSPoolsCleaner()
{
while (true)
{
CleanNonTLSPool(vNonTLSNodesInbound, cs_vNonTLSNodesInbound);
CleanNonTLSPool(vNonTLSNodesOutbound, cs_vNonTLSNodesOutbound);
MilliSleep(DEFAULT_CONNECT_TIMEOUT); // sleep and sleep_for are interruption points, which will throw boost::thread_interrupted
}
}
#endif // USE_TLS && COMPAT_NON_TLS
void ThreadSocketHandler()
{
unsigned int nPrevNodeCount = 0;
@ -1034,8 +1460,11 @@ void ThreadSocketHandler()
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes)
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET)
continue;
FD_SET(pnode->hSocket, &fdsetError);
hSocketMax = max(hSocketMax, pnode->hSocket);
have_fds = true;
@ -1118,17 +1547,54 @@ void ThreadSocketHandler()
//
// Receive
//
if (pnode->hSocket == INVALID_SOCKET)
continue;
if (FD_ISSET(pnode->hSocket, &fdsetRecv) || FD_ISSET(pnode->hSocket, &fdsetError))
bool recvSet = false, sendSet = false, errorSet = false;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET)
continue;
recvSet = FD_ISSET(pnode->hSocket, &fdsetRecv);
sendSet = FD_ISSET(pnode->hSocket, &fdsetSend);
errorSet = FD_ISSET(pnode->hSocket, &fdsetError);
}
if (recvSet || errorSet)
{
TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
if (lockRecv)
{
{
// typical socket buffer is 8K-64K
// maximum record size is 16kB for SSLv3/TLSv1
char pchBuf[0x10000];
int nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
bool bIsSSL = false;
int nBytes = 0, nRet = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET)
{
LogPrint("net", "Receive: connection with %s is already closed\n", pnode->addr.ToString());
continue;
}
bIsSSL = (pnode->ssl != NULL);
if (bIsSSL)
{
nBytes = SSL_read(pnode->ssl, pchBuf, sizeof(pchBuf));
nRet = SSL_get_error(pnode->ssl, nBytes);
}
else
{
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
nRet = WSAGetLastError();
}
}
if (nBytes > 0)
{
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
@ -1139,20 +1605,39 @@ void ThreadSocketHandler()
}
else if (nBytes == 0)
{
// socket closed gracefully
// socket closed gracefully (peer disconnected)
//
if (!pnode->fDisconnect)
LogPrint("net", "socket closed\n");
LogPrint("net", "socket closed (%s)\n", pnode->addr.ToString());
pnode->CloseSocketDisconnect();
}
else if (nBytes < 0)
{
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
//
if (bIsSSL)
{
if (!pnode->fDisconnect)
LogPrintf("socket recv error %s\n", NetworkErrorString(nErr));
pnode->CloseSocketDisconnect();
if (nRet != SSL_ERROR_WANT_READ && nRet != SSL_ERROR_WANT_WRITE) // SSL_read() operation has to be repeated because of SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE (https://wiki.openssl.org/index.php/Manual:SSL_read(3)#NOTES)
{
if (!pnode->fDisconnect)
LogPrintf("ERROR: SSL_read %s\n", ERR_error_string(nRet, NULL));
pnode->CloseSocketDisconnect();
}
else
{
// preventive measure from exhausting CPU usage
//
MilliSleep(1); // 1 msec
}
}
else
{
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS)
{
if (!pnode->fDisconnect)
LogPrintf("ERROR: socket recv %s\n", NetworkErrorString(nRet));
pnode->CloseSocketDisconnect();
}
}
}
}
@ -1162,9 +1647,7 @@ void ThreadSocketHandler()
//
// Send
//
if (pnode->hSocket == INVALID_SOCKET)
continue;
if (FD_ISSET(pnode->hSocket, &fdsetSend))
if (sendSet)
{
TRY_LOCK(pnode->cs_vSend, lockSend);
if (lockSend)
@ -1479,6 +1962,28 @@ bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOu
CNode* pnode = ConnectNode(addrConnect, pszDest);
boost::this_thread::interruption_point();
#if defined(USE_TLS) && defined(COMPAT_NON_TLS)
if (!pnode)
{
string strDest;
int port;
if (!pszDest)
strDest = addrConnect.ToStringIP();
else
SplitHostPort(string(pszDest), port, strDest);
if (IsNonTLSAddr(strDest, vNonTLSNodesOutbound, cs_vNonTLSNodesOutbound))
{
// Attempt to reconnect in non-TLS mode
pnode = ConnectNode(addrConnect, pszDest);
boost::this_thread::interruption_point();
}
}
#endif
if (!pnode)
return false;
if (grantOutbound)
@ -1610,6 +2115,11 @@ bool BindListenPort(const CService &addrBind, string& strError, bool fWhiteliste
#endif
// Set to non-blocking, incoming connections will also inherit this
//
// WARNING!
// On Linux, the new socket returned by accept() does not inherit file
// status flags such as O_NONBLOCK and O_ASYNC from the listening
// socket. http://man7.org/linux/man-pages/man2/accept.2.html
if (!SetSocketNonBlocking(hListenSocket, true)) {
strError = strprintf("BindListenPort: Setting listening socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
@ -1713,6 +2223,167 @@ void static Discover(boost::thread_group& threadGroup)
#endif
}
#ifdef USE_TLS
static int TLSCertVerificationCallback(int preverify_ok, X509_STORE_CTX *chainContext)
{
//If verify_callback always returns 1, the TLS/SSL handshake will not be terminated with respect to verification failures and the connection will be established.
return 1;
}
static SSL_CTX* TLSInitCtx(
TLSContextType ctxType,
const boost::filesystem::path &privateKeyFile,
const boost::filesystem::path &certificateFile,
const std::vector<boost::filesystem::path> &trustedDirs)
{
if (!boost::filesystem::exists(privateKeyFile) ||
!boost::filesystem::exists(certificateFile))
return NULL;
bool bInitialized = false;
SSL_CTX *tlsCtx = NULL;
if ((tlsCtx = SSL_CTX_new(ctxType == serverContext ? TLS_server_method() : TLS_client_method())))
{
SSL_CTX_set_mode(tlsCtx, SSL_MODE_AUTO_RETRY);
int rootCertsNum = LoadDefaultRootCertificates(tlsCtx);
int trustedPathsNum = 0;
for (boost::filesystem::path trustedDir : trustedDirs)
{
if (SSL_CTX_load_verify_locations(tlsCtx, NULL, trustedDir.string().c_str()) == 1)
trustedPathsNum++;
}
if (rootCertsNum == 0 && trustedPathsNum == 0)
LogPrintf("TLS: WARNING: %s: %s: failed to set up verified certificates. It will be impossible to verify peer certificates. \n", __FILE__, __func__);
SSL_CTX_set_verify(tlsCtx, SSL_VERIFY_PEER, TLSCertVerificationCallback);
if (SSL_CTX_use_certificate_file(tlsCtx, certificateFile.string().c_str(), SSL_FILETYPE_PEM) > 0)
{
if (SSL_CTX_use_PrivateKey_file(tlsCtx, privateKeyFile.string().c_str(), SSL_FILETYPE_PEM) > 0)
{
if (SSL_CTX_check_private_key(tlsCtx))
bInitialized = true;
else
LogPrintf("TLS: ERROR: %s: %s: private key does not match the certificate public key\n", __FILE__, __func__);
}
else
LogPrintf("TLS: ERROR: %s: %s: failed to use privateKey file\n", __FILE__, __func__);
}
else
{
LogPrintf("TLS: ERROR: %s: %s: failed to use certificate file\n", __FILE__, __func__);
ERR_print_errors_fp(stderr);
}
}
else
LogPrintf("TLS: ERROR: %s: %s: failed to create TLS context\n", __FILE__, __func__);
if (!bInitialized)
{
if (tlsCtx)
{
SSL_CTX_free(tlsCtx);
tlsCtx = NULL;
}
}
return tlsCtx;
}
static bool TLSInitialize()
{
bool bInitializationStatus = false;
// Initialization routines for the OpenSSL library
//
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms(); // OpenSSL_add_ssl_algorithms() always returns "1", so it is safe to discard the return value.
namespace fs = boost::filesystem;
fs::path certFile = GetArg("-tlscertpath", "");
if (!fs::exists(certFile))
certFile = (GetDataDir() / TLS_CERT_FILE_NAME);
fs::path privKeyFile = GetArg("-tlskeypath", "");
if (!fs::exists(privKeyFile))
privKeyFile = (GetDataDir() / TLS_KEY_FILE_NAME);
std::vector<fs::path> trustedDirs;
fs::path trustedDir = GetArg("-tlstrustdir", "");
if (fs::exists(trustedDir))
// Use only the specified trusted directory
trustedDirs.push_back(trustedDir);
else
// If specified directory can't be used, then setting the default trusted directories
trustedDirs = GetDefaultTrustedDirectories();
for (fs::path dir : trustedDirs)
LogPrintf("TLS: trusted directory '%s' will be used\n", dir.c_str());
// Initialization of the server and client contexts
//
if ((tls_ctx_server = TLSInitCtx(serverContext, privKeyFile, certFile, trustedDirs)))
{
if ((tls_ctx_client = TLSInitCtx(clientContext, privKeyFile, certFile, trustedDirs)))
{
LogPrint("net", "TLS: contexts are initialized\n");
bInitializationStatus = true;
}
else
{
LogPrintf("TLS: ERROR: %s: %s: failed to initialize TLS client context\n", __FILE__, __func__);
SSL_CTX_free (tls_ctx_server);
}
}
else
LogPrintf("TLS: ERROR: %s: %s: failed to initialize TLS server context\n", __FILE__, __func__);
return bInitializationStatus;
}
static bool TLSPrepareCredentials()
{
boost::filesystem::path
defaultKeyPath (GetDataDir() / TLS_KEY_FILE_NAME),
defaultCertPath(GetDataDir() / TLS_CERT_FILE_NAME);
CredentialsStatus credStatus =
VerifyCredentials(
boost::filesystem::path(GetArg("-tlskeypath", defaultKeyPath.string())),
boost::filesystem::path(GetArg("-tlscertpath", defaultCertPath.string())),
GetArg("-tlskeypwd",""));
bool bPrepared = (credStatus == credOk);
if (!bPrepared)
{
if (!mapArgs.count("-tlskeypath") && !mapArgs.count("-tlscertpath"))
{
// Default paths were used
//
if (credStatus == credAbsent)
{
// Generate new credentials (key and self-signed certificate on it) only if credentials were absent previously
//
bPrepared = GenerateCredentials(
defaultKeyPath,
defaultCertPath,
GetArg("-tlskeypwd",""));
}
}
}
return bPrepared;
}
#endif // USE_TLS
void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
{
uiInterface.InitMessage(_("Loading addresses..."));
@ -1738,6 +2409,23 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
Discover(threadGroup);
#ifdef USE_TLS
if (!TLSPrepareCredentials())
{
LogPrintf("TLS: ERROR: %s: %s: Credentials weren't loaded. Node can't be started.\n", __FILE__, __func__);
return;
}
if (!TLSInitialize())
{
LogPrintf("TLS: ERROR: %s: %s: TLS initialization failed. Node can't be started.\n", __FILE__, __func__);
return;
}
#else
LogPrintf("TLS is not used!\n");
#endif
//
// Start threads
//
@ -1759,6 +2447,11 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
// Process messages
threadGroup.create_thread(boost::bind(&TraceThread<void (*)()>, "msghand", &ThreadMessageHandler));
#if defined(USE_TLS) && defined(COMPAT_NON_TLS)
// Clean pools of addresses for non-TLS connections
threadGroup.create_thread(boost::bind(&TraceThread<void (*)()>, "poolscleaner", &ThreadNonTLSPoolsCleaner));
#endif
// Dump network addresses
scheduler.scheduleEvery(&DumpAddresses, DUMP_ADDRESSES_INTERVAL);
}
@ -1788,8 +2481,7 @@ public:
{
// Close sockets
BOOST_FOREACH(CNode* pnode, vNodes)
if (pnode->hSocket != INVALID_SOCKET)
CloseSocket(pnode->hSocket);
pnode->CloseSocketDisconnect();
BOOST_FOREACH(ListenSocket& hListenSocket, vhListenSocket)
if (hListenSocket.socket != INVALID_SOCKET)
if (!CloseSocket(hListenSocket.socket))
@ -2024,11 +2716,12 @@ bool CAddrDB::Read(CAddrMan& addr)
unsigned int ReceiveFloodSize() { return 1000*GetArg("-maxreceivebuffer", 5*1000); }
unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 1*1000); }
CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNameIn, bool fInboundIn) :
CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNameIn, bool fInboundIn, SSL *sslIn) :
ssSend(SER_NETWORK, INIT_PROTO_VERSION),
addrKnown(5000, 0.001),
setInventoryKnown(SendBufferSize() / 1000)
{
ssl = sslIn;
nServices = 0;
hSocket = hSocketIn;
nRecvVersion = INIT_PROTO_VERSION;
@ -2083,7 +2776,18 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
CNode::~CNode()
{
CloseSocket(hSocket);
// No need to make a lock on cs_hSocket, because before deletion CNode object is removed from the vNodes vector, so any other thread hasn't access to it.
// Removal is synchronized with read and write routines, so all of them will be completed to this moment.
if (hSocket != INVALID_SOCKET)
{
if (ssl)
{
WaitFor(sslShutdown, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000));
SSL_free(ssl);
ssl = NULL;
}
CloseSocket(hSocket);
}
if (pfilter)
delete pfilter;

29
src/net.h

@ -30,6 +30,10 @@
#include <boost/foreach.hpp>
#include <boost/signals2/signal.hpp>
// Enable OpenSSL Support for Zen
#include <openssl/bio.h>
#include <openssl/ssl.h>
class CAddrMan;
class CBlockIndex;
class CScheduler;
@ -74,6 +78,20 @@ bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhite
void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler);
bool StopNode();
void SocketSendData(CNode *pnode);
void SocketSendData(CNode *pnode);
SSL_CTX* create_context(bool server_side);
EVP_PKEY *generate_key();
X509 *generate_x509(EVP_PKEY *pkey);
bool write_to_disk(EVP_PKEY *pkey, X509 *x509);
void configure_context(SSL_CTX *ctx, bool server_side);
static boost::filesystem::path tlsKeyPath;
static boost::filesystem::path tlsCertPath;
// OpenSSL related variables for metrics.cpp
static std::string routingsecrecy;
static std::string cipherdescription;
static std::string securitylevel;
static std::string validationdescription;
typedef int NodeId;
@ -154,6 +172,9 @@ extern CCriticalSection cs_vAddedNodes;
extern NodeId nLastNodeId;
extern CCriticalSection cs_nLastNodeId;
extern SSL_CTX *tls_ctx_server;
extern SSL_CTX *tls_ctx_client;
struct LocalServiceInfo {
int nScore;
int nPort;
@ -167,6 +188,8 @@ class CNodeStats
public:
NodeId nodeid;
uint64_t nServices;
bool fTLSEstablished;
bool fTLSVerified;
int64_t nLastSend;
int64_t nLastRecv;
int64_t nTimeConnected;
@ -233,9 +256,13 @@ public:
class CNode
{
public:
// OpenSSL
SSL *ssl;
// socket
uint64_t nServices;
SOCKET hSocket;
CCriticalSection cs_hSocket;
CDataStream ssSend;
size_t nSendSize; // total size of all vSendMsg entries
size_t nSendOffset; // offset inside the first vSendMsg already sent
@ -324,7 +351,7 @@ public:
// Whether a ping is requested.
bool fPingQueued;
CNode(SOCKET hSocketIn, const CAddress &addrIn, const std::string &addrNameIn = "", bool fInboundIn = false);
CNode(SOCKET hSocketIn, const CAddress &addrIn, const std::string &addrNameIn = "", bool fInboundIn = false, SSL *sslIn = NULL);
~CNode();
private:

7
src/rpcnet.cpp

@ -13,6 +13,7 @@
#include "timedata.h"
#include "util.h"
#include "version.h"
#include "utiltls.h"
#include <boost/foreach.hpp>
@ -87,6 +88,8 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp)
" \"addr\":\"host:port\", (string) The ip address and port of the peer\n"
" \"addrlocal\":\"ip:port\", (string) local address\n"
" \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n"
" \"tls_established\": true:false, (boolean) Status of TLS connection\n"
" \"tls_verified\": true:false, (boolean) Status of peer certificate. Will be true if a peer certificate can be verified with some trusted root certs \n"
" \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n"
" \"lastrecv\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last receive\n"
" \"bytessent\": n, (numeric) The total bytes sent\n"
@ -130,6 +133,8 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp)
if (!(stats.addrLocal.empty()))
obj.push_back(Pair("addrlocal", stats.addrLocal));
obj.push_back(Pair("services", strprintf("%016x", stats.nServices)));
obj.push_back(Pair("tls_established", stats.fTLSEstablished));
obj.push_back(Pair("tls_verified", stats.fTLSVerified));
obj.push_back(Pair("lastsend", stats.nLastSend));
obj.push_back(Pair("lastrecv", stats.nLastRecv));
obj.push_back(Pair("bytessent", stats.nSendBytes));
@ -412,6 +417,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp)
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services we offer to the network\n"
" \"timeoffset\": xxxxx, (numeric) the time offset\n"
" \"connections\": xxxxx, (numeric) the number of connections\n"
" \"tls_cert_verified\": true|flase, (boolean) true if the certificate of the current node is verified\n"
" \"networks\": [ (array) information per network\n"
" {\n"
" \"name\": \"xxx\", (string) network (ipv4, ipv6 or onion)\n"
@ -447,6 +453,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp)
obj.push_back(Pair("localservices", strprintf("%016x", nLocalServices)));
obj.push_back(Pair("timeoffset", GetTimeOffset()));
obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("tls_cert_verified", ValidateCertificate(tls_ctx_server)));
obj.push_back(Pair("networks", GetNetworksInfo()));
obj.push_back(Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())));
UniValue localAddresses(UniValue::VARR);

491
src/utiltls.cpp

@ -0,0 +1,491 @@
// Copyright (c) 2017 The Zen Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <stdio.h>
#include <vector>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include "util.h"
#include "utiltls.h"
// Set of most common default trusted certificates directories used by OpenSSL
static const char* defaultTrustedDirs[] =
{
#ifdef WIN32
""
#elif MAC_OSX
"/System/Library/OpenSSL/certs"
#else // Linux build
"/etc/ssl/certs",
"/usr/local/ssl/certs",
"/usr/lib/ssl/certs",
"/usr/share/ssl/certs",
"/etc/pki/tls/certs",
"/var/lib/ca-certificates"
#endif
};
// Default root certificates (PEM encoded)
static const char defaultRootCerts[] =
{
// // Example of specifying a certificate
// //
// "-----BEGIN CERTIFICATE-----\n"
// "MIIDYDCCAkigAwIBAgIJAJMakdoBYY67MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"
// "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"
// "aWRnaXRzIFB0eSBMdGQwHhcNMTcwODE0MTc0MTMyWhcNNDQxMjMwMTc0MTMyWjBF\n"
// "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"
// "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
// "CgKCAQEAzNV+SPRCKSEGlntfpCRMVSfz99NoEo3K1SRyw6GTSb1LNSTQCn1EsCSH\n"
// "cVZTmyfjcTHpwz4aF14yw8lQC42f218AOsG1DV5suCaUXhSmZlajMkvEJVwfBOft\n"
// "xpcqE1fA9wovXlnJLXVgyJGMc896S8tcbrCU/l/BsqKh5QX8N60MQ3w376nSGvVP\n"
// "ussN8bVH3aKRwjhateqx1GRt0GPnM8/u7EkgF8Bc+m8WZYcUfkPC5Am2D0MO1HOA\n"
// "u3IKxXZMs/fYd6nF5DZBwg+D23EP/V8oqenn8ilvrSORq5PguOl1QoDyY66PhmjN\n"
// "L9c4Spxw8HXUDlrfuSQn2NJnw1XhdQIDAQABo1MwUTAdBgNVHQ4EFgQU/KD+n5Bz\n"
// "QLbp09qKzwwyNwOQU4swHwYDVR0jBBgwFoAU/KD+n5BzQLbp09qKzwwyNwOQU4sw\n"
// "DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVtprBxZD6O+WNYUM\n"
// "ksdKiVVoszEJXlt7wajuaPBPK/K3buxE9FLVxS+LiH1PUhPCc6V28guyKWwn109/\n"
// "4WnO51LQjygvd7SaePlbiO7iIatkOk4oETJQZ+tEJ7fv/NITY/GQUfgPNkANmPPz\n"
// "Mz9I6He8XhIpO6NGuDG+74aR1RhvR3PWJJYT0QpL0STVR4qTc/HfnymF5XnnjOYZ\n"
// "mwzT8jXX5dhLYwJmyPBS+uv+oa1quM/FitA63N9anYtRBiPaBtund9Ikjat1hM0h\n"
// "neo2tz7Mfsgjb0aiORtiyaH2OetvwR0QuCSVPnknkfGWPDINdUdkgKyA1PX58Smw\n"
// "vaXEcw==\n"
// "-----END CERTIFICATE-----"
""
};
// Generates RSA keypair (a private key of 'bits' length for a specified 'uPublicKey')
//
static EVP_PKEY* GenerateRsaKey(int bits, BN_ULONG uPublicKey)
{
EVP_PKEY *evpPrivKey = NULL;
BIGNUM *pubKey = BN_new();
if (pubKey)
{
if (BN_set_word(pubKey, uPublicKey))
{
RSA *privKey = RSA_new();
if (privKey)
{
if (RAND_poll() && // The pseudo-random number generator must be seeded prior to calling RSA_generate_key_ex(). (https://www.openssl.org/docs/man1.1.0/crypto/RSA_generate_key.html)
RSA_generate_key_ex(privKey, bits, pubKey, NULL))
{
if ((evpPrivKey = EVP_PKEY_new()))
{
if (!EVP_PKEY_assign_RSA(evpPrivKey, privKey))
{
EVP_PKEY_free(evpPrivKey);
evpPrivKey = NULL;
}
}
}
if(!evpPrivKey) // EVP_PKEY_assign_RSA uses the supplied key internally
RSA_free(privKey);
}
}
BN_free(pubKey);
}
return evpPrivKey;
}
// Generates certificate for a specified public key using a corresponding private key (both of them should be specified in the 'keypair').
//
static X509* GenerateCertificate(EVP_PKEY *keypair)
{
if (!keypair)
return NULL;
X509 *cert = X509_new();
if (cert)
{
bool bCertSigned = false;
long sn = 0;
if (RAND_bytes((unsigned char*)&sn, sizeof sn) &&
ASN1_INTEGER_set(X509_get_serialNumber(cert), sn))
{
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), (60 * 60 * 24 * CERT_VALIDITY_DAYS));
// setting a public key from the keypair
if (X509_set_pubkey(cert, keypair))
{
X509_NAME *subjectName = X509_get_subject_name(cert);
if (subjectName)
{
// an issuer name is the same as a subject name, due to certificate is self-signed
if (X509_set_issuer_name(cert, subjectName))
{
// private key from keypair is used; signature will be set inside of the cert
bCertSigned = X509_sign(cert, keypair, EVP_sha512());
}
}
}
}
if (!bCertSigned)
{
X509_free(cert);
cert = NULL;
}
}
return cert;
}
// Stores key to file, specified by the 'filePath'
//
static bool StoreKey(EVP_PKEY *key, const boost::filesystem::path &filePath, const std::string &passphrase)
{
if (!key)
return false;
bool bStored = false;
FILE *keyfd = fopen(filePath.string().c_str(), "wb");
if (keyfd)
{
const EVP_CIPHER* pCipher = NULL;
if (passphrase.length() && (pCipher = EVP_aes_256_cbc()))
bStored = PEM_write_PrivateKey(keyfd, key, pCipher, NULL, 0, NULL, (void*)passphrase.c_str());
else
bStored = PEM_write_PrivateKey(keyfd, key, NULL, NULL, 0, NULL, NULL);
fclose(keyfd);
}
return bStored;
}
// Stores certificate to file, specified by the 'filePath'
//
static bool StoreCertificate(X509 *cert, const boost::filesystem::path &filePath)
{
if (!cert)
return false;
bool bStored = false;
FILE *certfd = fopen(filePath.string().c_str(), "wb");
if (certfd)
{
bStored = PEM_write_X509(certfd, cert);
fclose(certfd);
}
return bStored;
}
// Loads key from file, specified by the 'filePath'
//
static EVP_PKEY* LoadKey(const boost::filesystem::path &filePath, const std::string &passphrase)
{
if (!boost::filesystem::exists(filePath))
return NULL;
EVP_PKEY *key = NULL;
FILE *keyfd = fopen(filePath.string().c_str(), "rb");
if (keyfd)
{
key = PEM_read_PrivateKey(keyfd, NULL, NULL, passphrase.length() ? (void*)passphrase.c_str() : NULL);
fclose(keyfd);
}
return key;
}
// Loads certificate from file, specified by the 'filePath'
//
static X509* LoadCertificate(const boost::filesystem::path &filePath)
{
if (!boost::filesystem::exists(filePath))
return NULL;
X509 *cert = NULL;
FILE *certfd = fopen(filePath.string().c_str(), "rb");
if (certfd)
{
cert = PEM_read_X509(certfd, NULL, NULL, NULL);
fclose(certfd);
}
return cert;
}
// Verifies if the private key in 'key' matches the public key in 'cert'
// (Signs random bytes on 'key' and verifies signature correctness on public key from 'cert')
//
static bool IsMatching(EVP_PKEY *key, X509 *cert)
{
if (!key || !cert)
return false;
bool bIsMatching = false;
EVP_PKEY_CTX *ctxSign = EVP_PKEY_CTX_new(key, NULL);
if (ctxSign)
{
if (EVP_PKEY_sign_init(ctxSign) == 1 &&
EVP_PKEY_CTX_set_signature_md(ctxSign, EVP_sha512()) > 0)
{
unsigned char digest[SHA512_DIGEST_LENGTH] = { 0 };
size_t digestSize = sizeof digest, signatureSize = 0;
if (RAND_bytes((unsigned char*)&digest, digestSize) && // set random bytes as a digest
EVP_PKEY_sign(ctxSign, NULL, &signatureSize, digest, digestSize) == 1) // determine buffer length
{
unsigned char *signature = (unsigned char*)OPENSSL_malloc(signatureSize);
if (signature)
{
if (EVP_PKEY_sign(ctxSign, signature, &signatureSize, digest, digestSize) == 1)
{
EVP_PKEY *pubkey = X509_get_pubkey(cert);
if (pubkey)
{
EVP_PKEY_CTX *ctxVerif = EVP_PKEY_CTX_new(pubkey, NULL);
if (ctxVerif)
{
if (EVP_PKEY_verify_init(ctxVerif) == 1 &&
EVP_PKEY_CTX_set_signature_md(ctxVerif, EVP_sha512()) > 0)
{
bIsMatching = (EVP_PKEY_verify(ctxVerif, signature, signatureSize, digest, digestSize) == 1);
}
EVP_PKEY_CTX_free(ctxVerif);
}
EVP_PKEY_free(pubkey);
}
}
OPENSSL_free(signature);
}
}
}
EVP_PKEY_CTX_free(ctxSign);
}
return bIsMatching;
}
// Checks the correctness of a private-public key pair and the validity of a certificate using public key from key pair
//
static bool CheckCredentials(EVP_PKEY *key, X509 *cert)
{
if (!key || !cert)
return false;
bool bIsOk = false;
// Validating the correctness of a private-public key pair, depending on a key type
//
switch (EVP_PKEY_base_id(key))
{
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
{
RSA *rsaKey = EVP_PKEY_get1_RSA(key);
if (rsaKey)
{
bIsOk = (RSA_check_key(rsaKey) == 1);
RSA_free(rsaKey);
}
break;
}
// Currently only RSA keys are supported.
// Other key types can be added here in further.
default:
bIsOk = false;
}
// Verifying if the private key matches the public key in certificate
if (bIsOk)
bIsOk = IsMatching(key, cert);
return bIsOk;
}
// Verifies credentials (a private key, a certificate for public key and a correspondence between the private and the public key)
//
CredentialsStatus VerifyCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase)
{
CredentialsStatus status = credAbsent;
EVP_PKEY *key = NULL;
X509 *cert = NULL;
key = LoadKey(keyPath, passphrase);
cert = LoadCertificate(certPath);
if (key && cert)
status = CheckCredentials(key, cert) ? credOk : credNonConsistent;
else if (!key && !cert)
status = credAbsent;
else
status = credPartiallyAbsent;
if (key)
EVP_PKEY_free(key);
if (cert)
X509_free(cert);
return status;
}
// Generates public key pair and the self-signed certificate for it, and then stores them by the specified paths 'keyPath' and 'certPath' respectively.
//
bool GenerateCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase)
{
bool bGenerated = false;
EVP_PKEY *key = NULL;
X509 *cert = NULL;
// Generating RSA key and the self-signed certificate for it
//
key = GenerateRsaKey(TLS_RSA_KEY_SIZE, RSA_F4);
if (key)
{
cert = GenerateCertificate(key);
if (cert)
{
if (StoreKey(key, keyPath, passphrase) &&
StoreCertificate(cert, certPath))
{
bGenerated = true;
LogPrintStr("TLS: New private key and self-signed certificate were generated successfully\n");
}
X509_free(cert);
}
EVP_PKEY_free(key);
}
return bGenerated;
}
// Checks if certificate of a peer is valid (by internal means of the TLS protocol)
//
// Validates peer certificate using a chain of CA certificates.
// If some of intermediate CA certificates are absent in the trusted certificates store, then validation status will be 'false')
//
bool ValidatePeerCertificate(SSL *ssl)
{
if (!ssl)
return false;
bool bIsOk = false;
X509 *cert = SSL_get_peer_certificate (ssl);
if (cert)
{
// NOTE: SSL_get_verify_result() is only useful in connection with SSL_get_peer_certificate (https://www.openssl.org/docs/man1.0.2/ssl/SSL_get_verify_result.html)
//
bIsOk = (SSL_get_verify_result(ssl) == X509_V_OK);
X509_free(cert);
}
else
{
LogPrint("net", "TLS: Peer does not have certificate\n");
bIsOk = false;
}
return bIsOk;
}
// Check if a given context is set up with a cert that can be validated by this context
//
bool ValidateCertificate(SSL_CTX *ssl_ctx)
{
if (!ssl_ctx)
return false;
bool bIsOk = false;
X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
if (store)
{
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
if (ctx)
{
if (X509_STORE_CTX_init(ctx, store, SSL_CTX_get0_certificate(ssl_ctx), NULL) == 1)
bIsOk = X509_verify_cert(ctx) == 1;
X509_STORE_CTX_free(ctx);
}
}
return bIsOk;
}
// Creates the list of available OpenSSL default directories for trusted certificates storage
//
std::vector<boost::filesystem::path> GetDefaultTrustedDirectories()
{
namespace fs = boost::filesystem;
std::vector<fs::path> defaultDirectoriesList;
// Default certificates directory specified in OpenSSL build
fs::path libDefaultDir = X509_get_default_cert_dir();
if (fs::exists(libDefaultDir))
defaultDirectoriesList.push_back(libDefaultDir);
// Check and set all possible standard default directories
for (const char *dir : defaultTrustedDirs)
{
fs::path defaultDir(dir);
if (defaultDir != libDefaultDir &&
fs::exists(defaultDir))
defaultDirectoriesList.push_back(defaultDir);
}
return defaultDirectoriesList;
}
// Loads default root certificates (placed in the 'defaultRootCerts') into the specified context.
// Returns the number of loaded certificates.
//
int LoadDefaultRootCertificates(SSL_CTX *ctx)
{
if (!ctx)
return 0;
int certsLoaded = 0;
// Certificate text buffer 'defaultRootCerts' is a C string with certificates in PEM format
BIO *memBuf = BIO_new_mem_buf(defaultRootCerts, -1);
if (memBuf)
{
X509 *cert = NULL;
while ((cert = PEM_read_bio_X509(memBuf, NULL, 0, NULL)))
{
if (X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert) > 0)
certsLoaded++;
X509_free(cert);
}
BIO_free(memBuf);
}
return certsLoaded;
}

53
src/utiltls.h

@ -0,0 +1,53 @@
// Copyright (c) 2017 The Zen Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef UTILTLS_H
#define UTILTLS_H
#include <boost/filesystem/path.hpp>
#define TLS_KEY_FILE_NAME "key.pem" // default name of a private key
#define TLS_CERT_FILE_NAME "cert.pem" // default name of a certificate
#define CERT_VALIDITY_DAYS (365 * 10) // period of validity, in days, for a self-signed certificate
#define TLS_RSA_KEY_SIZE 2048 // size of a private RSA key, in bits, that will be generated, if no other key is specified
typedef enum {credOk, credNonConsistent, credAbsent, credPartiallyAbsent} CredentialsStatus;
// Verifies credentials (a private key, a certificate for public key and a correspondence between the private and the public key)
//
CredentialsStatus VerifyCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase);
// Generates public key pair and the self-signed certificate for it, and then stores them by the specified paths 'keyPath' and 'certPath' respectively.
//
bool GenerateCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase);
// Checks if certificate of a peer is valid (by internal means of the TLS protocol)
//
// Validates peer certificate using a chain of CA certificates.
// If some of intermediate CA certificates are absent in the trusted certificates store, then validation status will be 'false')
//
bool ValidatePeerCertificate(SSL *ssl);
// Check if a given context is set up with a cert that can be validated by this context
//
bool ValidateCertificate(SSL_CTX *ssl_ctx);
// Creates the list of available OpenSSL default directories for trusted certificates storage
//
std::vector<boost::filesystem::path> GetDefaultTrustedDirectories();
// Loads default root certificates (placed in the 'defaultRootCerts') into the specified context.
// Returns the number of loaded certificates.
//
int LoadDefaultRootCertificates(SSL_CTX *ctx);
#endif // UTILTLS_H
Loading…
Cancel
Save