miodragpop
4 years ago
11 changed files with 1479 additions and 72 deletions
@ -0,0 +1,8 @@ |
|||
namespace hush |
|||
{ |
|||
typedef enum { SSL_ACCEPT, |
|||
SSL_CONNECT, |
|||
SSL_SHUTDOWN } SSLConnectionRoutine; |
|||
typedef enum { CLIENT_CONTEXT, |
|||
SERVER_CONTEXT } TLSContextType; |
|||
} |
@ -0,0 +1,484 @@ |
|||
#include <openssl/conf.h> |
|||
#include <openssl/ssl.h> |
|||
#include <openssl/err.h> |
|||
#include "utiltls.h" |
|||
|
|||
#include <boost/filesystem.hpp> |
|||
#include <boost/thread.hpp> |
|||
#include "../util.h" |
|||
#include "../protocol.h" |
|||
|
|||
#include <boost/filesystem.hpp> |
|||
#include <boost/thread.hpp> |
|||
|
|||
#include "tlsmanager.h" |
|||
using namespace std; |
|||
namespace hush |
|||
{ |
|||
/**
|
|||
* @brief 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. |
|||
* |
|||
* @param preverify_ok |
|||
* @param chainContext |
|||
* @return int |
|||
*/ |
|||
int tlsCertVerificationCallback(int preverify_ok, X509_STORE_CTX* chainContext) |
|||
{ |
|||
return 1; |
|||
} |
|||
/**
|
|||
* @brief Wait for a given SSL connection event. |
|||
* |
|||
* @param eRoutine a SSLConnectionRoutine value which determines the type of the event. |
|||
* @param hSocket |
|||
* @param ssl pointer to an SSL instance. |
|||
* @param timeoutSec timeout in seconds. |
|||
* @return int returns nError corresponding to the connection event. |
|||
*/ |
|||
int TLSManager::waitFor(SSLConnectionRoutine eRoutine, SOCKET hSocket, SSL* ssl, int timeoutSec) |
|||
{ |
|||
int nErr = 0; |
|||
ERR_clear_error(); // clear the error queue
|
|||
|
|||
while (true) { |
|||
switch (eRoutine) { |
|||
case SSL_CONNECT: |
|||
nErr = SSL_connect(ssl); |
|||
break; |
|||
|
|||
case SSL_ACCEPT: |
|||
nErr = SSL_accept(ssl); |
|||
break; |
|||
|
|||
case SSL_SHUTDOWN: |
|||
nErr = SSL_shutdown(ssl); |
|||
break; |
|||
|
|||
default: |
|||
return -1; |
|||
} |
|||
|
|||
if (eRoutine == SSL_SHUTDOWN) { |
|||
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; |
|||
} |
|||
/**
|
|||
* @brief establish TLS connection to an address |
|||
* |
|||
* @param hSocket socket |
|||
* @param addrConnect the outgoing address |
|||
* @param tls_ctx_client TLS Client context |
|||
* @return SSL* returns a ssl* if successful, otherwise returns NULL. |
|||
*/ |
|||
SSL* TLSManager::connect(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 (TLSManager::waitFor(SSL_CONNECT, 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; |
|||
} |
|||
/**
|
|||
* @brief Initialize TLS Context |
|||
* |
|||
* @param ctxType context type |
|||
* @param privateKeyFile private key file path |
|||
* @param certificateFile certificate key file path |
|||
* @param trustedDirs trusted directories |
|||
* @return SSL_CTX* returns the context. |
|||
*/ |
|||
SSL_CTX* TLSManager::initCtx( |
|||
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 == SERVER_CONTEXT ? 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; |
|||
} |
|||
/**
|
|||
* @brief load the certificate credentials from file. |
|||
* |
|||
* @return true returns true is successful. |
|||
* @return false returns false if an error has occured. |
|||
*/ |
|||
bool TLSManager::prepareCredentials() |
|||
{ |
|||
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; |
|||
} |
|||
/**
|
|||
* @brief accept a TLS connection |
|||
* |
|||
* @param hSocket the TLS socket. |
|||
* @param addr incoming address. |
|||
* @param tls_ctx_server TLS server context. |
|||
* @return SSL* returns pointer to the ssl object if successful, otherwise returns NULL |
|||
*/ |
|||
SSL* TLSManager::accept(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 (TLSManager::waitFor(SSL_ACCEPT, 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; |
|||
} |
|||
/**
|
|||
* @brief Determines whether a string exists in the non-TLS address pool. |
|||
* |
|||
* @param strAddr The address. |
|||
* @param vPool Pool to search in. |
|||
* @param cs reference to the corresponding CCriticalSection. |
|||
* @return true returns true if address exists in the given pool. |
|||
* @return false returns false if address doesnt exist in the given pool. |
|||
*/ |
|||
bool TLSManager::isNonTLSAddr(const string& strAddr, const vector<NODE_ADDR>& vPool, CCriticalSection& cs) |
|||
{ |
|||
LOCK(cs); |
|||
return (find(vPool.begin(), vPool.end(), NODE_ADDR(strAddr)) != vPool.end()); |
|||
} |
|||
/**
|
|||
* @brief Removes non-TLS node addresses based on timeout. |
|||
* |
|||
* @param vPool |
|||
* @param cs |
|||
*/ |
|||
void TLSManager::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()); |
|||
} |
|||
} |
|||
|
|||
/**
|
|||
* @brief Handles send and recieve functionality in TLS Sockets. |
|||
* |
|||
* @param pnode reference to the CNode object. |
|||
* @param fdsetRecv |
|||
* @param fdsetSend |
|||
* @param fdsetError |
|||
* @return int returns -1 when socket is invalid. returns 0 otherwise. |
|||
*/ |
|||
int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fdsetSend, fd_set& fdsetError) |
|||
{ |
|||
//
|
|||
// Receive
|
|||
//
|
|||
bool recvSet = false, sendSet = false, errorSet = false; |
|||
|
|||
{ |
|||
LOCK(pnode->cs_hSocket); |
|||
|
|||
if (pnode->hSocket == INVALID_SOCKET) |
|||
return -1; |
|||
|
|||
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]; |
|||
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()); |
|||
return -1; |
|||
} |
|||
|
|||
bIsSSL = (pnode->ssl != NULL); |
|||
|
|||
if (bIsSSL) { |
|||
ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
|
|||
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)) |
|||
pnode->CloseSocketDisconnect(); |
|||
pnode->nLastRecv = GetTime(); |
|||
pnode->nRecvBytes += nBytes; |
|||
pnode->RecordBytesRecv(nBytes); |
|||
} else if (nBytes == 0) { |
|||
// socket closed gracefully (peer disconnected)
|
|||
//
|
|||
if (!pnode->fDisconnect) |
|||
LogPrint("net", "socket closed (%s)\n", pnode->addr.ToString()); |
|||
pnode->CloseSocketDisconnect(); |
|||
} else if (nBytes < 0) { |
|||
// error
|
|||
//
|
|||
if (bIsSSL) { |
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
//
|
|||
// Send
|
|||
//
|
|||
if (sendSet) { |
|||
TRY_LOCK(pnode->cs_vSend, lockSend); |
|||
if (lockSend) |
|||
SocketSendData(pnode); |
|||
} |
|||
return 0; |
|||
} |
|||
/**
|
|||
* @brief Initialization of the server and client contexts |
|||
* |
|||
* @return true returns True if successful. |
|||
* @return false returns False if an error has occured. |
|||
*/ |
|||
bool TLSManager::initialize() |
|||
{ |
|||
bool bInitializationStatus = false; |
|||
|
|||
// Initialization routines for the OpenSSL library
|
|||
//
|
|||
SSL_load_error_strings(); |
|||
ERR_load_crypto_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.string().c_str()); |
|||
|
|||
// Initialization of the server and client contexts
|
|||
//
|
|||
if ((tls_ctx_server = TLSManager::initCtx(SERVER_CONTEXT, privKeyFile, certFile, trustedDirs))) |
|||
{ |
|||
if ((tls_ctx_client = TLSManager::initCtx(CLIENT_CONTEXT, 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; |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
#include <openssl/conf.h> |
|||
#include <openssl/ssl.h> |
|||
#include <openssl/err.h> |
|||
#include "utiltls.h" |
|||
#include "tlsenums.h" |
|||
#include <boost/filesystem.hpp> |
|||
#include <boost/thread.hpp> |
|||
#include "../util.h" |
|||
#include "../protocol.h" |
|||
#include "../net.h" |
|||
#include "sync.h" |
|||
#include <boost/filesystem/path.hpp> |
|||
#include <boost/foreach.hpp> |
|||
#include <boost/signals2/signal.hpp> |
|||
#ifdef WIN32 |
|||
#include <string.h> |
|||
#else |
|||
#include <fcntl.h> |
|||
#endif |
|||
|
|||
using namespace std; |
|||
|
|||
namespace hush |
|||
{ |
|||
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) {} |
|||
bool operator==(const _NODE_ADDR b) const |
|||
{ |
|||
return (ipAddr == b.ipAddr); |
|||
} |
|||
} NODE_ADDR, *PNODE_ADDR; |
|||
|
|||
/**
|
|||
* @brief A class to wrap some of hush specific TLS functionalities used in the net.cpp |
|||
* |
|||
*/ |
|||
class TLSManager |
|||
{ |
|||
public: |
|||
int waitFor(SSLConnectionRoutine eRoutine, SOCKET hSocket, SSL* ssl, int timeoutSec); |
|||
SSL* connect(SOCKET hSocket, const CAddress& addrConnect); |
|||
SSL_CTX* initCtx( |
|||
TLSContextType ctxType, |
|||
const boost::filesystem::path& privateKeyFile, |
|||
const boost::filesystem::path& certificateFile, |
|||
const std::vector<boost::filesystem::path>& trustedDirs); |
|||
|
|||
bool prepareCredentials(); |
|||
SSL* accept(SOCKET hSocket, const CAddress& addr); |
|||
bool isNonTLSAddr(const string& strAddr, const vector<NODE_ADDR>& vPool, CCriticalSection& cs); |
|||
void cleanNonTLSPool(std::vector<NODE_ADDR>& vPool, CCriticalSection& cs); |
|||
int threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fdsetSend, fd_set& fdsetError); |
|||
bool initialize(); |
|||
}; |
|||
} |
@ -0,0 +1,494 @@ |
|||
// 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" |
|||
|
|||
namespace hush { |
|||
|
|||
// 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; |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
// 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> |
|||
namespace hush { |
|||
|
|||
#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…
Reference in new issue