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-33018 Expose chosen OpenSSL cryptography capabilities via a plugin #19343

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ecllibrary/std/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ set(
Math.ecl
Metaphone3.ecl
Metaphone.ecl
OpenSSL.ecl
Str.ecl
Uni.ecl
)
Expand Down
329 changes: 329 additions & 0 deletions ecllibrary/std/OpenSSL.ecl
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
/*##############################################################################

HPCC SYSTEMS software Copyright (C) 2025 HPCC Systems®.

Licensed under the Apache License, Version 2.0 (the License);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an AS IS BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
############################################################################## */

EXPORT OpenSSL := MODULE

IMPORT lib_openssl;

EXPORT Digest := MODULE

/**
* Returns a list of the names of the available hash digest algorithms.
dcamper marked this conversation as resolved.
Show resolved Hide resolved
*
* @return A dataset containing the hash algorithm names.
*
* @see Hash()
*/
EXPORT DATASET({STRING name}) AvailableAlgorithms() := lib_openssl.OpenSSL.digestAvailableAlgorithms();

/**
* Compute the hash of given data according to the named
* hash algorithm.
*
* @param indata The data to hash; REQUIRED
* @param hash_name The name of the hash algorithm to use;
* must be one of the values returned from
* the AvailableAlgorithms() function in
* this module; cannot be empty; REQUIRED
*
* @return A DATA value representing the hash value of indata.
*
* @see AvailableAlgorithms()
*/
EXPORT DATA Hash(DATA _indata, VARSTRING _hash_name) := lib_openssl.OpenSSL.digesthash(_indata, _hash_name);
Copy link
Contributor

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....

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 think it is worthwhile to make the argument name consistent everywhere. I have set it to "algorithm_name."


END; // Digest

EXPORT Ciphers := MODULE

/**
* Returns a list of the names of the available symmetric
* cipher algorithms.
dcamper marked this conversation as resolved.
Show resolved Hide resolved
*
* @return A dataset containing the symmetric cipher algorithm names.
*
* @see IVSize()
* SaltSize()
* Encrypt()
* Decrypt()
*/
EXPORT DATASET({STRING name}) AvailableAlgorithms() := lib_openssl.OpenSSL.cipherAvailableAlgorithms();

/**
* Return the size of the IV used for the given symmetric
* cipher algorithm.
*
* This is primarily an introspection/discovery function. Once
* you determine the proper value for the algorithm you want to
* use, you should hardcode it.
*
* @param algorithm The name of the symmetric cipher to examine;
* must be one of the values returned from
* the AvailableAlgorithms() function in
* this module; cannot be empty; REQUIRED
*
* @return The size of the IV used by the given algorithm, in bytes.
*
* @see AvailableAlgorithms()
*/
EXPORT UNSIGNED2 IVSize(VARSTRING algorithm) := lib_openssl.OpenSSL.cipherIVSize(algorithm);

/**
* Return the size of the salt used for the given symmetric
* cipher algorithm.
*
* This is primarily an introspection/discovery function. Once
* you determine the proper value for the algorithm you want to
* use, you should hardcode it.
*
* @param algorithm The name of the symmetric cipher to examine;
* must be one of the values returned from
* the AvailableAlgorithms() function in
* this module; cannot be empty; REQUIRED
*
* @return The size of the salt used by the given algorithm, in bytes.
*
* @see AvailableAlgorithms()
*/
EXPORT UNSIGNED2 SaltSize(VARSTRING algorithm) := 8;

/**
* Encrypt some plaintext with the given symmetric cipher and a
* passphrase. Optionally, you can specify static IV and salt values.
* The encrypted ciphertext is returned as a DATA value.
*
* If IV or salt values are explicitly provided during encryption then
* those same values must be provided during decryption.
*
* @param plaintext The data to encrypt; REQUIRED
* @param algorithm The name of the symmetric cipher to use;
* must be one of the values returned from
* the AvailableAlgorithms() function in
* this module; cannot be empty; REQUIRED
* @param iv The IV to use during encryption; if not set
* then a random value will be generated; if set,
* 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
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* then a random value will be generated; if set,
* it must be of the expected size for the given
* algorithm; OPTIONAL, defaults to creating a
* random value
*
* @return The ciphertext as a DATA type.
*
* @see AvailableAlgorithms()
* IVSize()
* SaltSize()
* Decrypt()
*/
EXPORT DATA Encrypt(DATA plaintext, VARSTRING algorithm, DATA passphrase, DATA iv = (DATA)'', DATA salt = (DATA)'') := lib_openssl.OpenSSL.cipherEncrypt(plaintext, algorithm, passphrase, iv, salt);


/**
* Decrypt some ciphertext with the given symmetric cipher and a
* passphrase. Optionally, you can specify static IV and salt values.
* The decrypted plaintext is returned as a DATA value.
*
* @param ciphertext The data to decrypt; REQUIRED
* @param algorithm The name of the symmetric cipher to use;
* must be one of the values returned from
* the AvailableAlgorithms() function in
* this module; cannot be empty; REQUIRED
* @param iv The IV to use during decryption; if not set
* then a random value will be used; if set,
* it must be of the expected size for the given
* algorithm; OPTIONAL, defaults to creating a
* random value
* @param salt The salt to use during decryption; if not set
* then a random value will be used; if set,
* it must be of the expected size for the given
* algorithm; OPTIONAL, defaults to creating a
* random value
*
* @return The plaintext as a DATA type.
*
* @see AvailableAlgorithms()
* IVSize()
* SaltSize()
* Encrypt()
*/
EXPORT DATA Decrypt(DATA ciphertext, VARSTRING algorithm, DATA passphrase, DATA iv = (DATA)'', DATA salt = (DATA)'') := lib_openssl.OpenSSL.cipherDecrypt(ciphertext, algorithm, passphrase, iv, salt);
END; // Ciphers

EXPORT RSA := MODULE
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


/**
* Perform a hybrid encryption using one or more RSA public keys.
*
* Because asymmetric encryption is computationally expensive, large
* payloads are actually encrypted with a symmetric cipher and a
* randomly-generated passphrase. The passphrase, which is much shorter,
* is then encrypted with the public key. The whole package is then bundled
* together into an "envelope" and "sealed".
*
* The function uses RSA public keys and they must be in PEM format. To
* generate such keys on the command line:
*
* ssh-keygen -b 4096 -t rsa -m pem -f sample2
* ssh-keygen -f sample2 -e -m pem > sample2.pub
*
* The resulting files, sample2 and sample2.pub, are the private and public
* keys, respectively. Their contents may be passed to this function.
*
* @param plaintext The data to encrypt; REQUIRED
* @param pem_public_keys One or more RSA public keys, in PEM format;
* note that this is a SET -- you can pass
* more than one public key here, and the resulting
* ciphertext can be decrypted by any one of the
* corresponding private keys; REQUIRED
* @param symmetric_algorithm The name of the symmetric algorithm to use
* to encrypt the payload; must be one of those
* returned by Ciphers.AvailableAlgorithms();
* OPTIONAL, defaults to aes-256-cbc
*
* @return The encrypted ciphertext.
*
* @see Unseal()
Copy link
Contributor

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.

Copy link
Contributor Author

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.

* Ciphers.AvailableAlgorithms()
*/
EXPORT DATA Seal(DATA plaintext, SET OF STRING pem_public_keys, VARSTRING symmetric_algorithm = 'aes-256-cbc') := lib_openssl.OpenSSL.rsaSeal(plaintext, pem_public_keys, symmetric_algorithm);

/**
* Decrypts ciphertext previously generated by the Seal() function.
*
* Because asymmetric encryption is computationally expensive, large
* payloads are actually encrypted with a symmetric cipher and a
* randomly-generated passphrase. The passphrase, which is much shorter,
* is then encrypted with the public key. The whole package is then bundled
* together into an "envelope" and "sealed". Given the private key that
* corresponds to one of the public keys used to create the ciphertext,
* this function unpacks everything and decrypts the payload.
*
* The function uses RSA public keys and they must be in PEM format. To
* generate such keys on the command line:
*
* ssh-keygen -b 4096 -t rsa -m pem -f sample2
* ssh-keygen -f sample2 -e -m pem > sample2.pub
*
* The resulting files, sample2 and sample2.pub, are the private and public
* keys, respectively. Their contents may be passed to this function.
*
* @param ciphertext The data to decrypt; REQUIRED
* @param pem_private_key An RSA public key in PEM format; REQUIRED
* @param symmetric_algorithm The name of the symmetric algorithm to use
* to decrypt the payload; must be one of those
* returned by Ciphers.AvailableAlgorithms() and
* it must match the algorithm used to create the
* ciphertext; OPTIONAL, defaults to aes-256-cbc
*
* @return The decrypted plaintext.
*
* @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);
Copy link
Contributor

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().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added passphrase parameter.


/**
* This function performs asymmetric encryption. It should be used to
* encrypt only small plaintext (e.g. less than 100 bytes) because it is
* computationally expensive.
*
* @param plaintext The data to encrypt; REQUIRED
* @param pem_public_key The public key to use for encryption, in
* PEM format; REQUIRED
*
* @return The encrypted ciphertext.
*
* @see Decrypt()
*/
EXPORT DATA Encrypt(DATA plaintext, STRING pem_public_key) := lib_openssl.OpenSSL.rsaEncrypt(plaintext, pem_public_key);

/**
* This function performs asymmetric decryption. It should be used to
* decrypt only small plaintext (e.g. less than 100 bytes) because it is
* computationally expensive.
*
* @param ciphertext The data to decrypt; REUIRED
* @param pem_private_key The private key to use for decryption, in
* PEM format; REQUIRED
*
* @return The decrypted plaintext.
*
* @see Encrypt()
*/
EXPORT DATA Decrypt(DATA ciphertext, STRING pem_private_key) := lib_openssl.OpenSSL.rsaDecrypt(ciphertext, pem_private_key);
Copy link
Contributor

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().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added passphrase parameter.


/**
* Create a digital signature of the given data, using the
* specified private key, passphrase and algorithm.
*
* The function uses an RSA private key and it must be in PEM format. To
* generate such keys on the command line:
*
* ssh-keygen -b 4096 -t rsa -m pem -f sample2
* ssh-keygen -f sample2 -e -m pem > sample2.pub
*
* The resulting files, sample2 and sample2.pub, are the private and public
* keys, respectively. Their contents may be passed to this function
*
* @param plaintext Contents to sign; REQUIRED
* @param passphrase Passphrase to use for private key; REQUIRED
* @param pem_private_key Private key to use for signing; REQUIRED
dcamper marked this conversation as resolved.
Show resolved Hide resolved
* @param hash_name The name of the hash algorithm to use;
* must be one of the values returned from
* the AvailableAlgorithms() function in
* the Digest module; cannot be empty; REQUIRED
* @return Computed Digital signature
*
* @see Digest.AvailableAlgorithms()
* VerifySignature()
*/
EXPORT DATA Sign(DATA plaintext, DATA passphrase, STRING pem_private_key, VARSTRING hash_name) := lib_openssl.OpenSSL.rsaSign(plaintext, passphrase, pem_private_key, hash_name);

/**
* Verify the given digital signature of the given data, using
* the specified public key, passphrase and algorithm.
*
* The function uses an RSA public key and it must be in PEM format. To
* generate such keys on the command line:
*
* ssh-keygen -b 4096 -t rsa -m pem -f sample2
* ssh-keygen -f sample2 -e -m pem > sample2.pub
*
* The resulting files, sample2 and sample2.pub, are the private and public
* keys, respectively. Their contents may be passed to this function
*
* @param signature Signature to verify; REQUIRED
* @param signedData Data used to create signature; REQUIRED
* @param passphrase Passphrase to use for private key; REQUIRED
* @param pem_public_key Public key to use for verification; REQUIRED
* @param hash_name The name of the hash algorithm to use;
* must be one of the values returned from
* the AvailableAlgorithms() function in
* the Digest module; cannot be empty; REQUIRED
* @return Boolean TRUE/FALSE
*
* @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);
Copy link
Contributor

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.

Copy link
Contributor Author

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.


END; // RSA

END;
Loading
Loading