Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HPCC-27255 TLS cert/key as buffers #17719

Merged
merged 1 commit into from
Sep 19, 2023

Conversation

mckellyln
Copy link
Contributor

Type of change:

  • This change is a bug fix (non-breaking change which fixes an issue).
  • This change is a new feature (non-breaking change which adds functionality).
  • This change improves the code (refactor or other change that does not change the functionality)
  • This change fixes warnings (the fix does not alter the functionality or the generated code)
  • This change is a breaking change (fix or feature that will cause existing behavior to change).
  • This change alters the query API (existing queries will have to be recompiled)

Checklist:

  • My code follows the code style of this project.
    • My code does not create any new warnings from compiler, build system, or lint.
  • The commit message is properly formatted and free of typos.
    • The commit message title makes sense in a changelog, by itself.
    • The commit is signed.
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly, or...
    • I have created a JIRA ticket to update the documentation.
    • Any new interfaces or exported functions are appropriately commented.
  • I have read the CONTRIBUTORS document.
  • The change has been fully tested:
    • I have added tests to cover my changes.
    • All new and existing tests passed.
    • I have checked that this change does not introduce memory leaks.
    • I have used Valgrind or similar tools to check for potential issues.
  • I have given due consideration to all of the following potential concerns:
    • Scalability
    • Performance
    • Security
    • Thread-safety
    • Cloud-compatibility
    • Premature optimization
    • Existing deployed queries will not be broken
    • This change fixes the problem, not just the symptom
    • The target branch of this pull request is appropriate for such a change.
  • There are no similar instances of the same problem that should be addressed
    • I have addressed them here
    • I have raised JIRA issues to address them separately
  • This is a user interface / front-end modification
    • I have tested my changes in multiple modern browsers
    • The component(s) render as expected

Smoketest:

  • Send notifications about my Pull Request position in Smoketest queue.
  • Test my draft Pull Request.

Testing:

@github-actions
Copy link

@mckellyln
Copy link
Contributor Author

@afishbeck is this what you are sort of looking for ?
It is a draft PR - let me know if this is on the right track.

@mckellyln
Copy link
Contributor Author

@jakesmith this is a draft PR - let me know your thoughts.

@afishbeck
Copy link
Member

afishbeck commented Aug 31, 2023

@mckellyln I haven't reviewed the details, but this functionality looks great.

The one thing that would make this just sort of work magically for much of the kubernetes code is if you updated this constructor:

CSecureSocketContext(const IPropertyTree* config, SecureSocketType sockettype)

So that it detects if the following are file paths like they are now, or memory buffers.

If the ptree could support either then it would become almost seemless.

    const char* certfile = config->queryProp("certificate");
    if (isPEMBuffer(certfile)
       -- loadfrommemory
    else
       --current behavior

    const char* privkeyfile = config->queryProp("privatekey");
    if (isPEMBuffer(privkeyfile)
       -- loadfrommemory
    else
       --current behavior

    if(m_verify)
    {
        const char* cabuffer = config->queryProp("verify/ca_certificates/pem");
    if (!isEmptyString(cabuffer)
       --loadfrommemory
   else
   {
        const char* capath = config->queryProp("verify/ca_certificates/@path");
    if (!isEmptyString(capath)
       --current behavior
  }

Can verify the path to the cacert pem buffer later, but that's the idea.

@mckellyln
Copy link
Contributor Author

ok, I've added code for this, thanks.

@mckellyln mckellyln marked this pull request as ready for review August 31, 2023 20:13
@afishbeck
Copy link
Member

afishbeck commented Aug 31, 2023

@mckellyln I made a minor suggestion above. Other than that, I haven't reviewed the fine points, but the design looks great. I will be able to make use of this to allow more certificates to come from vault rather than local secrets.

@mckellyln
Copy link
Contributor Author

ok, thanks, I will remove the '@' from the '@pem'

Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln - looks good, a few minor points and questions, main one may be - is const char * the best choice for these certificate buffers that ultimately call through to the methods that take a void * + len ?

esp/clients/ws_dfsclient/ws_dfsclient.cpp Outdated Show resolved Hide resolved
esp/clients/ws_dfsclient/ws_dfsclient.cpp Outdated Show resolved Hide resolved
esp/clients/ws_dfsclient/ws_dfsclient.cpp Show resolved Hide resolved
esp/platform/esp.hpp Outdated Show resolved Hide resolved
system/security/securesocket/securesocket.cpp Outdated Show resolved Hide resolved
STACK_OF(X509_INFO) *infostk = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if (!infostk)
{
BIO_free(cbio);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: it would be safer to 'own' the BIO pointer, would avoid the manual free's and guarantee (e.g. if exception) that it would be freed, e.g. with:

cryptohelper::OwnedEVPBio cbio = BIO_new_mem_buf((void *)certbuf, -1);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth considering for similar cleanup code, e.g. when SSL_CTX_free called


return false;
}

class CSecureSocketContext : implements ISecureSocketContext, public CInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not new: slightly more efficient (avoids VMT) to do : public CInterfaceOf

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed

system/security/securesocket/securesocket.cpp Outdated Show resolved Hide resolved
char errbuf[512];
ERR_error_string_n(ERR_get_error(), errbuf, 512);
SSL_CTX_free(m_ctx);
throw MakeStringException(-1, "error loading certificate chain %s", errbuf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial: MakeStringException is deprecated in favour of makeStringException or makeStringExceptionV

system/security/securesocket/securesocket.hpp Outdated Show resolved Hide resolved
@ghalliday ghalliday self-requested a review September 5, 2023 14:44
Copy link
Member

@ghalliday ghalliday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln a few comments. I didn't look in detail at the functions.

esp/clients/ws_dfsclient/ws_dfsclient.cpp Outdated Show resolved Hide resolved
esp/clients/ws_dfsclient/ws_dfsclient.cpp Show resolved Hide resolved

if (!isEmptyString(clientCertBuf))
{
info->setProp("certificatebuf", clientCertBuf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@afishbeck there is a mixture of attributes (enable, accept) and values (trusted_peers, pem, certificatebuf etc.) is there a reason for each?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ghalliday There is, this is basically config for a SecureSocket to be used for either client or server. It's been around quite a while, and used multiple places. I create some from secrets which is where this PR comes from. We could clean it up at some point (along with much of the SecureSocket interface), but it would be a separate effort.

if (client_cert_.length() || ca_certs_.length() || accept_self_signed_)

if (client_cert_buf_.length() || ca_certs_buf_.length())
soapclient.setSecureSocketConfig(createSecClientConfigBuf(client_cert_buf_, client_priv_key_buf_, ca_certs_buf_, accept_self_signed_));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be simpler as a single function that also takes the buffer parameters - it would remove some duplicate code and would also work with a mixuture of buffers and files (although I can't see that being used)

system/security/securesocket/securesocket.cpp Outdated Show resolved Hide resolved
RSA *rsa = PEM_read_bio_RSAPrivateKey(cbio, NULL, 0, NULL);
if (rsa == NULL)
{
BIO_free(cbio);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth using a wrapper class for a BIO * which calls BIO_free on exit. It would clean up the code in this and other functions.
... I see Jake has suggested the same below. I think the following works (it compiles!), and might be a good pattern to adopt elsewhere:

struct BIO_deleter
{
    void operator() ( BIO * bio )
    {
       BIO_free( bio ); 
    }
};
using unique_bioptr = std::unique_ptr<BIO, BIO_deleter>;
...
unique_bioptr cbio(BIO_new_mem_buf((void *)privkeybuf, -1));

@jakesmith thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::unique_ptr vs cryptohelper::OwnedEVPBio would be okay too, and more standard.
Only -ve is that std::unique_ptr doesn't implicitly cast to the underlying pointer the way we are used to with our Owned* varieties [i.e. u need to call uptr.get() ]

Either would make the code cleaner/safer.
It would be nice if we consistently use the same pattern though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, added for BIO.
Perhaps could also do same for stack of x509 ...

Copy link
Member

@jakesmith jakesmith Sep 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I think in almost all cases, it would be cleaner/safer and make the code more robust in general (the pke/cryptocommon code does so for all SSL objects).

@mckellyln
Copy link
Contributor Author

Have made many updates in commit 4 to address your comments.
Please re-review when you have time.

if (!cbio)
return false;

STACK_OF(X509_INFO) *infostk = PEM_X509_INFO_read_bio(cbio.get(), NULL, NULL, NULL);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps a unique_ptr could be used for infostk as well ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I think it would help the code be more robust in general if we contained these objects, such as they were auto deleted when out of scope.

@mckellyln
Copy link
Contributor Author

build / checks failed because of something not related to code changes -

Error: buildx failed with: ERROR: failed to solve: failed to register layer: write /opt/HPCCSystems/lib/libws_topology.so: no space left on device

Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln - please see comments

setRpcSSLOptions(rpc, true, clientCertFilename, clientPrivateKeyFilename, caCertFilename, false);
Owned<IPropertyTree> secretPTree = getSecret("storage", secretName);
if (!secretPTree)
throw MakeStringException(-1, "secret %s.%s not found", "storage", secretName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial: this should really be makeStringExceptionV, also in some other places new and old.

virtual void setClientCertificate(const char *certPath, const char *privateKeyPath) override
{
client_cert_.set(certPath);
client_priv_key_.set(privateKeyPath);
}

void setCACertificatesBuf(const char *caCertBuf) override {ca_certs_buf_.set(caCertBuf);}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personal: not keen on formatting like this, clearer if spaced and on newlines IMO.


void setCACertificatesBuf(const char *caCertBuf) override {ca_certs_buf_.set(caCertBuf);}

virtual void setClientCertificateBuf(const char *certBuf, const char *privKeyBuf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: missing 'override'


SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY);
}

CSecureSocketContext(const char* certfile, const char* privkeyfile, const char* passphrase, SecureSocketType sockettype)
CSecureSocketContext(StringBuffer& certBuf, StringBuffer& privkeyBuf, const char* passphrase, SecureSocketType sockettype)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently, this new constructor isn't used, but also it's confusing that there's 1 ctor taking 2 const char *'s and another taking 2 StringBuffer's.

I think as with the property config ctor, it would be better to consolidate into 1, which differentiated whether a file or content, via isPEMBuffer().

There's quite a bit of common code here too (was to some extent before as well), it would be cleaner/clearer to read if there were member helper functions, e.g. setCertificateFromBuf, setCertificateFromFile, and same for private key.
The calling code in ctor's could then reuse, and with the above change, both can call a common setCertificate and setPrivateKey private member methods, i.e.

    void setCertificate(const char *certificate)
    {
        if (isPEMBuffer(certificate))
            setCertificateFromBuf(certificate);
        else
            setCertificateFromFile(certificate);
    }
    void setPrivateKey(const char *privateKey)
    {
        if (isPEMBuffer(privateKey))
            setPrivateKeyFromBuf(privateKey);
        else
            setPrivateKeyFromFile(privateKey);
    }

const char* privkeyfile = config->queryProp("privatekey");
if(privkeyfile && *privkeyfile)
bool hasPrivKey = false;
const char* privkeybuf = config->queryProp("privatekeybuf");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial: quite a lot of pre-existing mixed conventions in use already, but we should try to use camelCase for new/changed code.

if(!SSL_CTX_check_private_key(m_ctx))
{
SSL_CTX_free(m_ctx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, management of these types of objects would be cleaner/safer if owned (e.g. with unique_ptr).
and if this code is moved into the helper methods that handle private key setting, then it will be more relevant, as it will be owned by the class - the free would not need to happen at this point.

if (!cbio)
return false;

RSA *rsa = PEM_read_bio_RSAPrivateKey(cbio.get(), NULL, 0, NULL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is being leaked (similar code in pke.cpp, ensures it is owned)

if (!cbio)
return false;

STACK_OF(X509_INFO) *infostk = PEM_X509_INFO_read_bio(cbio.get(), NULL, NULL, NULL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I think it would help the code be more robust in general if we contained these objects, such as they were auto deleted when out of scope.

system/security/securesocket/securesocket.cpp Outdated Show resolved Hide resolved
return false;
}

class CSecureSocketContext : public CInterfaceOf<ISecureSocketContext>
{
private:
SSL_CTX* m_ctx = nullptr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would help clearup other code if this was owned.

@@ -1155,7 +1155,147 @@ const char* strtok__(const char* s, const char* d, StringBuffer& tok)
return s;
}

class CSecureSocketContext : implements ISecureSocketContext, public CInterface
using BIO_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code I suggested:

struct BIO_deleter
{
    void operator() ( BIO * bio )
    {
       BIO_free( bio ); 
    }
};
using unique_bioptr = std::unique_ptr<BIO, BIO_deleter>;

avoids the need to pass BIO_free to the constructor of each BIO_ptr.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, that's a nice approach.

We should keep these helper objects and functions together.
We already have existing helpers in system/security/cryptohelper/cryptocommon.hpp,
both for owned semantics of these ssl objects, and for openssl exception creation.

I'd be happy for those to be switched to using std::unique_ptr in this PR, or it might be best to use them as is for now, and switch their implementation to std::unique_ptr at a later date.

@mckellyln
Copy link
Contributor Author

Code changes in commits 5 and 6.
Only items left (except for new comments) are:
1). X509_STORE *store = SSL_CTX_get_cert_store(ctx); - do we need to explicitly free this ?
2). no unique_ptr / owned wrapper for STACK_OF(X509_INFO) *infoStk to free up automatically
Please re-review when you have time, thx.

Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln - looks pretty good, some minor comments, 1 question, and replies to your q's

@@ -271,24 +271,37 @@ int CHttpSoapBinding::HandleSoapRequest(CHttpRequest* request, CHttpResponse* re
return 0;
}


static IPropertyTree *createSecClientConfig(const char *clientCertPath, const char *clientPrivateKey, const char *caCertsPath, bool acceptSelfSigned)
static IPropertyTree *createSecClientConfig(const char *clientCertFileorBuf, const char *clientPrivKeyFileorBuf, const char *caCertsPathorBuf, bool acceptSelfSigned)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial/pedantic/camelCase: ...Fileor... > ...FileOr..

(and similar elsewhere)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed

throw makeStringExceptionV(-1,"Client private key not found %s.", clientPrivateKey);

rpc.setClientCertificate(clientCert, clientPrivateKey);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: if this code (and similar at other entry points) spotted it was a file, could it load it into a buffer and then unify much of the code it calls to dealing with PEM buffer's only (not files), and therefore simply the code?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our current kubernetes installations that would work. But in general I'm not sure whether our file handling supports more formats than just PEM. This buffer handling is for PEM content only.

{
throw MakeStringException(-1, "ctx can't be created");
}
throw makeStringExceptionV(-1, "ctx can't be created");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: should be makeStringException

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed

{
char errbuf[512];
ERR_error_string_n(ERR_get_error(), errbuf, 512);
throw MakeStringException(-1, "error loading certificate chain file %s - %s", certfile, errbuf);
throw makeStringExceptionV(-1, "error loading certificate chain file %s - %s", certFileorBuf, errbuf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use cryptohelper::makeEVPExceptionVA instead of these 3 lines,
and similar in other places below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed

{
char errbuf[512];
ERR_error_string_n(ERR_get_error(), errbuf, 512);
throw makeStringExceptionV(-1, "error loading certificate chain %s", errbuf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use cryptohelper::makeEVPExceptionVA instead of these 3 lines

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln - although squashed, I have scanned this and other changes made in the last commit and looks good.

}

if(!SSL_CTX_check_private_key(m_ctx))
if (!SSL_CTX_check_private_key(m_ctx))
throw makeStringExceptionV(-1, "Private key does not match the certificate public key");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: should be makeStringException

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed

throw MakeStringException(-1, "Private key does not match the certificate public key");
// can have multiple certs in buffer
if (!loadVerifyLocationsPEMBuffer(m_ctx, caCertsPathorBuf))
throw makeStringExceptionV(-1, "Error loading CA certificates");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: should be makeStringException

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed

{
SSL_CTX_free(m_ctx);
}
~CSecureSocketContext() { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need, better to remove it.

m_meth = SSLv23_client_method();
else
m_meth = SSLv23_server_method();
// MCK TODO: should we set a default cipherList, as is done in other ctor (below) ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we should provide a way for an opional cipherList to be provided via this ctor route too,
and default like the other ctor if not.
and/or could it a method of ISecureSocketContext and (optionally) called to set after construction?

Copy link
Member

@ghalliday ghalliday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln that looks good. A few comments that you may want to address, but I think it can be merged soon/now and other issues tackled later.

info->setProp("certificate", clientCertPath);
if (!isEmptyString(clientPrivateKey))
info->setProp("privatekey", clientPrivateKey);
if (isPEMBuffer(clientCertFileorBuf))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this always set "certificate" and then have the PEMbuffer test later? A later clean up PR would be fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but there is also the pem / @path difference, do we leave that in or also remove ?

@@ -1066,6 +1066,25 @@ const MemoryAttr &getSecretUdpKey(bool required)
return udpKey;
}

jlib_decl bool isPEMBuffer(const char *certificate)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth coming up with a more intuitive function name (I didn't know what PEM stood for). Something like containsEmbeddedKey()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, renamed function.

@mckellyln
Copy link
Contributor Author

Squashed commits

Copy link
Member

@afishbeck afishbeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln some comments.

@@ -271,6 +271,29 @@ int CHttpSoapBinding::HandleSoapRequest(CHttpRequest* request, CHttpResponse* re
return 0;
}

static IPropertyTree *createSecClientConfigBuf(const char *clientCertBuf, const char *clientPrivKeyBuf, const char *caCertsBuf, bool acceptSelfSigned)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is picky and might just be me, but I'd prefer the method name be slightly different.
Like "createInMemorySecClientConfig" or "createBufferedSecClientConfig".. the ending of "ConfigBuf" just implies something specific to me.

if (!cbio)
return false;

RSA *rsa = PEM_read_bio_RSAPrivateKey(cbio, NULL, 0, NULL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of this is probably copy and paste, but new code being added should use nullptr rather than NULL.


SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY);
}

CSecureSocketContext(const char* certfile, const char* privkeyfile, const char* passphrase, SecureSocketType sockettype)
CSecureSocketContext(StringBuffer& certBuf, StringBuffer& privkeyBuf, const char* passphrase, SecureSocketType sockettype)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the parameters should still just be char pointers and you should find another way of making the constructor signature unique. Like passing flags. That way if the caller is using some other type of string class they can still function.

If you did keep them as StringBuffer, I think the parameters could be const.

certfile = config->queryProp("certificate");
if (certfile && *certfile)
{
if (isPEMBuffer(certfile))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this now, I think I can just do what you did when I create these config ptrees. When I have a buffer I can call it certificatebuf, privatekeybuf, pem, etc. You don't need to autodetect the type. It just gets a bit convoluted.

@@ -1871,6 +2122,11 @@ SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPrope
return new securesocket::CSecureSocketContext(config, sockettype);
}

SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx3(StringBuffer& certBuf, StringBuffer& privkeyBuf, const char* passphrase, SecureSocketType sockettype)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should force these to be passed as StringBuffer. It takes away the flexibility const char * gives is someone is using a different primitive for the strings, or reads them directly into some location.

throw makeStringExceptionV(-1,"Client private key not found %s.", clientPrivateKey);

rpc.setClientCertificate(clientCert, clientPrivateKey);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our current kubernetes installations that would work. But in general I'm not sure whether our file handling supports more formats than just PEM. This buffer handling is for PEM content only.

if (!isEmptyString(clientPrivateKey))
info->setProp("privatekey", clientPrivateKey);
if (isPEMBuffer(clientCertFileOrBuf))
info->setProp("certificatebuf", clientCertFileOrBuf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having given it more thought, to support other formats in the future should we call this "certificate_pem" or something similar?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, renamed. Yes the check for if its embedded could return a type ...

return true;
}

static bool loadVerifyLocationsPEMBuffer(SSL_CTX *ctx, const char *caCertBuf, int caCertLen=-1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "verify locations" usually refers to locations on disk, can we change the name to something like "setVerifyCertPEMBuffer" or "setCACertPEMBuffer"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, renamed to setVerifyCertPEMBuffer().

throw MakeStringException(-1, "error loading certificate chain file %s - %s", certfile, errbuf);
}
}
const char *certFileOrBuf = config->queryProp("certificatebuf");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, I think we should rename these "buf" properties to "certificate_pem" or similiar so we know exactly what they are. For any new formats in the future we can use other names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, renamed.

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);
void setVerifyLocations(const char *caCertsPathOrBuf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps rename this to "setCACerts" or "setVerifyCerts". I think locations is meant to convey files and/or directories.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, renamed to setVerifyCerts

@mckellyln
Copy link
Contributor Author

@afishbeck made changes based on your comments in commit 2a.
Please re-review when you have time.

Copy link
Member

@afishbeck afishbeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln clarified my 1 remaining question.

return false;
}

X509_STORE *store = SSL_CTX_get_cert_store(ctx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln what I'm wondering is, because we want only our cacerts, and don't want any default cacerts to remain...

Instead of doing:

 X509_STORE *store = SSL_CTX_get_cert_store(ctx);
loop{
    X509_STORE_add_cert(store, infoVal->x509);
}

we should do?:

X509_STORE *store = X509_STORE_new();
loop{
    X509_STORE_add_cert(store, infoVal->x509);
}
SSL_CTX_set_cert_store(ctx, store);  //this will clear (and free) old and add new

@mckellyln
Copy link
Contributor Author

ok, good point, I agree we should reset/clear store or check if cert already there, etc.
I was thinking of it being same as call to SSL_CTX_load_verify_locations(), but its not quite the same.
I'll look at it next week.
Thanks.

@mckellyln
Copy link
Contributor Author

@afishbeck like this ?

}
}

SSL_CTX_set_cert_store(ctx, store);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the OwnedX509Store above goes out of scope but the pointer has been passed to SSL_CTX_set_cert_store?

Should you do:
SSL_CTX_set_cert_store(ctx, store.getClear());

instead?

Copy link
Contributor Author

@mckellyln mckellyln Sep 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you. It needs to survive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then does it get freed when ref count goes to zero ? Or it is already zero at this point ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would it ever get freed ? I am thinking we need to free it when we free ctx ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a store should be added to CSecureSocketContext ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a m_store member, if you think this is an ok way to go.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln I think this is the difference between SSL_CTX_set_cert_store and SSL_CTX_set1_cert_store. One takes ownership and one increments and decrements the reference count. Either case needs testing to make sure there is no leak or extra free.

I think the only reason to add an m_store is if the store is shared by multiple contexts otherwise it's extra complexity with nothing to gain.

Copy link
Member

@afishbeck afishbeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln one comment.

@mckellyln
Copy link
Contributor Author

Having some trouble deciding / understanding the lifetime of a store within a context. Please let me know what you think of adding an m_store member like the last commit ??

@mckellyln mckellyln force-pushed the tls_buffer branch 2 times, most recently from d51854a to 40fd720 Compare September 18, 2023 16:52
@mckellyln
Copy link
Contributor Author

mckellyln commented Sep 18, 2023

ok, removed commit 4a with m_store, so back to original design with the getClear() so it is not freed just after here.

Copy link
Member

@ghalliday ghalliday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln looks good, and code looks clean. Please squash once Tony has approved and I will merge.

Copy link
Member

@afishbeck afishbeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mckellyln looks good.

@mckellyln
Copy link
Contributor Author

Squashed and rebased onto candidate-9.4.x

@ghalliday ghalliday merged commit 5f388e5 into hpcc-systems:candidate-9.4.x Sep 19, 2023
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants