diff --git a/common/thorhelper/thorsoapcall.cpp b/common/thorhelper/thorsoapcall.cpp index be9abd2b4b2..98df33c7a32 100644 --- a/common/thorhelper/thorsoapcall.cpp +++ b/common/thorhelper/thorsoapcall.cpp @@ -1242,7 +1242,10 @@ class CWSCHelper : implements IWSCHelper, public CInterface if (!ownedSC) { if (clientCert != NULL) - ownedSC.setown(createSecureSocketContextEx(clientCert->certificate, clientCert->privateKey, clientCert->passphrase, ClientSocket)); + { + Owned config = createSecureSocketConfig(clientCert->certificate, clientCert->privateKey, clientCert->passphrase); + ownedSC.setown(createSecureSocketContextEx2(config, ClientSocket)); + } else if (clientCertIssuer.length()) ownedSC.setown(createSecureSocketContextSecret(clientCertIssuer.str(), ClientSocket)); else diff --git a/esp/bindings/http/client/httpclient.cpp b/esp/bindings/http/client/httpclient.cpp index 3c0f1dd99c9..07829510c71 100644 --- a/esp/bindings/http/client/httpclient.cpp +++ b/esp/bindings/http/client/httpclient.cpp @@ -123,7 +123,7 @@ IHttpClient* CHttpClientContext::createHttpClient(const char* proxy, const char* if (xproc) m_ssctx.setown(xproc(m_config.get(),ClientSocket)); else - throw MakeStringException(-1, "procedure createSecureSocketContext can't be loaded"); + throw MakeStringException(-1, "procedure createSecureSocketContextEx2 can't be loaded"); } if(m_ssctx.get() == NULL) diff --git a/esp/clients/wsdfuaccess/wsdfuaccess.cpp b/esp/clients/wsdfuaccess/wsdfuaccess.cpp index 7850464c80f..3726aee1f95 100644 --- a/esp/clients/wsdfuaccess/wsdfuaccess.cpp +++ b/esp/clients/wsdfuaccess/wsdfuaccess.cpp @@ -534,7 +534,6 @@ StringBuffer &encodeDFUFileMeta(StringBuffer &metaInfoBlob, IPropertyTree *metaI metaInfo->serialize(metaInfoBlob); const char *keyPairName = metaInfo->queryProp("keyPairName"); // NB: in container mode, this is the name of the secret containing the cert. - const char *privateKeyFName = nullptr; Owned metaInfoEnvelope = createPTree(); #ifdef _CONTAINERIZED /* Encode the public certificate in the request. NB: this is an approach used for JWT token delegation. @@ -543,24 +542,24 @@ StringBuffer &encodeDFUFileMeta(StringBuffer &metaInfoBlob, IPropertyTree *metaI * If the size of this initial request was ever a concern, we could consider other ways to ensure a one-off * delivery of this esp public signing cert. to dafilesrv, e.g. by dafilesrv reaching out to esp to request it. */ - Owned info = getIssuerTlsServerConfig(keyPairName); - if (!info) + Owned config = getIssuerTlsSyncedConfig(keyPairName); + if (!config || !config->isValid()) throw makeStringExceptionV(-1, "encodeDFUFileMeta: No '%s' MTLS certificate detected.", keyPairName); - privateKeyFName = info->queryProp("privatekey"); - if (isEmptyString(privateKeyFName)) - throw makeStringException(-1, "encodeDFUFileMeta: MTLS - private path missing"); - const char *certPath = info->queryProp("certificate"); - verifyex(certPath); - StringBuffer certificate; - certificate.loadFile(certPath); - verifyex(certificate.length()); + + Owned info = config->getTree(); + const char *privateKeyText = info->queryProp("privatekey"); + if (isEmptyString(privateKeyText)) + throw makeStringException(-1, "encodeDFUFileMeta: MTLS - private key missing"); + const char *certificate = info->queryProp("certificate"); + verifyex(certificate); metaInfoEnvelope->setProp("certificate", certificate); + Owned privateKey = loadPrivateKeyFromMemory(privateKeyText, nullptr); #else - privateKeyFName = environment->getPrivateKeyPath(keyPairName); + const char *privateKeyFName = privateKeyFName = environment->getPrivateKeyPath(keyPairName); if (isEmptyString(privateKeyFName)) throw makeStringExceptionV(-1, "Key name '%s' is not found in environment settings: /EnvSettings/Keys/KeyPair.", keyPairName); -#endif Owned privateKey = loadPrivateKeyFromFile(privateKeyFName, nullptr); +#endif StringBuffer metaInfoSignature; digiSign(metaInfoSignature, metaInfoBlob.length(), metaInfoBlob.bytes(), *privateKey); diff --git a/esp/services/ws_dfu/ws_dfuService.cpp b/esp/services/ws_dfu/ws_dfuService.cpp index 343d9cf26e2..97412c42cbb 100644 --- a/esp/services/ws_dfu/ws_dfuService.cpp +++ b/esp/services/ws_dfu/ws_dfuService.cpp @@ -6113,8 +6113,7 @@ void CWsDfuEx::dFUFileAccessCommon(IEspContext &context, const CDfsLogicalFileNa StringBuffer dafilesrvHost; #ifdef _CONTAINERIZED keyPairName.set("signing"); - Owned info = getIssuerTlsServerConfig(keyPairName); - if (!info) + if (!hasIssuerTlsConfig(keyPairName)) throw makeStringExceptionV(-1, "dFUFileAccessCommon: file signing certificate ('%s') not defined in configuration.", keyPairName.str()); auto externalService = k8s::getDafileServiceFromConfig("stream"); @@ -6490,8 +6489,7 @@ bool CWsDfuEx::onDFUFileCreateV2(IEspContext &context, IEspDFUFileCreateV2Reques #ifdef _CONTAINERIZED keyPairName.set("signing"); - Owned info = getIssuerTlsServerConfig(keyPairName); - if (!info) + if (!hasIssuerTlsConfig(keyPairName)) throw makeStringExceptionV(-1, "onDFUFileCreateV2: file signing certificate ('%s' ) not defined in configuration.", keyPairName.str()); const char *planeName = clusterName; diff --git a/esp/test/httptest/httptest.cpp b/esp/test/httptest/httptest.cpp index 0888478912e..b72bbd55311 100644 --- a/esp/test/httptest/httptest.cpp +++ b/esp/test/httptest/httptest.cpp @@ -152,10 +152,7 @@ HttpClient::HttpClient(int threads, int times, const char* host, int port, FILE* if(use_ssl) { #ifdef _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); #else throw MakeStringException(-1, "HttpClient: failure to create SSL connection to host '%s': OpenSSL not enabled in build", host); #endif @@ -614,10 +611,7 @@ HttpServer::HttpServer(int port, const char* in, FILE* ofile, bool use_ssl, IPro if(use_ssl) { #ifdef _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ServerSocket)); - else - m_ssctx.setown(createSecureSocketContext(ServerSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ServerSocket)); #else throw MakeStringException(-1, "HttpServer: failure to create SSL socket - OpenSSL not enabled in build"); #endif @@ -1180,10 +1174,7 @@ HttpProxy::HttpProxy(int localport, const char* host, int port, FILE* ofile, boo if(use_ssl) { #if _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); #else throw MakeStringException(-1, "HttpProxy: failure to create SSL connection to host '%s': OpenSSL not enabled in build", host); #endif diff --git a/esp/tools/soapplus/http.cpp b/esp/tools/soapplus/http.cpp index fd288668ef4..ceb70c6fba8 100644 --- a/esp/tools/soapplus/http.cpp +++ b/esp/tools/soapplus/http.cpp @@ -505,10 +505,7 @@ HttpClient::HttpClient(IProperties* globals, const char* url, const char* inname if (cfg && *cfg) cfgtree.setown(createPTreeFromXMLFile(cfg)); } - if (cfgtree) - m_ssctx.setown(createSecureSocketContextEx2(cfgtree, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(cfgtree, ClientSocket)); } #else throw MakeStringException(-1, "HttpClient: failure to create SSL socket - OpenSSL not enabled in build"); diff --git a/esp/tools/soapplus/httpproxy.cpp b/esp/tools/soapplus/httpproxy.cpp index b07874d8c7f..4d9a8d303b2 100644 --- a/esp/tools/soapplus/httpproxy.cpp +++ b/esp/tools/soapplus/httpproxy.cpp @@ -582,10 +582,7 @@ HttpProxy::HttpProxy(int localport, const char* host, int port, FILE* ofile, boo if(use_ssl) { #ifdef _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); #else throw MakeStringException(-1, "HttpProxy: failure to create SSL socket - OpenSSL not enabled in build"); #endif diff --git a/fs/dafilesrv/dafilesrv.cpp b/fs/dafilesrv/dafilesrv.cpp index 0b1bbc3e5a3..9819caa666e 100644 --- a/fs/dafilesrv/dafilesrv.cpp +++ b/fs/dafilesrv/dafilesrv.cpp @@ -395,8 +395,7 @@ int main(int argc, const char* argv[]) // Use the "public" certificate issuer, unless it's visibility is "cluster" (meaning internal only) const char *visibility = getComponentConfigSP()->queryProp("service/@visibility"); const char *certScope = strsame("cluster", visibility) ? "local" : "public"; - Owned info = getIssuerTlsServerConfig(certScope); - connectMethod = info ? SSLOnly : SSLNone; + connectMethod = hasIssuerTlsConfig(certScope) ? SSLOnly : SSLNone; // NB: connectMethod will direct the CRemoteFileServer on accept to create a secure socket based on the same issuer certificates dedicatedRowServicePort = 0; // row service always runs on same secure ssl port in containerized mode diff --git a/fs/dafsclient/rmtclient.cpp b/fs/dafsclient/rmtclient.cpp index 49dfc0657d0..d6e504331f8 100644 --- a/fs/dafsclient/rmtclient.cpp +++ b/fs/dafsclient/rmtclient.cpp @@ -112,6 +112,12 @@ static class _securitySettingsClient } } + const IPropertyTree * getSecureConfig() + { + //Later: return a synced tree... + return createSecureSocketConfig(queryCertificate(), queryPrivateKey(), queryPassPhrase()); + } + protected: DAFSConnectCfg connectMethod; unsigned short daFileSrvPort; @@ -156,10 +162,10 @@ static ISecureSocket *createSecureSocket(ISocket *sock, const char *issuer) auto it = secureCtxClientIssuerMap.find(issuer); if (it == secureCtxClientIssuerMap.end()) { - Owned info = getIssuerTlsServerConfig(issuer); - if (!info) + Owned info = getIssuerTlsSyncedConfig(issuer); + if (!info || !info->isValid()) throw makeStringExceptionV(-1, "createSecureSocket() : missing MTLS configuration for issuer: %s", issuer); - secureContext.setown(createSecureSocketContextEx2(info, ClientSocket)); + secureContext.setown(createSecureSocketContextSynced(info, ClientSocket)); secureCtxClientIssuerMap.emplace(issuer, secureContext.getLink()); } else @@ -168,7 +174,10 @@ static ISecureSocket *createSecureSocket(ISocket *sock, const char *issuer) else { if (!secureContextClient) - secureContextClient.setown(createSecureSocketContextEx(securitySettings.queryCertificate(), securitySettings.queryPrivateKey(), securitySettings.queryPassPhrase(), ClientSocket)); + { + Owned config = securitySettings.getSecureConfig(); + secureContextClient.setown(createSecureSocketContextEx2(config, ClientSocket)); + } secureContext.set(secureContextClient); } } @@ -722,17 +731,8 @@ void CRemoteBase::connectSocket(SocketEndpoint &ep, unsigned connectTimeoutMs, u // The context would be 'owned' by the hook, and would expire when the mappings are removed (when removeMappedDafileSrvSecrets is called). if (storageSecret) { - Owned secretPTree = getSecret("storage", storageSecret); - if (!secretPTree) - throw makeStringExceptionV(-1, "secret %s.%s not found", "storage", storageSecret.str()); - - StringBuffer certSecretBuf; - getSecretKeyValue(certSecretBuf, secretPTree, "tls.crt"); - - StringBuffer privKeySecretBuf; - getSecretKeyValue(privKeySecretBuf, secretPTree, "tls.key"); - - Owned secureContext = createSecureSocketContextEx(certSecretBuf, privKeySecretBuf, nullptr, ClientSocket); + Owned config = createStorageTlsConfig(storageSecret, false); + Owned secureContext = createSecureSocketContextSynced(config, ClientSocket); int loglevel = SSLogNormal; #ifdef _DEBUG loglevel = SSLogMax; diff --git a/fs/dafsserver/dafsserver.cpp b/fs/dafsserver/dafsserver.cpp index c53ca376908..4184d99d80b 100644 --- a/fs/dafsserver/dafsserver.cpp +++ b/fs/dafsserver/dafsserver.cpp @@ -112,6 +112,13 @@ static class _securitySettingsServer { queryDafsSecSettings(&connectMethod, &daFileSrvPort, &daFileSrvSSLPort, &certificate, &privateKey, &passPhrase); } + + const IPropertyTree * getSecureConfig() + { + //Later: return a synced tree... + return createSecureSocketConfig(certificate, privateKey, passPhrase); + } + } securitySettings; #endif @@ -133,23 +140,23 @@ static ISecureSocket *createSecureSocket(ISocket *sock, bool disableClientCertVe */ const char *certScope = strsame("cluster", getComponentConfigSP()->queryProp("service/@visibility")) ? "local" : "public"; - Owned info = getIssuerTlsServerConfig(certScope); - if (!info) + Owned info = getIssuerTlsSyncedConfig(certScope); + if (!info || !info->isValid()) throw makeStringException(-1, "createSecureSocket() : missing MTLS configuration"); Owned cloneInfo; if (disableClientCertVerification) { - // do not insist clients provide a cerificate for verification. + // do not insist clients provide a certificate for verification. // This is used when the connection is TLS, but the authentication is done via other means // e.g. in the case of the streaming service a opaque signed blob is transmitted and must // be verified before proceeding. cloneInfo.setown(createPTreeFromIPT(info)); cloneInfo->setPropBool("verify/@enable", false); - info = cloneInfo; + info.set(cloneInfo); } - secureContextServer.setown(createSecureSocketContextEx2(info, ServerSocket)); + secureContextServer.setown(createSecureSocketContextSynced(info, ServerSocket)); #else - secureContextServer.setown(createSecureSocketContextEx(securitySettings.certificate, securitySettings.privateKey, securitySettings.passPhrase, ServerSocket)); + secureContextServer.setown(createSecureSocketContextEx2(securitySettings.getSecureConfig(), ServerSocket)); #endif } } diff --git a/roxie/ccd/ccd.hpp b/roxie/ccd/ccd.hpp index 422153cdfb8..5b3b6ca14b3 100644 --- a/roxie/ccd/ccd.hpp +++ b/roxie/ccd/ccd.hpp @@ -410,7 +410,7 @@ extern int backgroundCopyClass; extern int backgroundCopyPrio; extern unsigned roxiePort; // If listening on multiple, this is the first. Used for lock cascading -extern IPropertyTree *roxiePortTlsClientConfig; +extern ISyncedPropertyTree *roxiePortTlsClientConfig; extern unsigned udpMulticastBufferSize; diff --git a/roxie/ccd/ccdlistener.cpp b/roxie/ccd/ccdlistener.cpp index e0830b7edc9..99a48198fe8 100644 --- a/roxie/ccd/ccdlistener.cpp +++ b/roxie/ccd/ccdlistener.cpp @@ -79,7 +79,7 @@ class CascadeManager : public CInterface CriticalSection revisionCrit; int myEndpoint; const IRoxieContextLogger &logctx; - IPropertyTree *tlsConfig = nullptr; + ISyncedPropertyTree *tlsConfig = nullptr; void unlockChildren() { @@ -135,7 +135,7 @@ class CascadeManager : public CInterface assertex(sock); if (tlsConfig) { - Owned secureCtx = createSecureSocketContextEx2(tlsConfig, ClientSocket); + Owned secureCtx = createSecureSocketContextSynced(tlsConfig, ClientSocket); if (!secureCtx) throw makeStringException(ROXIE_TLS_ERROR, "Roxie CascadeManager failed creating secure context for roxie control message"); Owned ssock = secureCtx->createSecureSocket(sock.getClear()); diff --git a/roxie/ccd/ccdmain.cpp b/roxie/ccd/ccdmain.cpp index 9f20d67fc63..c0f13791c97 100644 --- a/roxie/ccd/ccdmain.cpp +++ b/roxie/ccd/ccdmain.cpp @@ -230,7 +230,7 @@ unsigned leafCacheMB = 50; unsigned blobCacheMB = 0; unsigned roxiePort = 0; -IPropertyTree *roxiePortTlsClientConfig = nullptr; +ISyncedPropertyTree *roxiePortTlsClientConfig = nullptr; #ifndef _CONTAINERIZED Owned perfMonHook; @@ -1501,7 +1501,7 @@ int CCD_API roxie_main(int argc, const char *argv[], const char * defaultYaml) { roxiePort = port; if (roxieFarm.getPropBool("@tls")) - roxiePortTlsClientConfig = createIssuerTlsClientConfig(roxieFarm.queryProp("@issuer"), roxieFarm.getPropBool("@selfSigned")); + roxiePortTlsClientConfig = createIssuerTlsConfig(roxieFarm.queryProp("@issuer"), nullptr, true, roxieFarm.getPropBool("@selfSigned"), true); debugEndpoint.set(roxiePort, ip); } bool suspended = roxieFarm.getPropBool("@suspended", false); @@ -1513,7 +1513,7 @@ int CCD_API roxie_main(int argc, const char *argv[], const char * defaultYaml) StringBuffer certFileName; StringBuffer keyFileName; StringBuffer passPhraseStr; - Owned tlsConfig; + Owned tlsConfig; if (serviceTLS) { protocol = "ssl"; diff --git a/roxie/ccd/ccdprotocol.cpp b/roxie/ccd/ccdprotocol.cpp index fe0e1be6567..2d46a4eb697 100644 --- a/roxie/ccd/ccdprotocol.cpp +++ b/roxie/ccd/ccdprotocol.cpp @@ -246,10 +246,13 @@ class ProtocolSocketListener : public ProtocolListener #ifdef _USE_OPENSSL if (isSSL) { - if (_tlsConfig) - secureContext.setown(createSecureSocketContextEx2(_tlsConfig, ServerSocket)); - else - secureContext.setown(createSecureSocketContextEx(certFile.get(), keyFile.get(), passPhrase.get(), ServerSocket)); + Owned config; + if (!_tlsConfig) + { + config.setown(createSecureSocketConfig(certFile.get(), keyFile.get(), passPhrase.get())); + _tlsConfig = config; + } + secureContext.setown(createSecureSocketContextEx2(_tlsConfig, ServerSocket)); } #endif } diff --git a/system/jlib/jptree.cpp b/system/jlib/jptree.cpp index 4d37725d7f8..0fddd3560b8 100644 --- a/system/jlib/jptree.cpp +++ b/system/jlib/jptree.cpp @@ -10130,3 +10130,88 @@ void setExpertOpt(const char *opt, const char *value) getExpertOptPath(opt, xpath.clear()); config->setProp(xpath, value); } + +//--------------------------------------------------------------------------------------------------------------------- + +//More: Should this move inside PTree? It may allow a more efficient hash caclulaton. +unsigned getPropertyTreeHash(const IPropertyTree & source, unsigned hashcode) +{ + if (source.isBinary()) + { + MemoryBuffer mb; + source.getPropBin(nullptr, mb); + hashcode = hashc((const byte *)mb.bufferBase(), mb.length(), hashcode); + } + else + { + const char * value = source.queryProp(nullptr); + if (value) + hashcode = hashcz((const byte *)value, hashcode); + } + + Owned aiter = source.getAttributes(); + ForEach(*aiter) + { + hashcode = hashcz((const byte *)aiter->queryName(), hashcode); + hashcode = hashcz((const byte *)aiter->queryValue(), hashcode); + } + + Owned iter = source.getElements("*"); + ForEach(*iter) + { + IPropertyTree & child = iter->query(); + hashcode = hashcz((const byte *)child.queryName(), hashcode); + hashcode = getPropertyTreeHash(child, hashcode); + } + return hashcode; +} + +class UnchangingPropertyTree : extends CInterfaceOf +{ +public: + UnchangingPropertyTree(IPropertyTree * _tree) : tree(_tree) + { + } + + virtual const IPropertyTree * getTree() const override + { + return LINK(tree); + } + + virtual bool getProp(MemoryBuffer & result, const char * xpath) const override + { + if (!tree) + return false; + return tree->getPropBin(xpath, result); + } + + virtual bool getProp(StringBuffer & result, const char * xpath) const override + { + if (!tree) + return false; + return tree->getProp(xpath, result); + } + + virtual unsigned getVersion() const override + { + return 0; + } + + virtual bool isStale() const override + { + return false; + } + + virtual bool isValid() const override + { + return tree != nullptr; + } + +protected: + Linked tree; +}; + +ISyncedPropertyTree * createSyncedPropertyTree(IPropertyTree * tree) +{ + return new UnchangingPropertyTree(tree); +} diff --git a/system/jlib/jptree.hpp b/system/jlib/jptree.hpp index 32885b51ffe..f8d643cd805 100644 --- a/system/jlib/jptree.hpp +++ b/system/jlib/jptree.hpp @@ -429,4 +429,26 @@ extern jlib_decl StringBuffer &getExpertOptString(const char *opt, StringBuffer extern jlib_decl void setExpertOpt(const char *opt, const char *value); +//--------------------------------------------------------------------------------------------------------------------- + +extern jlib_decl unsigned getPropertyTreeHash(const IPropertyTree & source, unsigned hashcode); + +//Interface for encapsulating an IPropertyTree that can be atomically updated. The result of getTree() is guaranteed +//to not be modified and to remain vali and consistent until it is released. +interface ISyncedPropertyTree : extends IInterface +{ + virtual const IPropertyTree * getTree() const = 0; + virtual bool getProp(MemoryBuffer & result, const char * xpath) const = 0; + virtual bool getProp(StringBuffer & result, const char * xpath) const = 0; + //Return a sequence number which changes whenever the secret actually changes - so that a caller can determine + //whether it needs to reload the certificates. + virtual unsigned getVersion() const = 0; + //An indication that the property tree may be out of date because it couldn'tt be resynchronized. + virtual bool isStale() const = 0; + virtual bool isValid() const = 0; // Was an entry found for this secret? +}; + +extern jlib_decl ISyncedPropertyTree * createSyncedPropertyTree(IPropertyTree * tree); + + #endif diff --git a/system/jlib/jsecrets.cpp b/system/jlib/jsecrets.cpp index b99e6265c7c..3d761b59c55 100644 --- a/system/jlib/jsecrets.cpp +++ b/system/jlib/jsecrets.cpp @@ -72,7 +72,7 @@ interface IVaultManager : extends IInterface static CriticalSection secretCacheCS; static Owned secretCache; static CriticalSection mtlsInfoCacheCS; -static Owned mtlsInfoCache; +static std::unordered_map> mtlsInfoCache; static Owned vaultManager; static MemoryAttr udpKey; static bool udpKeyInitialized = false; @@ -80,7 +80,6 @@ static bool udpKeyInitialized = false; MODULE_INIT(INIT_PRIORITY_SYSTEM) { secretCache.setown(createPTree()); - mtlsInfoCache.setown(createPTree()); return true; } @@ -1136,41 +1135,17 @@ IPropertyTree *getSecret(const char *category, const char * name, const char * o bool getSecretKeyValue(MemoryBuffer & result, const IPropertyTree *secret, const char * key) { validateKeyName(key); - if (!secret) return false; - - IPropertyTree *tree = secret->queryPropTree(key); - if (tree) - return tree->getPropBin(nullptr, result); - return false; + return secret->getPropBin(key, result); } bool getSecretKeyValue(StringBuffer & result, const IPropertyTree *secret, const char * key) { validateKeyName(key); - if (!secret) return false; - - IPropertyTree *tree = secret->queryPropTree(key); - if (!tree) - return false; - if (tree->isBinary(nullptr)) - { - MemoryBuffer mb; - tree->getPropBin(nullptr, mb); - //caller implies it's a string - result.append(mb.length(), mb.toByteArray()); - return true; - } - const char *value = tree->queryProp(nullptr); - if (value) - { - result.append(value); - return true; - } - return false; + return secret->getProp(key, result); } extern jlib_decl bool getSecretValue(StringBuffer & result, const char *category, const char * name, const char * key, bool required) @@ -1186,7 +1161,7 @@ extern jlib_decl bool getSecretValue(StringBuffer & result, const char *category //--------------------------------------------------------------------------------------------------------------------- -class CSecret final : public CInterfaceOf +class CSecret final : public CInterfaceOf { public: CSecret(const char *_category, const char * _name, const char * _vaultId, const char * _version, const IPropertyTree * _secret) @@ -1195,28 +1170,33 @@ class CSecret final : public CInterfaceOf updateHash(); } - virtual const IPropertyTree * getTree() const; + virtual const IPropertyTree * getTree() const override; - virtual bool getKeyValue(MemoryBuffer & result, const char * key) const + virtual bool getProp(MemoryBuffer & result, const char * key) const override { CriticalBlock block(secretCs); checkStale(); return getSecretKeyValue(result, secret, key); } - virtual bool getKeyValue(StringBuffer & result, const char * key) const + virtual bool getProp(StringBuffer & result, const char * key) const override { CriticalBlock block(secretCs); checkStale(); return getSecretKeyValue(result, secret, key); } - virtual bool isStale() const + virtual bool isStale() const override { return secret && hasCacheExpired(secret); } - virtual unsigned getVersion() const + virtual unsigned getVersion() const override { return secretHash; } + virtual bool isValid() const override + { + CriticalBlock block(secretCs); + return secret != nullptr; + } protected: void checkStale() const; @@ -1259,48 +1239,15 @@ void CSecret::checkStale() const } } -//This should probably move to jptree.?pp as a generally useful function -static unsigned calculateTreeHash(const IPropertyTree & source, unsigned hashcode) -{ - if (source.isBinary()) - { - MemoryBuffer mb; - source.getPropBin(nullptr, mb); - hashcode = hashc((const byte *)mb.bufferBase(), mb.length(), hashcode); - } - else - { - const char * value = source.queryProp(nullptr); - if (value) - hashcode = hashcz((const byte *)value, hashcode); - } - - Owned aiter = source.getAttributes(); - ForEach(*aiter) - { - hashcode = hashcz((const byte *)aiter->queryName(), hashcode); - hashcode = hashcz((const byte *)aiter->queryValue(), hashcode); - } - - Owned iter = source.getElements("*"); - ForEach(*iter) - { - IPropertyTree & child = iter->query(); - hashcode = hashcz((const byte *)child.queryName(), hashcode); - hashcode = calculateTreeHash(child, hashcode); - } - return hashcode; -} - void CSecret::updateHash() const { if (secret) - secretHash = calculateTreeHash(*secret.get(), 0x811C9DC5); + secretHash = getPropertyTreeHash(*secret.get(), 0x811C9DC5); else secretHash = 0; } -ISecret * resolveSecret(const char *category, const char * name, const char * optVaultId, const char * optVersion) +ISyncedPropertyTree * resolveSecret(const char *category, const char * name, const char * optVaultId, const char * optVersion) { Owned resolved = getSecret(category, name, optVaultId, optVersion); return new CSecret(category, name, optVaultId, optVersion, resolved); @@ -1364,106 +1311,240 @@ jlib_decl bool containsEmbeddedKey(const char *certificate) return false; } -IPropertyTree *createIssuerTlsClientConfig(const char *issuer, bool acceptSelfSigned, bool addCACert) + +//--------------------------------------------------------------------------------------------------------------------- + +class CSyncedCertificateBase : public CInterfaceOf { - if (isEmptyString(issuer)) - return nullptr; +public: + CSyncedCertificateBase(const char *_issuer) + : issuer(_issuer) + { + } - StringBuffer filepath; - StringBuffer secretpath; - buildSecretPath(secretpath, "certificates", issuer); + virtual const IPropertyTree * getTree() const override final; - Owned info = createPTree(); + virtual bool getProp(MemoryBuffer & result, const char * key) const override final + { + CriticalBlock block(secretCs); + checkStale(); + return getSecretKeyValue(result, config, key); + } + virtual bool getProp(StringBuffer & result, const char * key) const override final + { + CriticalBlock block(secretCs); + checkStale(); + return getSecretKeyValue(result, config, key); + } + virtual bool isStale() const override final + { + return secret->isStale(); + } + virtual bool isValid() const override + { + return secret->isValid(); - if (strieq(issuer, "remote")||strieq(issuer, "local")) + } + virtual unsigned getVersion() const override final { - filepath.set(secretpath).append("tls.crt"); - if (!checkFileExists(filepath)) - return nullptr; + //If information that is combined with the secret (e.g. trusted peers) can also change dynamically this would + //need to be a separate hash calculated from the condig tree + return secretHash; + } + +protected: + virtual void updateConfigFromSecret(const IPropertyTree * secretInfo) const = 0; + +protected: + void checkStale() const; + void createConfig() const; + void createDefaultConfigFromSecret(const IPropertyTree * secretInfo, bool addCertificates, bool addCertificateAuthority) const; + void updateCertificateFromSecret(const IPropertyTree * secretInfo) const; + void updateCertificateAuthorityFromSecret(const IPropertyTree * secretInfo) const; + +protected: + StringAttr issuer; + Owned secret; + mutable CriticalSection secretCs; + mutable Linked config; + mutable std::atomic secretHash{0}; +}; + + +const IPropertyTree * CSyncedCertificateBase::getTree() const +{ + CriticalBlock block(secretCs); + checkStale(); + return LINK(config); +} + +void CSyncedCertificateBase::checkStale() const +{ + CriticalBlock block(secretCs); + if (secretHash != secret->getVersion()) + createConfig(); +} - info->setProp("certificate", filepath.str()); - filepath.set(secretpath).append("tls.key"); - if (checkFileExists(filepath)) - info->setProp("privatekey", filepath.str()); +void CSyncedCertificateBase::createConfig() const +{ + //Update before getting the tree to avoid potential race condition updating the tree at the same time. + //Could alternatively return the version number from the getTree() call. + secretHash = secret->getVersion(); + + Owned secretInfo = secret->getTree(); + if (secretInfo) + { + config.setown(createPTree(issuer)); + ensurePTree(config, "verify"); + updateConfigFromSecret(secretInfo); } + else + config.clear(); +} - IPropertyTree *verify = ensurePTree(info, "verify"); - if (addCACert) + +void CSyncedCertificateBase::updateCertificateFromSecret(const IPropertyTree * secretInfo) const +{ + StringBuffer value; + config->setProp("@issuer", issuer); // server only? + if (secretInfo->getProp("tls.crt", value.clear())) + config->setProp("certificate", value.str()); + if (secretInfo->getProp("tls.key", value.clear())) + config->setProp("privatekey", value.str()); +} + +void CSyncedCertificateBase::updateCertificateAuthorityFromSecret(const IPropertyTree * secretInfo) const +{ + StringBuffer value; + if (secretInfo->getProp("ca.crt", value.clear())) { - filepath.set(secretpath).append("ca.crt"); - if (checkFileExists(filepath)) - { - IPropertyTree *ca = ensurePTree(verify, "ca_certificates"); - ca->setProp("@path", filepath.str()); - } + IPropertyTree *verify = config->queryPropTree("verify"); + IPropertyTree *ca = ensurePTree(verify, "ca_certificates"); + ca->setProp("pem", value.str()); + } +} + + +//--------------------------------------------------------------------------------------------------------------------- + +class CIssuerConfig final : public CSyncedCertificateBase +{ +public: + CIssuerConfig(const char *_issuer, const char * _trustedPeers, bool _isClientConnection, bool _acceptSelfSigned, bool _addCACert) + : CSyncedCertificateBase(_issuer), trustedPeers(_trustedPeers), isClientConnection(_isClientConnection), acceptSelfSigned(_acceptSelfSigned), addCACert(_addCACert) + { + secret.setown(resolveSecret("certificates", issuer, nullptr, nullptr)); + createConfig(); } - verify->setPropBool("@enable", true); + + virtual void updateConfigFromSecret(const IPropertyTree * secretInfo) const override; + +protected: + StringAttr trustedPeers; + bool isClientConnection; // required in constructor + bool acceptSelfSigned; // required in constructor + bool addCACert; // required in constructor +}; + + +void CIssuerConfig::updateConfigFromSecret(const IPropertyTree * secretInfo) const +{ + if (!isClientConnection || !strieq(issuer, "public")) + updateCertificateFromSecret(secretInfo); + + if (!isClientConnection || addCACert) // same condition as before, but is it correct? + updateCertificateAuthorityFromSecret(secretInfo); + + IPropertyTree *verify = config->queryPropTree("verify"); + //For now only the "public" issuer implies client certificates are not required + verify->setPropBool("@enable", isClientConnection || !strieq(issuer, "public")); verify->setPropBool("@address_match", false); - verify->setPropBool("@accept_selfsigned", acceptSelfSigned); - verify->setProp("trusted_peers", "anyone"); + verify->setPropBool("@accept_selfsigned", isClientConnection && acceptSelfSigned); + if (trustedPeers) // Allow blank string to mean none, null means anyone + verify->setProp("trusted_peers", trustedPeers); + else + verify->setProp("trusted_peers", "anyone"); +} + + +ISyncedPropertyTree * createIssuerTlsConfig(const char * issuer, const char * optTrustedPeers, bool isClientConnection, bool acceptSelfSigned, bool addCACert) +{ + return new CIssuerConfig(issuer, optTrustedPeers, isClientConnection, acceptSelfSigned, addCACert); - return info.getClear(); } +//--------------------------------------------------------------------------------------------------------------------- -IPropertyTree *getIssuerTlsServerConfig(const char *name) +class CCertificateConfig final : public CSyncedCertificateBase { - if (isEmptyString(name)) - return nullptr; +public: + CCertificateConfig(const char * _category, const char * _secretName, bool _addCACert) + : CSyncedCertificateBase(nullptr), addCACert(_addCACert) + { + secret.setown(resolveSecret(_category, _secretName, nullptr, nullptr)); + if (!secret->isValid()) + throw makeStringExceptionV(-1, "secret %s.%s not found", _category, _secretName); + createConfig(); + } - validateSecretName(name); + virtual void updateConfigFromSecret(const IPropertyTree * secretInfo) const override; - CriticalBlock block(mtlsInfoCacheCS); - Owned info = mtlsInfoCache->getPropTree(name); - if (info) - return info.getClear(); +protected: + bool addCACert; // required in constructor +}; + +void CCertificateConfig::updateConfigFromSecret(const IPropertyTree * secretInfo) const +{ + updateCertificateFromSecret(secretInfo); + + if (addCACert) + updateCertificateAuthorityFromSecret(secretInfo); +} - StringBuffer filepath; - StringBuffer secretpath; - buildSecretPath(secretpath, "certificates", name); +ISyncedPropertyTree * createStorageTlsConfig(const char * secretName, bool addCACert) +{ + return new CCertificateConfig("storage", secretName, addCACert); + +} - filepath.set(secretpath).append("tls.crt"); - if (!checkFileExists(filepath)) + +const ISyncedPropertyTree * getIssuerTlsSyncedConfig(const char * issuer, const char * optTrustedPeers) +{ + if (isEmptyString(issuer)) return nullptr; - info.set(mtlsInfoCache->setPropTree(name)); - info->setProp("@issuer", name); - info->setProp("certificate", filepath.str()); - filepath.set(secretpath).append("tls.key"); - if (checkFileExists(filepath)) - info->setProp("privatekey", filepath.str()); - IPropertyTree *verify = ensurePTree(info, "verify"); - if (verify) - { - filepath.set(secretpath).append("ca.crt"); - if (checkFileExists(filepath)) - { - IPropertyTree *ca = ensurePTree(verify, "ca_certificates"); - if (ca) - ca->setProp("@path", filepath.str()); - } - //For now only the "public" issuer implies client certificates are not required - verify->setPropBool("@enable", !strieq(name, "public")); - verify->setPropBool("@address_match", false); - verify->setPropBool("@accept_selfsigned", false); - verify->setProp("trusted_peers", "anyone"); + const char * key; + StringBuffer temp; + if (!isEmptyString(optTrustedPeers)) + { + temp.append(issuer).append("/").append(optTrustedPeers); + key = temp.str(); } - return info.getClear(); + else + key = issuer; + + CriticalBlock block(mtlsInfoCacheCS); + auto match = mtlsInfoCache.find(key); + if (match != mtlsInfoCache.cend()) + return LINK(match->second); + + Owned config = createIssuerTlsConfig(issuer, optTrustedPeers, false, false, true); + mtlsInfoCache.emplace(key, config); + return config.getClear(); } -IPropertyTree *getIssuerTlsServerConfigWithTrustedPeers(const char *issuer, const char *trusted_peers) +bool hasIssuerTlsConfig(const char *issuer) { - Owned issuerConfig = getIssuerTlsServerConfig(issuer); - if (!issuerConfig || isEmptyString(trusted_peers)) - return issuerConfig.getClear(); - //TBD: might cache in the future, but needs thought, lookup must include trusted_peers, but will there be cases where trusted_peers can change dynamically? - Owned tlsConfig = createPTreeFromIPT(issuerConfig); - if (!tlsConfig) - return nullptr; + Owned match = getIssuerTlsSyncedConfig(issuer, nullptr); + return match && match->isValid(); +} - IPropertyTree *verify = ensurePTree(tlsConfig, "verify"); - verify->setProp("trusted_peers", trusted_peers); - return tlsConfig.getClear(); +const IPropertyTree *getIssuerTlsServerConfigWithTrustedPeers(const char *issuer, const char *trusted_peers) +{ + Owned match = getIssuerTlsSyncedConfig(issuer, trusted_peers); + if (!match) + return nullptr; + return match->getTree(); } enum UseMTLS { UNINIT, DISABLED, ENABLED }; @@ -1497,21 +1578,18 @@ jlib_decl bool queryMtls() if (checkFileExists(cert) && checkFileExists(privKey)) { CriticalBlock block(mtlsInfoCacheCS); - if (mtlsInfoCache) - { - IPropertyTree *info = mtlsInfoCache->queryPropTree("local"); - if (!info) - info = mtlsInfoCache->setPropTree("local"); - if (info) - { // always update - info->setProp("certificate", cert); - info->setProp("privatekey", privKey); - if ( (!isEmptyString(pubKey)) && (checkFileExists(pubKey)) ) - info->setProp("publickey", pubKey); - if (!isEmptyString(passPhrase)) - info->setProp("passphrase", passPhrase); // encrypted - } - } + assertex(mtlsInfoCache.find("local") == mtlsInfoCache.cend()); + + Owned info = createPTree("local"); + info->setProp("certificate", cert); + info->setProp("privatekey", privKey); + if ( (!isEmptyString(pubKey)) && (checkFileExists(pubKey)) ) + info->setProp("publickey", pubKey); + if (!isEmptyString(passPhrase)) + info->setProp("passphrase", passPhrase); // encrypted + + Owned entry = createSyncedPropertyTree(info); + mtlsInfoCache.emplace("local", entry); } } } diff --git a/system/jlib/jsecrets.hpp b/system/jlib/jsecrets.hpp index 81bada07763..1172e453c54 100644 --- a/system/jlib/jsecrets.hpp +++ b/system/jlib/jsecrets.hpp @@ -22,22 +22,13 @@ #include "jlib.hpp" #include "jstring.hpp" -interface ISecret : extends IInterface -{ - virtual const IPropertyTree * getTree() const = 0; - virtual bool getKeyValue(MemoryBuffer & result, const char * key) const = 0; - virtual bool getKeyValue(StringBuffer & result, const char * key) const = 0; - virtual bool isStale() const = 0; - //Return a sequence number which changes whenever the secret actually changes - so that a caller can determine - //whether it needs to reload the certificates. - virtual unsigned getVersion() const = 0; -}; +interface ISyncedPropertyTree; extern jlib_decl void setSecretMount(const char * path); extern jlib_decl void setSecretTimeout(unsigned timeoutMs); extern jlib_decl IPropertyTree *getSecret(const char *category, const char * name, const char * optVaultId = nullptr, const char * optVersion = nullptr); -extern jlib_decl ISecret * resolveSecret(const char *category, const char * name, const char * optRequiredVault); +extern jlib_decl ISyncedPropertyTree * resolveSecret(const char *category, const char * name, const char * optRequiredVault); extern jlib_decl bool getSecretKeyValue(MemoryBuffer & result, const IPropertyTree *secret, const char * key); extern jlib_decl bool getSecretKeyValue(StringBuffer & result, const IPropertyTree *secret, const char * key); @@ -48,11 +39,13 @@ extern jlib_decl const MemoryAttr &getSecretUdpKey(bool required); extern jlib_decl bool containsEmbeddedKey(const char *certificate); -//getIssuerTlsServerConfig must return owned because the internal cache could be updated internally and the return will become invalid, so must be linked -extern jlib_decl IPropertyTree *getIssuerTlsServerConfig(const char *issuer); -extern jlib_decl IPropertyTree *getIssuerTlsServerConfigWithTrustedPeers(const char *issuer, const char *trusted_peers); +//getIssuerTlsConfig must return owned because the internal cache could be updated internally and the return will become invalid, so must be linked +extern jlib_decl const ISyncedPropertyTree * getIssuerTlsSyncedConfig(const char * issuer, const char * optTrustedPeers = nullptr); +extern jlib_decl bool hasIssuerTlsConfig(const char *issuer); +extern jlib_decl const IPropertyTree *getIssuerTlsServerConfigWithTrustedPeers(const char *issuer, const char *trusted_peers); -extern jlib_decl IPropertyTree *createIssuerTlsClientConfig(const char *issuer, bool acceptSelfSigned, bool addCACert=true); +extern jlib_decl ISyncedPropertyTree * createIssuerTlsConfig(const char * issuer, const char * optTrustedPeers, bool isClientConnection, bool acceptSelfSigned, bool addCACert); +extern jlib_decl ISyncedPropertyTree * createStorageTlsConfig(const char * secretName, bool addCACert); extern jlib_decl void splitFullUrl(const char *url, bool &https, StringBuffer &user, StringBuffer &password, StringBuffer &host, StringBuffer &port, StringBuffer &fullpath); extern jlib_decl void splitUrlSchemeHostPort(const char *url, StringBuffer &user, StringBuffer &password, StringBuffer &schemeHostPort, StringBuffer &path); diff --git a/system/jlib/jsmartsock.cpp b/system/jlib/jsmartsock.cpp index f7f0b4190dd..1bfddd7d9fb 100644 --- a/system/jlib/jsmartsock.cpp +++ b/system/jlib/jsmartsock.cpp @@ -218,7 +218,7 @@ CSmartSocketFactory::CSmartSocketFactory(IPropertyTree &service, bool _retry, un tlsService = service.getPropBool("@tls"); issuer.set(service.queryProp("@issuer")); if (tlsService) - tlsConfig.setown(createIssuerTlsClientConfig(issuer, service.getPropBool("@selfSigned"), service.getPropBool("@caCert"))); + tlsConfig.setown(createIssuerTlsConfig(issuer, nullptr, true, service.getPropBool("@selfSigned"), service.getPropBool("@caCert"))); StringBuffer s; s.append(name).append(':').append(port); diff --git a/system/jlib/jsmartsock.hpp b/system/jlib/jsmartsock.hpp index 7e5e21e4c3a..e5139364ef2 100644 --- a/system/jlib/jsmartsock.hpp +++ b/system/jlib/jsmartsock.hpp @@ -37,7 +37,7 @@ interface jlib_decl ISmartSocket : extends IInterface virtual void close() = 0; }; - +interface ISyncedPropertyTree; interface jlib_decl ISmartSocketFactory : extends IInterface { virtual int run()=0; @@ -59,7 +59,7 @@ interface jlib_decl ISmartSocketFactory : extends IInterface virtual StringBuffer & getUrlStr(StringBuffer &str, bool useHostName) = 0; virtual bool isTlsService() const = 0; - virtual const IPropertyTree *queryTlsConfig() const = 0; + virtual const ISyncedPropertyTree *queryTlsConfig() const = 0; }; diff --git a/system/jlib/jsmartsock.ipp b/system/jlib/jsmartsock.ipp index e89c7850d1c..0aa065f95f1 100644 --- a/system/jlib/jsmartsock.ipp +++ b/system/jlib/jsmartsock.ipp @@ -68,7 +68,7 @@ protected: unsigned nextEndpointIndex; bool retry; bool tlsService = false; - Owned tlsConfig; + Owned tlsConfig; StringAttr issuer; unsigned retryInterval; @@ -104,7 +104,7 @@ public: virtual StringBuffer & getUrlStr(StringBuffer &str, bool useHostName); virtual bool isTlsService() const override { return tlsService; } - virtual const IPropertyTree *queryTlsConfig() const { return tlsConfig; }; + virtual const ISyncedPropertyTree *queryTlsConfig() const { return tlsConfig; }; const char *queryTlsIssuer() const { return issuer.str(); } }; diff --git a/system/security/securesocket/securesocket.cpp b/system/security/securesocket/securesocket.cpp index 2b416916d7d..36710efc161 100644 --- a/system/security/securesocket/securesocket.cpp +++ b/system/security/securesocket/securesocket.cpp @@ -97,6 +97,13 @@ static void readBio(BIO* bio, StringBuffer& buf) } } + +interface ISecureSocketContextCallback : implements IInterface +{ + virtual unsigned getVersion() = 0; // Check the version of the context to see if the SSL context needs to be recreated + virtual SSL * createActiveSSL() = 0; // Must be called after getVersion() +}; + //Use a namespace to prevent clashes with a class of the same name in jhtree namespace securesocket { @@ -137,6 +144,7 @@ class CSecureSocket : implements ISecureSocket, public CInterface { private: SSL* m_ssl; + Linked contextCallback; Owned m_socket; bool m_verify; bool m_address_match; @@ -147,6 +155,7 @@ class CSecureSocket : implements ISecureSocket, public CInterface size32_t nextblocksize = 0; unsigned blockflags = BF_ASYNC_TRANSFER; unsigned blocktimeoutms = WAIT_FOREVER; + unsigned contextVersion; #ifdef USERECVSEM static Semaphore receiveblocksem; bool receiveblocksemowned; // owned by this socket @@ -158,8 +167,7 @@ class CSecureSocket : implements ISecureSocket, public CInterface public: IMPLEMENT_IINTERFACE; - CSecureSocket(ISocket* sock, SSL_CTX* ctx, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); - CSecureSocket(int sockfd, SSL_CTX* ctx, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); + CSecureSocket(ISocket* sock, int sockfd, ISecureSocketContextCallback * callback, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); ~CSecureSocket(); virtual int secure_accept(int logLevel); @@ -173,6 +181,21 @@ class CSecureSocket : implements ISecureSocket, public CInterface virtual size32_t writetms(void const* buf, size32_t size, unsigned timeoutms=WAIT_FOREVER); void readTimeout(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeout, bool useSeconds); + void checkForUpdatedContext() + { + //Check if a new ssl context should be created. + //No need for a critical section because the socket functions are never accessed by multiple threads at the same time + //It is possible that createActiveSSL() may be for a later version - but that will only mean that the same context + //is recreated when the version number is seen to have chanegd. + unsigned activeVersion = contextCallback->getVersion(); + if (activeVersion != contextVersion) + { + contextVersion = activeVersion; + SSL_free(m_ssl); + m_ssl = contextCallback->createActiveSSL(); + } + } + virtual StringBuffer& get_ssl_version(StringBuffer& ver) { @@ -467,38 +490,14 @@ Semaphore CSecureSocket::receiveblocksem(2); /************************************************************************** * CSecureSocket -- secure socket layer implementation using openssl * **************************************************************************/ -CSecureSocket::CSecureSocket(ISocket* sock, SSL_CTX* ctx, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) +CSecureSocket::CSecureSocket(ISocket* sock, int sockfd, ISecureSocketContextCallback * callback, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) + : contextCallback(callback) { + if (sock) + sockfd = sock->OShandle(); m_socket.setown(sock); - m_ssl = SSL_new(ctx); - - m_verify = verify; - m_address_match = address_match; - m_peers = peers;; - m_loglevel = loglevel; - m_isSecure = false; - - if(m_ssl == NULL) - { - throw MakeStringException(-1, "Can't create ssl"); - } - - // there is no MSG_NOSIGNAL or SO_NOSIGPIPE for SSL_write() ... -#ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); -#endif - - SSL_set_fd(m_ssl, sock->OShandle()); - - if (fqdn) - m_fqdn.set(fqdn); -} - -CSecureSocket::CSecureSocket(int sockfd, SSL_CTX* ctx, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) -{ - //m_socket.setown(sock); - //m_socket.setown(ISocket::attach(sockfd)); - m_ssl = SSL_new(ctx); + contextVersion = callback->getVersion(); + m_ssl = callback->createActiveSSL(); m_verify = verify; m_address_match = address_match; @@ -652,6 +651,7 @@ bool CSecureSocket::verify_cert(X509* cert) int CSecureSocket::secure_accept(int logLevel) { + checkForUpdatedContext(); int err; err = SSL_accept(m_ssl); if(err == 0) @@ -1268,9 +1268,10 @@ static bool setVerifyCertsPEMBuffer(SSL_CTX *ctx, const char *caCertBuf, int caC return true; } -class CSecureSocketContext : public CInterfaceOf +class CSecureSocketContext : implements ISecureSocketContext, implements ISecureSocketContextCallback, public CInterface { private: + SecureSocketType sockettype; OwnedSSLCTX m_ctx; #if (OPENSSL_VERSION_NUMBER > 0x00909000L) const SSL_METHOD* m_meth = nullptr; @@ -1282,6 +1283,9 @@ class CSecureSocketContext : public CInterfaceOf bool m_address_match = false; Owned m_peers; StringAttr password; + CriticalSection cs; + Owned syncedConfig; + unsigned configVersion = 0; void setSessionIdContext() { @@ -1352,129 +1356,156 @@ class CSecureSocketContext : public CInterfaceOf throw makeStringExceptionV(-1, "Error loading CA certificates from %s", caCertsPathOrBuf); } -public: - CSecureSocketContext(SecureSocketType sockettype) + void createNewContext(const IPropertyTree* config) { initContext(sockettype); - SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); - } - - CSecureSocketContext(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase, SecureSocketType sockettype) - { - initContext(sockettype); - - // MCK TODO: should we set a default cipherList, as is done in other ctor (below) ? - - password.set(passphrase); - SSL_CTX_set_default_passwd_cb_userdata(m_ctx, (void*)password.str()); - SSL_CTX_set_default_passwd_cb(m_ctx, pem_passwd_cb); - - setCertificate(certFileOrBuf); - setPrivateKey(privKeyFileOrBuf); - - SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); - } - - CSecureSocketContext(const IPropertyTree* config, SecureSocketType sockettype) - { - assertex(config); - - initContext(sockettype); - - const char *cipherList = config->queryProp("cipherList"); - if (!cipherList || !*cipherList) - cipherList = "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5"; - SSL_CTX_set_cipher_list(m_ctx, cipherList); - - const char* passphrase = config->queryProp("passphrase"); - if (passphrase && *passphrase) + if (config) { - StringBuffer pwd; - decrypt(pwd, passphrase); - password.set(pwd); - SSL_CTX_set_default_passwd_cb_userdata(m_ctx, (void*)password.str()); - SSL_CTX_set_default_passwd_cb(m_ctx, pem_passwd_cb); - } + const char *cipherList = config->queryProp("cipherList"); + if (!cipherList || !*cipherList) + cipherList = "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5"; + SSL_CTX_set_cipher_list(m_ctx, cipherList); - const char *certFileOrBuf = config->queryProp("certificate_pem"); - if (!certFileOrBuf) - certFileOrBuf = config->queryProp("certificate"); - if (certFileOrBuf && *certFileOrBuf) - setCertificate(certFileOrBuf); + const char* passphrase = config->queryProp("passphrase"); + if (passphrase && *passphrase) + { + StringBuffer pwd; + decrypt(pwd, passphrase); + password.set(pwd); + SSL_CTX_set_default_passwd_cb_userdata(m_ctx, (void*)password.str()); + SSL_CTX_set_default_passwd_cb(m_ctx, pem_passwd_cb); + } - const char *privKeyFileOrBuf = config->queryProp("privatekey_pem"); - if (!privKeyFileOrBuf) - privKeyFileOrBuf = config->queryProp("privatekey"); - if (privKeyFileOrBuf && *privKeyFileOrBuf) - setPrivateKey(privKeyFileOrBuf); + const char *certFileOrBuf = config->queryProp("certificate_pem"); + if (!certFileOrBuf) + certFileOrBuf = config->queryProp("certificate"); + if (certFileOrBuf && *certFileOrBuf) + setCertificate(certFileOrBuf); - SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); + const char *privKeyFileOrBuf = config->queryProp("privatekey_pem"); + if (!privKeyFileOrBuf) + privKeyFileOrBuf = config->queryProp("privatekey"); + if (privKeyFileOrBuf && *privKeyFileOrBuf) + setPrivateKey(privKeyFileOrBuf); - m_verify = config->getPropBool("verify/@enable"); - m_address_match = config->getPropBool("verify/@address_match"); + m_verify = config->getPropBool("verify/@enable"); + m_address_match = config->getPropBool("verify/@address_match"); - if(m_verify) - { - const char *caCertPathOrBuf = config->queryProp("verify/ca_certificates/pem"); - if (!caCertPathOrBuf) - caCertPathOrBuf = config->queryProp("verify/ca_certificates/@path"); - if (caCertPathOrBuf && *caCertPathOrBuf) - setVerifyCerts(caCertPathOrBuf); - - bool acceptSelfSigned = config->getPropBool("verify/@accept_selfsigned"); - SSL_CTX_set_verify(m_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE, (acceptSelfSigned) ? verify_callback_allow_selfSigned : verify_callback_reject_selfSigned); - - m_peers.setown(new CStringSet()); - const char* peersstr = config->queryProp("verify/trusted_peers"); - while(peersstr && *peersstr) + if(m_verify) { - StringBuffer onepeerbuf; - peersstr = strtok__(peersstr, "|", onepeerbuf); - if(onepeerbuf.length() == 0) - break; - - char* onepeer = onepeerbuf.detach(); - if (isdigit(*onepeer)) + const char *caCertPathOrBuf = config->queryProp("verify/ca_certificates/pem"); + if (!caCertPathOrBuf) + caCertPathOrBuf = config->queryProp("verify/ca_certificates/@path"); + if (caCertPathOrBuf && *caCertPathOrBuf) + setVerifyCerts(caCertPathOrBuf); + + bool acceptSelfSigned = config->getPropBool("verify/@accept_selfsigned"); + SSL_CTX_set_verify(m_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE, (acceptSelfSigned) ? verify_callback_allow_selfSigned : verify_callback_reject_selfSigned); + + m_peers.setown(new CStringSet()); + const char* peersstr = config->queryProp("verify/trusted_peers"); + while(peersstr && *peersstr) { - char *dash = strrchr(onepeer, '-'); - if (dash) + StringBuffer onepeerbuf; + peersstr = strtok__(peersstr, "|", onepeerbuf); + if(onepeerbuf.length() == 0) + break; + + char* onepeer = onepeerbuf.detach(); + if (isdigit(*onepeer)) { - *dash = 0; - int last = atoi(dash+1); - char *dot = strrchr(onepeer, '.'); - *dot = 0; - int first = atoi(dot+1); - for (int i = first; i <= last; i++) + char *dash = strrchr(onepeer, '-'); + if (dash) { - StringBuffer t; - t.append(onepeer).append('.').append(i); - m_peers->add(t.str()); + *dash = 0; + int last = atoi(dash+1); + char *dot = strrchr(onepeer, '.'); + *dot = 0; + int first = atoi(dot+1); + for (int i = first; i <= last; i++) + { + StringBuffer t; + t.append(onepeer).append('.').append(i); + m_peers->add(t.str()); + } + } + else + { + m_peers->add(onepeer); } } else { m_peers->add(onepeer); } + free(onepeer); } - else - { - m_peers->add(onepeer); - } - free(onepeer); } } + + SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); } + void checkForUpdatedContext() + { + //Check if a new context should be created - it must be called within a critical section + if (syncedConfig) + { + unsigned activeVersion = syncedConfig->getVersion(); + if (activeVersion != configVersion) + { + configVersion = activeVersion; + Owned config = syncedConfig->getTree(); + createNewContext(config); + } + } + } + +public: + IMPLEMENT_IINTERFACE + + CSecureSocketContext(const IPropertyTree* config, SecureSocketType _sockettype) : sockettype(_sockettype) + { + createNewContext(config); + } + + CSecureSocketContext(const ISyncedPropertyTree* _syncedConfig, SecureSocketType _sockettype) : syncedConfig(_syncedConfig), sockettype(_sockettype) + { + Owned config; + if (syncedConfig) + { + configVersion = syncedConfig->getVersion(); + config.setown(syncedConfig->getTree()); + } + createNewContext(config); + } + +//interface ISecureSocketContext ISecureSocket* createSecureSocket(ISocket* sock, int loglevel, const char *fqdn) { - return new CSecureSocket(sock, m_ctx, m_verify, m_address_match, m_peers, loglevel, fqdn); + return new CSecureSocket(sock, 0, this, m_verify, m_address_match, m_peers, loglevel, fqdn); } ISecureSocket* createSecureSocket(int sockfd, int loglevel, const char *fqdn) { - return new CSecureSocket(sockfd, m_ctx, m_verify, m_address_match, m_peers, loglevel, fqdn); + return new CSecureSocket(nullptr, sockfd, this, m_verify, m_address_match, m_peers, loglevel, fqdn); + } + +//interface ISecureSocketContextCallback + virtual unsigned getVersion() + { + CriticalBlock block(cs); + checkForUpdatedContext(); + return configVersion; + } + virtual SSL * createActiveSSL() + { + //If this function is called it is either a new socket or getVersion() has been called to check it is up to date + CriticalBlock block(cs); + return SSL_new(m_ctx); } + }; class CRsaCertificate : implements ICertificate, public CInterface @@ -1961,51 +1992,61 @@ extern "C" { SECURESOCKET_API ISecureSocketContext* createSecureSocketContext(SecureSocketType sockettype) { - return new securesocket::CSecureSocketContext(sockettype); + return new securesocket::CSecureSocketContext((ISyncedPropertyTree *)nullptr, sockettype); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase, SecureSocketType sockettype) +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSynced(const ISyncedPropertyTree * config, SecureSocketType sockettype) { - return new securesocket::CSecureSocketContext(certFileOrBuf, privKeyFileOrBuf, passphrase, sockettype); + return new securesocket::CSecureSocketContext(config, sockettype); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType sockettype) + +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecret(const char *issuer, SecureSocketType sockettype) { - if (config == NULL) - return createSecureSocketContext(sockettype); + Owned info = getIssuerTlsSyncedConfig(issuer); + return createSecureSocketContextSynced(info, sockettype); +} - return new securesocket::CSecureSocketContext(config, sockettype); -} -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf) +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecretSrv(const char *issuer, bool requireMtlsFlag) { - if (ssf == nullptr || !ssf->queryTlsConfig()) - return createSecureSocketContext(ClientSocket); + if (requireMtlsFlag && !queryMtls()) + throw makeStringException(-100, "TLS secure communication requested but not configured"); + + Owned info = getIssuerTlsSyncedConfig(issuer); + if (!!info->isValid()) + throw makeStringException(-101, "TLS secure communication requested but not configured (2)"); - return new securesocket::CSecureSocketContext(ssf->queryTlsConfig(), ClientSocket); + return createSecureSocketContextSynced(info, ServerSocket); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecret(const char *issuer, SecureSocketType sockettype) +IPropertyTree * createSecureSocketConfig(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase) { - Owned info = getIssuerTlsServerConfig(issuer); - //if the secret doesn't exist doesn't exist just go on without it. IF it is required the tls connection will fail. - //This is primarily for client side... server side would probably use the explict ptree config or explict cert param at least for now. - if (info) - return createSecureSocketContextEx2(info, sockettype); - else - return createSecureSocketContext(sockettype); + if (!certFileOrBuf && !privKeyFileOrBuf && !passphrase) + return nullptr; + + Owned config = createPTree("ssl"); + if (certFileOrBuf) + config->setProp("certificate", certFileOrBuf); + if (privKeyFileOrBuf) + config->setProp("privatekey", privKeyFileOrBuf); + if (passphrase) + config->setProp("passphrase", passphrase); + return config.getClear(); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecretSrv(const char *issuer, bool requireMtlsFlag) +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf) { - if (requireMtlsFlag && !queryMtls()) - throw makeStringException(-100, "TLS secure communication requested but not configured"); + if (ssf == nullptr || !ssf->queryTlsConfig()) + return createSecureSocketContext(ClientSocket); - Owned info = getIssuerTlsServerConfig(issuer); - if (!info) - throw makeStringException(-101, "TLS secure communication requested but not configured (2)"); + return createSecureSocketContextSynced(ssf->queryTlsConfig(), ClientSocket); +} - return createSecureSocketContextEx2(info, ServerSocket); +//Legacy factory interfaces +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType sockettype) +{ + return new securesocket::CSecureSocketContext(config, sockettype); } SECURESOCKET_API ICertificate *createCertificate() @@ -2123,7 +2164,7 @@ class CSecureSmartSocketFactory : public CSmartSocketFactory CSecureSmartSocketFactory(IPropertyTree &service, bool _retry, unsigned _retryInterval, unsigned _dnsInterval) : CSmartSocketFactory(service, _retry, _retryInterval, _dnsInterval) { - secureContext.setown(createSecureSocketContextEx2(queryTlsConfig(), ClientSocket)); + secureContext.setown(createSecureSocketContextSynced(queryTlsConfig(), ClientSocket)); } virtual ISmartSocket *connect_timeout(unsigned timeoutms) override diff --git a/system/security/securesocket/securesocket.hpp b/system/security/securesocket/securesocket.hpp index 2bf1758d92e..e5a1b643c6c 100644 --- a/system/security/securesocket/securesocket.hpp +++ b/system/security/securesocket/securesocket.hpp @@ -87,12 +87,18 @@ typedef ISecureSocketContext* (*createSecureSocketContextSecret_t)(const char *m extern "C" { -SECURESOCKET_API ISecureSocketContext* createSecureSocketContext(SecureSocketType); -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase, SecureSocketType); -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType); -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf); +//The following allow the creation of a secure socket context where the certificates will automatically be updated when they expire. +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSynced(const ISyncedPropertyTree * config, SecureSocketType sockettype); // Will become the primary (only) factory method SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecret(const char *mtlsSecretName, SecureSocketType); SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecretSrv(const char *mtlsSecretName, bool requireMtlsConfig); +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf); + +//Helper function to aid migration to the functions above. This should eventually be removed. +IPropertyTree * createSecureSocketConfig(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase); + +//Legacy factory methods - should be phased out. +SECURESOCKET_API ISecureSocketContext* createSecureSocketContext(SecureSocketType); +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType); SECURESOCKET_API ICertificate *createCertificate(); SECURESOCKET_API int signCertificate(const char* csr, const char* ca_certificate, const char* ca_privkey, const char* ca_passphrase, int days, StringBuffer& certificate); };