-
Notifications
You must be signed in to change notification settings - Fork 304
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-33018 Expose chosen OpenSSL cryptography capabilities via a plugin #19343
Conversation
Jira Issue: https://hpccsystems.atlassian.net//browse/HPCC-33018 Jirabot Action Result: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I stopped reviewing after I realized that there may be an issue with supporting passphrases for private keys.
Your RSA.Sign() test uses both the private key and a passphrase, and you indicate that it works. That implies that the key requires that passphrase. But, that key is used elsewhere without the passphrase, and it also appears to work. That isn't right; something should be failing.
Take a look at that, please.
ecllibrary/std/OpenSSL.ecl
Outdated
* | ||
* @see AvailableAlgorithms() | ||
*/ | ||
EXPORT DATA Hash(DATA _indata, VARSTRING _hash_name) := lib_openssl.OpenSSL.digesthash(_indata, _hash_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the ECL version of the arguments friendly: indata andhash_name (no leading underscores).
Thought: Would it be worthwhile to settle on an argument name of "algorithm_name" (or something, as long as it is consistent) everywhere that argument is used? Thinking of matching the "AvailableAlgorithms()" naming....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is worthwhile to make the argument name consistent everywhere. I have set it to "algorithm_name."
ecllibrary/std/OpenSSL.ecl
Outdated
* it must be of the expected size for the given | ||
* algorithm; OPTIONAL, defaults to creating a | ||
* random value | ||
* @param salt TCURRENT_OPENSSL_VERSIONencryption; if not set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like a 'replace all' event went wrong here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
ecllibrary/std/OpenSSL.ecl
Outdated
* | ||
* @see Encrypt() | ||
*/ | ||
EXPORT DATA Decrypt(DATA ciphertext, STRING pem_private_key) := lib_openssl.OpenSSL.rsaDecrypt(ciphertext, pem_private_key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should permit passing a passphrase for the private key, like RSA.Sign().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added passphrase parameter.
ecllibrary/std/OpenSSL.ecl
Outdated
* @see Seal() | ||
* Ciphers.AvailableAlgorithms() | ||
*/ | ||
EXPORT DATA Unseal(DATA ciphertext, STRING pem_private_key, VARSTRING symmetric_algorithm = 'aes-256-cbc') := lib_openssl.OpenSSL.rsaUnseal(ciphertext, pem_private_key, symmetric_algorithm); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should permit passing a passphrase for the private key, like RSA.Sign().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added passphrase parameter.
ecllibrary/std/OpenSSL.ecl
Outdated
* @see Digest.AvailableAlgorithms() | ||
* Sign() | ||
*/ | ||
EXPORT BOOLEAN VerifySignature(DATA signature, DATA signedData, DATA passphrase, STRING pem_public_key, VARSTRING hash_name) := lib_openssl.OpenSSL.rsaVerifySignature(signature, signedData, passphrase, pem_public_key, hash_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Public keys don't have passphrases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed passphrase parameter from VerifySignature.
EXPORT encrypt_default_iv_default_salt := Std.OpenSSL.Ciphers.Encrypt((DATA)PLAINTEXT, CIPHERS_CIPHER, (DATA)PASSPHRASE); | ||
EXPORT encrypt_rsa := Std.OpenSSL.RSA.Encrypt((DATA)PLAINTEXT, RSA_PUBLIC_1); | ||
EXPORT seal_rsa := Std.OpenSSL.RSA.Seal((DATA)PLAINTEXT, [RSA_PUBLIC_1, RSA_PUBLIC_2]); | ||
EXPORT signed_rsa_sha256 := Std.OpenSSL.RSA.Sign((DATA)PLAINTEXT, (DATA)PASSPHRASE, RSA_PRIVATE_1, 'sha256'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was RSA_PRIVATE_1 created with a non-empty passphrase? If not then I don't see how this could work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was accepting the passphrase parameter, but not doing anything with it. The passphrase is now used and I have added RSA_PRIVATE_2 that was created with a non-empty passphrase to test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking pretty good! Just a few changes for this round.
ecllibrary/std/OpenSSL.ecl
Outdated
* @see Digest.AvailableAlgorithms() | ||
* VerifySignature() | ||
*/ | ||
EXPORT DATA Sign(DATA plaintext, DATA passphrase, STRING pem_private_key, VARSTRING algorithm_name) := lib_openssl.OpenSSL.rsaSign(plaintext, passphrase, pem_private_key, algorithm_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make algorithm_name default to 'sha256'. That is the most common hash algorithm for signing. Remember to adjust the @param doc accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed default to value to sha256.
ecllibrary/std/OpenSSL.ecl
Outdated
* @see Digest.AvailableAlgorithms() | ||
* Sign() | ||
*/ | ||
EXPORT BOOLEAN VerifySignature(DATA signature, DATA signedData, STRING pem_public_key, VARSTRING algorithm_name) := lib_openssl.OpenSSL.rsaVerifySignature(signature, signedData, pem_public_key, algorithm_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as with Sign(): Set the default value of algorithm_name to 'sha256' and adjust the @param doc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
EXPORT STRING CIPHERS_CIPHER := 'aes-256-cbc'; | ||
EXPORT STRING PASSPHRASE := 'mypassphrase'; | ||
|
||
EXPORT STRING RSA_PUBLIC_1 := |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Why not use the triple-apostrophe multi-line quote scheme for these big PEM keys? It's fine the way it is, just asking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using the triple-apostrophe syntax, the regression test would fail inside PEM_read_bio_PrivateKey with "Error: -1: Error within loading a pkey: error:1E08010C:DECODER routines::unsupported", but worked just fine using the other syntax.
EXPORT seal_rsa := Std.OpenSSL.RSA.Seal((DATA)PLAINTEXT, [RSA_PUBLIC_1, RSA_PUBLIC_2]); | ||
EXPORT signed_rsa_sha256 := Std.OpenSSL.RSA.Sign((DATA)PLAINTEXT, (DATA)'', RSA_PRIVATE_1, 'sha256'); | ||
EXPORT signed_rsa_sha256_passphrase := Std.OpenSSL.RSA.Sign((DATA)PLAINTEXT, (DATA)PASSPHRASE, RSA_PRIVATE_2, 'SHA256'); | ||
// EXPORT signed_rsa_sha256_wrong_passphrase := Std.OpenSSL.RSA.Sign((DATA)PLAINTEXT, (DATA)'notmypassphrase', RSA_PRIVATE_2, 'SHA256'); Fails with Error: -1: Error within loading a pkey: error:1C800064:Provider routines::bad decrypt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've asked Gavin for recommendations on how to test this. Trapping rtlFail() can be difficult, if not impossible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears that it isn't possible to cleanly capture a failure that throws an exception. My advice is to leave the failure tests in the file, commented-out, along with a comment describing why they are commented-out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added an explanation to the test.
ecllibrary/std/OpenSSL.ecl
Outdated
EXPORT DATA Decrypt(DATA ciphertext, VARSTRING algorithm_name, DATA passphrase, DATA iv = (DATA)'', DATA salt = (DATA)'') := lib_openssl.OpenSSL.cipherDecrypt(ciphertext, algorithm_name, passphrase, iv, salt); | ||
END; // Ciphers | ||
|
||
EXPORT RSA := MODULE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that the MODULEs I defined need the best. Existing structure is:
OpenSSL
Digest
AvailableAlgorithms()
Hash()
Ciphers
AvailableAlgorithms()
IVSize()
SaltSize()
Encrypt()
Decrypt()
RSA
Seal()
Unseal()
Encrypt()
Decrypt()
Sign()
VerifySignature()
New structure should be:
OpenSSL
Digest
AvailableAlgorithms()
Hash()
Cipher *
AvailableAlgorithms()
IVSize()
SaltSize()
Encrypt()
Decrypt()
PublicKey *
RSASeal() *
RSAUnseal() *
Encrypt()
Decrypt()
Sign()
VerifySignature()
Asterisks indicate changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
plugins/openssl/openssl.cpp
Outdated
failOpenSSLError("creating buffer for EVP_PKEY"); | ||
|
||
EVP_PKEY * pkey; | ||
if (startsWith(key, "-----BEGIN RSA PUBLIC KEY-----")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This public key may not be RSA; it could be ECC. I think you should check for 'contains "PUBLIC KEY-----"' (not sure about the dashes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a function isPublicKey to check the string for "PUBLIC KEY-----".
plugins/openssl/openssl.cpp
Outdated
// Create and initialize the message digest context | ||
mdCtx = EVP_MD_CTX_new(); | ||
if (!mdCtx) | ||
failOpenSSLError("EVP_MD_CTX_new (rsaSign)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'rsaSign' is a misnomer, because you can sign with other public key types. This may need alteration elsewhere in the code as well.
Technically, there is a subset of public key types that can be used for signing. We can let OpenSSL generate an error message if necessary; no need to explicitly check. For reference:
Only EVP_PKEY types that support signing can be used with these functions. This includes MAC algorithms where the MAC generation is considered as a form of "signing". Built-in EVP_PKEY types supported by these functions are CMAC, Poly1305, DSA, ECDSA, HMAC, RSA, SipHash, Ed25519 and Ed448.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed RSA naming from Sign and VerifySignature and changed the modulename to PublicKey
plugins/openssl/openssl.cpp
Outdated
} | ||
} | ||
|
||
OPENSSL_API void OPENSSL_CALL rsaSign(ICodeContext *ctx, size32_t & __lenResult, void * & __result, size32_t len_plaintext, const void * _plaintext, size32_t len_passphrase, const void * _passphrase, size32_t len_pem_private_key, const char * _pem_private_key, const char * _algorithm_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest renaming this function because it is not RSA-specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to pkSign as part of the Public Key module.
plugins/openssl/openssl.cpp
Outdated
} | ||
} | ||
|
||
OPENSSL_API bool OPENSSL_CALL rsaVerifySignature(ICodeContext *ctx, size32_t len_signature, const void * _signature, size32_t len_signedData, const void * _signedData, size32_t len_pem_public_key, const char * _pem_public_key, const char * _algorithm_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest renaming this function because it is not RSA-specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to pkVerifySignature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Approving, but I would ask that you make the trivial changes I noted.
ecllibrary/std/OpenSSL.ecl
Outdated
* | ||
* @return The encrypted ciphertext. | ||
* | ||
* @see Unseal() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did some renaming, so this Unseal() was changed to RSAUnseal(). Check all other @see functions as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the @see functions in the comments.
plugins/openssl/CMakeLists.txt
Outdated
OpenSSL::SSL | ||
OpenSSL::Crypto | ||
) | ||
endif() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you need a final linefeed in the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a linefeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good. A comment about the name of the plugin, an object leak and a few scattered comments about style.
plugins/openssl/CMakeLists.txt
Outdated
# Cmake Input File for openssl | ||
############################################################# | ||
|
||
project(openssl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect a different name would be better, otherwise it is confusing whether it means the real openssl library. sslservices?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to sslservices. The toplevel std/OpenSSL.ecl file is still named OpenSSl. Should that be renamed as well e.g. SSL.ecl?
plugins/openssl/openssl.hpp
Outdated
#define _OPENSSL_INCL | ||
|
||
#ifdef _WIN32 | ||
#define OPENSSL_CALL _cdecl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed? Do the regression tests work on windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if it is required. I added it to stay consistent with cryptolib and fileservices.
I also don't think it works on windows because it expects 'ecl' to be installed. I haven't tried doing a windows build though.
plugins/openssl/openssl.cpp
Outdated
} | ||
} | ||
misses++; | ||
const T * newObj = getObjectByName(algorithm_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these will never be freed. Should this be using a unique_ptr in the tuple?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The algorithms and digests are allocated by OPENSSL_init_crypto, and generally, they are freed when the application exits. They can be explicitly freed by a call to OPENSSL_cleanup, but that is discouraged in the documentation. I think it is currently correct.
plugins/openssl/openssl.cpp
Outdated
failOpenSSLError("adding new object to cache"); | ||
|
||
return newObj; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: ; not needed after a function definition (and in other places)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
plugins/openssl/openssl.cpp
Outdated
void clear() | ||
{ | ||
for (auto& c : cache) | ||
EVP_PKEY_free(std::get<1>(c)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use unique_ptr to avoid having to explicitly delete the associated items. (Or create a wrapper class.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use unique_ptr.
|
||
EXPORT STRING RSA_PUBLIC_1 := | ||
'-----BEGIN RSA PUBLIC KEY-----' + '\n' + | ||
'MIICCgKCAgEAuLYYTmPMzp13999s7bUAaJZVON+9k/2PDBF5AfHtQ40FmxwRdG3d' + '\n' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may trigger warnings from scanning rules about private keys being exported in public repositories.... I'm not quite sure what the best solution idea is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TestCrypto_PKE.ecl Includes a private key the same way for testing. I'm not sure if there is a better way either.
plugins/openssl/openssl.cpp
Outdated
{ | ||
for (size_t x = 0; x < publicKeys.size(); x++) | ||
{ | ||
if (encryptedKeys[x]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: A general point, for free, delete and rtlFree it is legal to pass a null ptr, and it is defined to do nothing - so no need to check first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed checks around rtlFree.
plugins/openssl/openssl.cpp
Outdated
|
||
// Allocate memory for encrypted keys | ||
size_t keyCount = publicKeys.size(); | ||
encryptedKeys = static_cast<byte **>(rtlMalloc(sizeof(byte *) * keyCount)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: You could use
enryptedKeys = new byte * [keyCount];
and
delete [] encryptedKeys;
rtlMalloc and rtlFree need to be used for allocated data that is returned from the function. The reason is to avoid conflicts between the debug and release versions of the c++ library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use new/delete syntax instead of rtlMalloc/rtlFree.
plugins/openssl/openssl.cpp
Outdated
failOpenSSLError("EVP_SealFinal"); | ||
ciphertextLen += len; | ||
|
||
int totalKeyLen = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
general: Use size32_t for the sizes of objects that are never going to be ridiculously large, size_t for full compatibility. Same in various other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use size32_t instead of int in most places.
plugins/openssl/openssl.cpp
Outdated
// Keys; each is (size_t) + <content> | ||
for (size_t x = 0; x < keyCount; x++) | ||
{ | ||
size_t keyLen = keyLens[x]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be size32_t rather than size_t? All ecl structures are 32 bit lengths. Another alternative if it is not read from ecl, you could use appendPacked()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be size32_t.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks jack, a few more comments.
plugins/sslservices/sslservices.cpp
Outdated
{ | ||
|
||
void failOpenSSLError(const std::string& context) | ||
{ | ||
unsigned long errCode = 0; | ||
char buffer[120]; | ||
size_t errCode = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed unused variable.
plugins/sslservices/sslservices.cpp
Outdated
@@ -77,7 +77,7 @@ void failOpenSSLError(const std::string& context) | |||
int passphraseCB(char *passPhraseBuf, int passPhraseBufSize, int rwflag, void *pPassPhraseMB) | |||
{ | |||
size32_t len = ((MemoryBuffer*)pPassPhraseMB)->length(); | |||
if (passPhraseBufSize >= (int)len) | |||
if (passPhraseBufSize >= len) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may need to cast passPhraseBufSize to size32_t to avoid complains about mixing signed and unsigned in a comparison
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added cast to size32_t for passPhraseBufSize.
plugins/sslservices/sslservices.cpp
Outdated
|
||
private: | ||
int hits; | ||
int misses; | ||
size32_t hits; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be unsigned. size32_t is only used for numbers of bytes - not arbitrary counts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to unsigned.
plugins/sslservices/sslservices.cpp
Outdated
int hits; | ||
int misses; | ||
std::list<std::tuple<unsigned, EVP_PKEY *>> cache; | ||
size32_t hits; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment about unsigned here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Also reverted the hash to an unsigned since it is not a number of bytes.
plugins/sslservices/sslservices.cpp
Outdated
std::list<std::tuple<unsigned, EVP_PKEY *>> cache; | ||
size32_t hits; | ||
size32_t misses; | ||
std::list<std::tuple<size32_t, std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>> cache; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleanup: you can use a typedef/using to declare a type for a unique Pkey, which simplifies the code when adding to the cache
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added typedef.
cache.pop_back(); | ||
} | ||
} | ||
else | ||
failOpenSSLError("loading a pkey"); | ||
|
||
return pkey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multi-threading: What happens if there are lots of calls to resolve items in the cache from other threads - so this item is evicted (and deleted) as soon as the function returns. May require a shared pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that is an issue since the caches are initialized with a thread_local storage specifier. Should the caches be shared among multiple threads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be clearly documented as a comment on the class. The cached results cannot be relied on if it is called from multiple threads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added note to caches.
plugins/sslservices/sslservices.cpp
Outdated
|
||
//-------------------------------------------------------------------------- | ||
// Advertised Entry Point Functions | ||
// Advertised Entry Posize32_t Functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly too enthusiastic replacement of int!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
plugins/sslservices/sslservices.cpp
Outdated
@@ -407,17 +398,17 @@ OPENSSL_API void OPENSSL_CALL cipherEncrypt(ICodeContext *ctx, size32_t & __lenR | |||
|
|||
try | |||
{ | |||
int len = 0; | |||
int ciphertextLen = 0; | |||
size32_t len = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a case where using int is probably correct - because that is the type required by the api you are calling. (technically the reinterpret cast is undefined behaviour.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed cases where the type is required by the api to use that type.
for (size_t x = 0; x < publicKeys.size(); x++) | ||
rtlFree(encryptedKeys[x]); | ||
rtlFree(encryptedKeys); | ||
delete [] encryptedKeys; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You still need to iterate and delete the encryptedKeys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only fixed in the catch{} block - it needs similar code here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in try block.
plugins/sslservices/sslservices.cpp
Outdated
plaintext.ensureCapacity(plaintextLen); | ||
|
||
int len = 0; | ||
if (EVP_OpenUpdate(decryptCtx, static_cast<byte *>(plaintext.bufferBase()), &len, newCipherText, newCipherTextLen) != 1) | ||
size32_t len = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment about int being correct for this call/instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ghalliday Fixed those issues and had one question about the storage class of the caches.
plugins/sslservices/sslservices.cpp
Outdated
{ | ||
|
||
void failOpenSSLError(const std::string& context) | ||
{ | ||
unsigned long errCode = 0; | ||
char buffer[120]; | ||
size_t errCode = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed unused variable.
plugins/sslservices/sslservices.cpp
Outdated
@@ -77,7 +77,7 @@ void failOpenSSLError(const std::string& context) | |||
int passphraseCB(char *passPhraseBuf, int passPhraseBufSize, int rwflag, void *pPassPhraseMB) | |||
{ | |||
size32_t len = ((MemoryBuffer*)pPassPhraseMB)->length(); | |||
if (passPhraseBufSize >= (int)len) | |||
if (passPhraseBufSize >= len) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added cast to size32_t for passPhraseBufSize.
plugins/sslservices/sslservices.cpp
Outdated
|
||
private: | ||
int hits; | ||
int misses; | ||
size32_t hits; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to unsigned.
plugins/sslservices/sslservices.cpp
Outdated
int hits; | ||
int misses; | ||
std::list<std::tuple<unsigned, EVP_PKEY *>> cache; | ||
size32_t hits; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Also reverted the hash to an unsigned since it is not a number of bytes.
plugins/sslservices/sslservices.cpp
Outdated
std::list<std::tuple<unsigned, EVP_PKEY *>> cache; | ||
size32_t hits; | ||
size32_t misses; | ||
std::list<std::tuple<size32_t, std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>> cache; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added typedef.
plugins/sslservices/sslservices.cpp
Outdated
|
||
//-------------------------------------------------------------------------- | ||
// Advertised Entry Point Functions | ||
// Advertised Entry Posize32_t Functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
plugins/sslservices/sslservices.cpp
Outdated
@@ -407,17 +398,17 @@ OPENSSL_API void OPENSSL_CALL cipherEncrypt(ICodeContext *ctx, size32_t & __lenR | |||
|
|||
try | |||
{ | |||
int len = 0; | |||
int ciphertextLen = 0; | |||
size32_t len = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed cases where the type is required by the api to use that type.
for (size_t x = 0; x < publicKeys.size(); x++) | ||
rtlFree(encryptedKeys[x]); | ||
rtlFree(encryptedKeys); | ||
delete [] encryptedKeys; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
plugins/sslservices/sslservices.cpp
Outdated
plaintext.ensureCapacity(plaintextLen); | ||
|
||
int len = 0; | ||
if (EVP_OpenUpdate(decryptCtx, static_cast<byte *>(plaintext.bufferBase()), &len, newCipherText, newCipherTextLen) != 1) | ||
size32_t len = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
cache.pop_back(); | ||
} | ||
} | ||
else | ||
failOpenSSLError("loading a pkey"); | ||
|
||
return pkey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that is an issue since the caches are initialized with a thread_local storage specifier. Should the caches be shared among multiple threads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments about the thread safety of the caches. Something to discuss with Dan whether it is best to have per thread caches, or one shared cache.
If they are per thread, then the initialisation all needs to happen in the constructor, and clean up in the destructor. If one shared, then the returned object's lifetime needs more thought.
void AlgorithmCache<EVP_MD>::setCacheName() {cacheName = "DIGEST";} | ||
|
||
template <> | ||
const EVP_CIPHER * AlgorithmCache<EVP_CIPHER>::getObjectByName(const char * name) { return EVP_get_cipherbyname(name); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these objects need cleaning up? I suspect not, but please confirm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the lists of ciphers/digests are populated at initialization and cleaned up automatically by OpenSSL.
plugins/sslservices/sslservices.cpp
Outdated
void init() | ||
{ | ||
setCacheName(); | ||
hits = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: Safer/cleaner to have default initialisers on the member variables. They should be initialised in the (implicit or explicit) constructor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added default initializer to cache member variables.
plugins/sslservices/sslservices.cpp
Outdated
|
||
MODULE_INIT(INIT_PRIORITY_STANDARD) | ||
{ | ||
pkeyCache.init(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these are thread-local variables you cannot rely on calling init.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed init functions and moved logic to constructor and added default initializers.
plugins/sslservices/sslservices.cpp
Outdated
return newObj; | ||
} | ||
|
||
void printStatistics() {DBGLOG("SSLSERVICES %s CACHE STATS: HITS = %d, MISSES = %d", cacheName.c_str(), hits, misses);} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be better logged as a metric, with text that is machine readable. Something along the lines of ...:
LOG(MCmonitorMetric, "{ \"type\": \"metric\", \"name\": \"sslServiceCache%s\", \"hits\": \"%u\", \"misses\": \"%u\" }", cacheName.c_str(), hits, misses);
There is only one other place that currently does this, but it would be good to switch all existing equivalents to a similar pattern. It would be worth rationalising the format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed printStatistics functions and change to LOG call in the destructor.
const T * getObjectByName(const char * name); | ||
}; | ||
|
||
template <> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, I didn't know you could do this...
for (size_t x = 0; x < publicKeys.size(); x++) | ||
rtlFree(encryptedKeys[x]); | ||
rtlFree(encryptedKeys); | ||
delete [] encryptedKeys; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only fixed in the catch{} block - it needs similar code here.
size32_t keySize = 0; | ||
memcpy(&keySize, inPtr, sizeof(keySize)); | ||
inPtr += sizeof(keySize); | ||
encryptedKeys.emplace_back(reinterpret_cast<const char *>(inPtr), keySize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same potential problem with large numbers of keys, and cache size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is the same issue because there is only a single key checked out from the cache to compare to the encrypted public keys stored in the _ciphertext.
plugins/sslservices/sslservices.cpp
Outdated
__lenResult = plaintextLen; | ||
MemoryBuffer resultBuffer(__lenResult); | ||
resultBuffer.append(__lenResult, plaintext.bufferBase()); | ||
__result = resultBuffer.detachOwn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you directly return from plaintext.detachOwn() and avoid a clone?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed and removed clone MemoryBuffer.
cache.pop_back(); | ||
} | ||
} | ||
else | ||
failOpenSSLError("loading a pkey"); | ||
|
||
return pkey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be clearly documented as a comment on the class. The cached results cannot be relied on if it is called from multiple threads.
plugins/sslservices/sslservices.cpp
Outdated
{ | ||
if(PRINT_STATS) | ||
{ | ||
pkeyCache.printStatistics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only going to work for hthor - because the caches are threaded local. Even then there are situations where it will display nothing.
I had originally suggested that Jack implement thread-local caches. This is one of the (few) cases where I think the overhead of thread-safety via locks outweighs the savings of globally caching constructed objects. Considerations included: cached objects are usually quite small; there will typically be very few objects in any particular run (there won't be much switching between algorithms or keys during a job, for instance); and crypto calls will generally be applied en masse to large data in a TRANSFORM (as opposed to one-off calls at the top level of a BWR). |
@ghalliday I had a couple questions that I left for you in the comments. Back to you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jackdelv looks good. Please can you squash ready for merging.
plugins/sslservices/sslservices.cpp
Outdated
} | ||
} | ||
|
||
MODULE_INIT(INIT_PRIORITY_STANDARD) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trivial: could now be deleted.
Jira Issue: https://hpccsystems.atlassian.net//browse/HPCC-33018 Jirabot Action Result: |
Signed-off-by: Jack Del Vecchio <[email protected]>
@ghalliday Squashed. |
Jirabot Action Result: |
Type of change:
Checklist:
Smoketest:
Testing: