From f506b4b733993139c4f67007e461abaf6255acd6 Mon Sep 17 00:00:00 2001 From: Shubham Mittal <107728331+smittals2@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:54:14 -0700 Subject: [PATCH] Refactor RSA_METHOD and expand API (#1790) ### Issues `CryptoAlg-2482 ` ### Description of changes: This PR is the last in a series of three (prev PRs: [1st](https://github.com/aws/aws-lc/pull/1776), [2nd](https://github.com/aws/aws-lc/pull/1785)) to refactor and expand ENGINE, EC_KEY_METHOD and RSA_METHOD. This PR: 1. Expands the RSA_METHOD struct and changes the function signatures for encrypt, decrypt, sign_raw, and verify_raw to match OpenSSL 1.1.1. 2. Makes the struct definition for RSA_METHOD internal. This means static allocation of this struct is no longer possible. 3. Adds various functions to get/set, create, free, and manipulate RSA_METHOD ### Callout: This PR includes the commits for the other 2 PRs. They should be reviewed and merged before this one. ### Testing: Tested with various fields initialized in RSA_METHOD to ensure that custom functionality is correctly invoked. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --- crypto/engine/engine.c | 10 +- crypto/fipsmodule/ec/ec_test.cc | 26 +++ crypto/fipsmodule/ec/internal.h | 8 +- crypto/fipsmodule/rsa/internal.h | 69 ++++++- crypto/fipsmodule/rsa/rsa.c | 182 +++++++++++++++++- crypto/fipsmodule/rsa/rsa_impl.c | 19 +- crypto/rsa_extra/rsa_crypt.c | 33 +++- crypto/rsa_extra/rsa_test.cc | 136 +++++++++++++ include/openssl/engine.h | 21 +- include/openssl/rsa.h | 136 +++++++++---- .../ci/integration/run_openssh_integration.sh | 2 +- .../ci/integration/run_openvpn_integration.sh | 3 +- 12 files changed, 577 insertions(+), 68 deletions(-) diff --git a/crypto/engine/engine.c b/crypto/engine/engine.c index 95a1bbd988..e08dbb34fa 100644 --- a/crypto/engine/engine.c +++ b/crypto/engine/engine.c @@ -50,7 +50,10 @@ int ENGINE_set_RSA(ENGINE *engine, const RSA_METHOD *method) { } const RSA_METHOD *ENGINE_get_RSA(const ENGINE *engine) { - return engine->rsa_method; + if(engine) { + return engine->rsa_method;; + } + return NULL; } int ENGINE_set_EC(ENGINE *engine, const EC_KEY_METHOD *method) { @@ -64,7 +67,10 @@ int ENGINE_set_EC(ENGINE *engine, const EC_KEY_METHOD *method) { } const EC_KEY_METHOD *ENGINE_get_EC(const ENGINE *engine) { - return engine->eckey_method; + if(engine) { + return engine->eckey_method; + } + return NULL; } OPENSSL_DECLARE_ERROR_REASON(ENGINE, OPERATION_NOT_SUPPORTED) diff --git a/crypto/fipsmodule/ec/ec_test.cc b/crypto/fipsmodule/ec/ec_test.cc index ab86698f09..20d91a9018 100644 --- a/crypto/fipsmodule/ec/ec_test.cc +++ b/crypto/fipsmodule/ec/ec_test.cc @@ -2525,3 +2525,29 @@ TEST(ECTest, ECKEYMETHOD) { EC_KEY_METHOD_set_flags(ec_method, ECDSA_FLAG_OPAQUE); ASSERT_TRUE(EC_KEY_is_opaque(ec)); } + +TEST(ECTest, ECEngine) { + ENGINE *engine = ENGINE_new(); + ASSERT_TRUE(engine); + ASSERT_FALSE(ENGINE_get_EC(engine)); + + EC_KEY_METHOD *eng_funcs = EC_KEY_METHOD_new(NULL); + ASSERT_TRUE(eng_funcs); + EC_KEY_METHOD_set_sign(eng_funcs, NULL, NULL, ecdsa_sign_sig); + + ASSERT_TRUE(ENGINE_set_EC(engine, eng_funcs)); + ASSERT_TRUE(ENGINE_get_EC(engine)); + + EC_KEY *key = EC_KEY_new_method(engine); + ASSERT_TRUE(key); + + // Call custom Engine implementation + ECDSA_do_sign(NULL, 0, key); + ASSERT_STREQ(static_cast(EC_KEY_get_ex_data(key, 1)) + , "ecdsa_sign_sig"); + + EC_KEY_free(key); + ENGINE_free(engine); + EC_KEY_METHOD_free(eng_funcs); +} + diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h index 649911aad4..86e7dfb202 100644 --- a/crypto/fipsmodule/ec/internal.h +++ b/crypto/fipsmodule/ec/internal.h @@ -763,14 +763,16 @@ struct ec_key_method_st { // AWS-LC doesn't support custom values for EC_KEY operations // as of now. |k_inv| and |r| must be NULL parameters. // The |type| parameter is ignored in OpenSSL, we pass in zero for it. - // |sign| is invoked in |ECDSA_sign|. + // The default behavior for |sign| is implemented in |ECDSA_sign|. If custom + // functionality is provided, |sign| will be invoked within |ECDSA_sign|. int (*sign)(int type, const uint8_t *digest, int digest_len, uint8_t *sig, unsigned int *siglen, const BIGNUM *k_inv, const BIGNUM *r, EC_KEY *eckey); // AWS-LC doesn't support custom values for EC_KEY operations - // as of now. |k_inv| and |r| must be NULL parameters. |sign_sig| is - // invoked in |ECDSA_do_sign|. + // as of now. |k_inv| and |r| must be NULL parameters. The default behavior + // for |sign_sig| is implemented in |ECDSA_do_sign|. If custom functionality + // is provided, |sign_sig| will be invoked within |ECDSA_do_sign|. ECDSA_SIG *(*sign_sig)(const uint8_t *digest, int digest_len, const BIGNUM *in_kinv, const BIGNUM *in_r, EC_KEY *eckey); diff --git a/crypto/fipsmodule/rsa/internal.h b/crypto/fipsmodule/rsa/internal.h index 1e09f1400a..9870d02285 100644 --- a/crypto/fipsmodule/rsa/internal.h +++ b/crypto/fipsmodule/rsa/internal.h @@ -69,6 +69,71 @@ extern "C" { typedef struct bn_blinding_st BN_BLINDING; +struct rsa_meth_st { + void *app_data; + + int (*init)(RSA *rsa); + int (*finish)(RSA *rsa); + + // size returns the size of the RSA modulus in bytes. + size_t (*size)(const RSA *rsa); + + // Set via |RSA_meth_set_sign|. The default behavior for |sign| is + // implemented in |RSA_sign|. If custom functionality is provided, |sign| + // will be invoked within |RSA_sign|. + int (*sign)(int type, const uint8_t *m, unsigned int m_length, + uint8_t *sigret, unsigned int *siglen, const RSA *rsa); + + // Set via |RSA_meth_set_priv_enc|. |sign_raw| is equivalent to the + // |priv_enc| field of OpenSSL's |RSA_METHOD| struct. The default behavior + // for |sign_raw| is implemented in |RSA_sign_raw|. If custom + // functionality is provided, |sign_raw| will be invoked within + // |RSA_sign_raw|. + int (*sign_raw)(int max_out, const uint8_t *in, uint8_t *out, RSA *rsa, + int padding); + + // Set via |RSA_meth_set_pub_dec|. |verify_raw| is equivalent to the + // |pub_dec| field of OpenSSL's |RSA_METHOD| struct. The default behavior + // for |verify_raw| is implemented in |RSA_verify_raw|. If custom + // functionality is provided, |verify_raw| will be invoked within + // |RSA_verify_raw|. + int (*verify_raw)(int max_out, const uint8_t *in, uint8_t *out, RSA *rsa, + int padding); + + // Set via |RSA_meth_set_priv_dec|. |decrypt| is equivalent to the + // |priv_dec| field of OpenSSL's |RSA_METHOD| struct. The default behavior + // for |decrypt| is implemented in |RSA_decrypt|. If custom + // functionality is provided, |decrypt| will be invoked within + // |RSA_decrypt|. + int (*decrypt)(int max_out, const uint8_t *in, uint8_t *out, RSA *rsa, + int padding); + + // Set via |RSA_meth_set_pub_enc|. |encrypt| is equivalent to the + // |pub_enc| field of OpenSSL's |RSA_METHOD| struct. The default behavior + // for |encrypt| is implemented in |RSA_encrypt|. If custom + // functionality is provided, |encrypt| will be invoked within + // |RSA_encrypt|. + int (*encrypt)(int max_out, const uint8_t *in, uint8_t *out, RSA *rsa, + int padding); + + // private_transform takes a big-endian integer from |in|, calculates the + // d'th power of it, modulo the RSA modulus and writes the result as a + // big-endian integer to |out|. Both |in| and |out| are |len| bytes long and + // |len| is always equal to |RSA_size(rsa)|. If the result of the transform + // can be represented in fewer than |len| bytes, then |out| must be zero + // padded on the left. + // + // It returns one on success and zero otherwise. + // + // RSA decrypt and sign operations will call this, thus an ENGINE might wish + // to override it in order to avoid having to implement the padding + // functionality demanded by those, higher level, operations. + int (*private_transform)(RSA *rsa, uint8_t *out, const uint8_t *in, + size_t len); + + int flags; +}; + struct rsa_st { const RSA_METHOD *meth; @@ -128,10 +193,6 @@ struct rsa_st { // Default implementations of RSA operations. -// RSA_default_method returns a zero initialized |RSA_METHOD| object. The -// wrapper functions will select the appropriate |rsa_default_*| implementation. -const RSA_METHOD *RSA_default_method(void); - size_t rsa_default_size(const RSA *rsa); int rsa_default_sign_raw(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, diff --git a/crypto/fipsmodule/rsa/rsa.c b/crypto/fipsmodule/rsa/rsa.c index 1beab4b305..6134c24bc9 100644 --- a/crypto/fipsmodule/rsa/rsa.c +++ b/crypto/fipsmodule/rsa/rsa.c @@ -221,7 +221,7 @@ RSA *RSA_new_method(const ENGINE *engine) { } if (rsa->meth == NULL) { - rsa->meth = (RSA_METHOD *) RSA_default_method(); + rsa->meth = (RSA_METHOD *) RSA_get_default_method(); } rsa->references = 1; @@ -448,12 +448,159 @@ int RSA_set0_crt_params(RSA *rsa, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) { return 1; } +RSA_METHOD *RSA_meth_new(const char *name, int flags) { + RSA_METHOD *meth = OPENSSL_zalloc(sizeof(*meth)); + + if (meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_MALLOC_FAILURE); + return NULL; + } + + if (flags == RSA_FLAG_OPAQUE) { + meth->flags = flags; + } + return meth; +} + +int RSA_set_method(RSA *rsa, const RSA_METHOD *meth) { + if(rsa == NULL || meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + rsa->meth = meth; + return 1; +} + +const RSA_METHOD *RSA_get_method(const RSA *rsa) { + if(rsa == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + return rsa->meth; +} + +void RSA_meth_free(RSA_METHOD *meth) +{ + if (meth != NULL) { + OPENSSL_free(meth); + } +} + +int RSA_meth_set_init(RSA_METHOD *meth, int (*init) (RSA *rsa)) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->init = init; + return 1; +} + +int RSA_meth_set_finish(RSA_METHOD *meth, int (*finish) (RSA *rsa)) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->finish = finish; + return 1; +} + +int RSA_meth_set_priv_dec(RSA_METHOD *meth, + int (*priv_dec) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->decrypt = priv_dec; + return 1; +} + +int RSA_meth_set_priv_enc(RSA_METHOD *meth, + int (*priv_enc) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->sign_raw = priv_enc; + return 1; +} + +int RSA_meth_set_pub_dec(RSA_METHOD *meth, + int (*pub_dec) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->verify_raw = pub_dec; + return 1; +} + +int RSA_meth_set_pub_enc(RSA_METHOD *meth, + int (*pub_enc) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->encrypt = pub_enc; + return 1; +} + +int RSA_meth_set0_app_data(RSA_METHOD *meth, void *app_data) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->app_data = app_data; + return 1; +} + +int RSA_meth_set_sign(RSA_METHOD *meth, int (*sign) (int type, + const unsigned char *m, unsigned int m_length, unsigned char *sigret, + unsigned int *siglen, const RSA *rsa)) { + if(meth == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + + meth->sign = sign; + return 1; +} + static int rsa_sign_raw_no_self_test(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, int padding) { SET_DIT_AUTO_DISABLE; - if (rsa->meth->sign_raw) { - return rsa->meth->sign_raw(rsa, out_len, out, max_out, in, in_len, padding); + if (rsa->meth && rsa->meth->sign_raw) { + // In OpenSSL, the RSA_METHOD |sign_raw| or |priv_enc| operation does + // not directly take and initialize an |out_len| parameter. Instead, it + // returns the size of the encrypted data or a negative number for error. + // Our wrapping functions like |RSA_sign_raw| diverge from this paradigm + // and expect an |out_len| parameter. To remain compatible with this new + // paradigm and OpenSSL, we initialize |out_len| based on the return value + // here. + int ret = rsa->meth->sign_raw((int)max_out, in, out, rsa, padding); + if(ret < 0) { + *out_len = 0; + return 0; + } + *out_len = ret; + return 1; } return rsa_default_sign_raw(rsa, out_len, out, max_out, in, in_len, padding); @@ -470,7 +617,8 @@ int RSA_sign_raw(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, unsigned RSA_size(const RSA *rsa) { SET_DIT_AUTO_DISABLE; - size_t ret = rsa->meth->size ? rsa->meth->size(rsa) : rsa_default_size(rsa); + size_t ret = (rsa->meth && rsa->meth->size) ? + rsa->meth->size(rsa) : rsa_default_size(rsa); // RSA modulus sizes are bounded by |BIGNUM|, which must fit in |unsigned|. // // TODO(https://crbug.com/boringssl/516): Should we make this return |size_t|? @@ -693,7 +841,7 @@ int RSA_add_pkcs1_prefix(uint8_t **out_msg, size_t *out_msg_len, int rsa_sign_no_self_test(int hash_nid, const uint8_t *digest, size_t digest_len, uint8_t *out, unsigned *out_len, RSA *rsa) { - if (rsa->meth->sign) { + if (rsa->meth && rsa->meth->sign) { if (!rsa_check_digest_size(hash_nid, digest_len)) { return 0; } @@ -890,7 +1038,7 @@ int RSA_verify_pss_mgf1(RSA *rsa, const uint8_t *digest, size_t digest_len, int rsa_private_transform_no_self_test(RSA *rsa, uint8_t *out, const uint8_t *in, size_t len) { - if (rsa->meth->private_transform) { + if (rsa->meth && rsa->meth->private_transform) { return rsa->meth->private_transform(rsa, out, in, len); } @@ -906,12 +1054,32 @@ int rsa_private_transform(RSA *rsa, uint8_t *out, const uint8_t *in, int RSA_flags(const RSA *rsa) { SET_DIT_AUTO_DISABLE; + if (rsa == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + return rsa->flags; } +void RSA_set_flags(RSA *rsa, int flags) { + SET_DIT_AUTO_DISABLE; + if (rsa == NULL) { + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return; + } + + rsa->flags |= flags; +} + int RSA_test_flags(const RSA *rsa, int flags) { SET_DIT_AUTO_DISABLE; - return rsa->flags & flags; + if (rsa) { + return rsa->flags & flags; + } + + OPENSSL_PUT_ERROR(RSA, ERR_R_PASSED_NULL_PARAMETER); + return 0; } int RSA_blinding_on(RSA *rsa, BN_CTX *ctx) { diff --git a/crypto/fipsmodule/rsa/rsa_impl.c b/crypto/fipsmodule/rsa/rsa_impl.c index 6114b4eed3..f9d2c81411 100644 --- a/crypto/fipsmodule/rsa/rsa_impl.c +++ b/crypto/fipsmodule/rsa/rsa_impl.c @@ -412,6 +412,23 @@ static int mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx); int rsa_verify_raw_no_self_test(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, int padding) { + if(rsa->meth && rsa->meth->verify_raw) { + // In OpenSSL, the RSA_METHOD |verify_raw| or |pub_dec| operation does + // not directly take and initialize an |out_len| parameter. Instead, it + // returns the size of the recovered plaintext or negative number for error. + // Our wrapping functions like |RSA_verify_raw| diverge from this paradigm + // and expect an |out_len| parameter. To remain compatible with this new + // paradigm and OpenSSL, we initialize |out_len| based on the return value + // here. + int ret = rsa->meth->verify_raw((int)max_out, in, out, rsa, padding); + if(ret < 0) { + *out_len = 0; + return 0; + } + *out_len = ret; + return 1; + } + if (rsa->n == NULL || rsa->e == NULL) { OPENSSL_PUT_ERROR(RSA, RSA_R_VALUE_MISSING); return 0; @@ -1273,7 +1290,7 @@ int RSA_generate_key_fips(RSA *rsa, int bits, BN_GENCB *cb) { return ret; } -DEFINE_METHOD_FUNCTION(RSA_METHOD, RSA_default_method) { +DEFINE_METHOD_FUNCTION(RSA_METHOD, RSA_get_default_method) { // All of the methods are NULL to make it easier for the compiler/linker to // drop unused functions. The wrapper functions will select the appropriate // |rsa_default_*| implementation. diff --git a/crypto/rsa_extra/rsa_crypt.c b/crypto/rsa_extra/rsa_crypt.c index 220e81c7d1..62e34f9714 100644 --- a/crypto/rsa_extra/rsa_crypt.c +++ b/crypto/rsa_extra/rsa_crypt.c @@ -385,6 +385,23 @@ int RSA_private_encrypt(size_t flen, const uint8_t *from, uint8_t *to, RSA *rsa, int RSA_encrypt(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, int padding) { + if(rsa->meth && rsa->meth->encrypt) { + // In OpenSSL, the RSA_METHOD |encrypt| or |pub_enc| operation does not + // directly take and initialize an |out_len| parameter. Instead, it returns + // the number of bytes written to |out| or a negative number for error. + // Our wrapping functions like |RSA_encrypt| diverge from this paradigm and + // expect an |out_len| parameter. To remain compatible with this new + // paradigm and OpenSSL, we initialize |out_len| based on the return value + // here. + int ret = rsa->meth->encrypt((int)max_out, in, out, rsa, padding); + if(ret < 0) { + *out_len = 0; + return 0; + } + *out_len = ret; + return 1; + } + if (rsa->n == NULL || rsa->e == NULL) { OPENSSL_PUT_ERROR(RSA, RSA_R_VALUE_MISSING); return 0; @@ -541,8 +558,20 @@ static int rsa_default_decrypt(RSA *rsa, size_t *out_len, uint8_t *out, int RSA_decrypt(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, int padding) { - if (rsa->meth->decrypt) { - return rsa->meth->decrypt(rsa, out_len, out, max_out, in, in_len, padding); + if (rsa->meth && rsa->meth->decrypt) { + // In OpenSSL, the RSA_METHOD |decrypt| operation does not directly take + // and initialize an |out_len| parameter. Instead, it returns the number + // of bytes written to |out| or a negative number for error. Our wrapping + // functions like |RSA_decrypt| diverge from this paradigm and expect + // an |out_len| parameter. To remain compatible with this new paradigm and + // OpenSSL, we initialize |out_len| based on the return value here. + int ret = rsa->meth->decrypt((int)max_out, in, out, rsa, padding); + if(ret < 0) { + *out_len = 0; + return 0; + } + *out_len = ret; + return 1; } return rsa_default_decrypt(rsa, out_len, out, max_out, in, in_len, padding); diff --git a/crypto/rsa_extra/rsa_test.cc b/crypto/rsa_extra/rsa_test.cc index aeb5a821ce..66de5bcc1f 100644 --- a/crypto/rsa_extra/rsa_test.cc +++ b/crypto/rsa_extra/rsa_test.cc @@ -65,6 +65,7 @@ #include #include #include +#include #include #include #include @@ -988,6 +989,141 @@ TEST(RSATest, CheckKey) { ASSERT_TRUE(BN_sub(rsa->iqmp, rsa->iqmp, rsa->p)); } +static int rsa_priv_enc(int max_out, const uint8_t *from, uint8_t *to, RSA *rsa, + int padding) { + RSA_set_ex_data(rsa, 0, (void*)"rsa_priv_enc"); + return 0; +} + +static int rsa_priv_dec(int max_out, const uint8_t *from, uint8_t *to, RSA *rsa, + int padding) { + RSA_set_ex_data(rsa, 0, (void*)"rsa_priv_dec"); + return 0; +} + +static int rsa_pub_enc(int max_out, const uint8_t *from, uint8_t *to, RSA *rsa, + int padding) { + RSA_set_ex_data(rsa, 0, (void*)"rsa_pub_enc"); + return 0; +} + +static int rsa_pub_dec(int max_out, const uint8_t *from, uint8_t *to, RSA *rsa, + int padding) { + RSA_set_ex_data(rsa, 0, (void*)"rsa_pub_dec"); + return 0; +} + +static int extkey_rsa_finish (RSA *rsa) { + const RSA_METHOD *meth = RSA_get_method(rsa); + RSA_meth_free((RSA_METHOD *)meth); + return 1; +} + +TEST(RSATest, RSAMETHOD) { + RSA *key = RSA_new(); + bssl::UniquePtr e(BN_new()); + ASSERT_TRUE(key); + ASSERT_TRUE(e); + ASSERT_TRUE(BN_set_word(e.get(), RSA_F4)); + + ASSERT_TRUE(RSA_generate_key_ex(key, 2048, e.get(), nullptr)); + ASSERT_TRUE(RSA_check_key(key)); + + // This implementation hides the key material specified by + // |RSA_METHOD_FLAG_NO_CHECK| + RSA_METHOD *rsa_meth = RSA_meth_new(NULL, RSA_METHOD_FLAG_NO_CHECK); + ASSERT_TRUE(rsa_meth); + + ASSERT_TRUE(RSA_meth_set_pub_enc(rsa_meth, rsa_pub_enc)); + ASSERT_TRUE(RSA_meth_set_pub_dec(rsa_meth, rsa_pub_dec)); + ASSERT_TRUE(RSA_meth_set_priv_enc(rsa_meth, rsa_priv_enc)); + ASSERT_TRUE(RSA_meth_set_priv_dec(rsa_meth, rsa_priv_dec)); + ASSERT_TRUE(RSA_meth_set_init(rsa_meth, nullptr)); + ASSERT_TRUE(RSA_meth_set_finish(rsa_meth, extkey_rsa_finish)); + ASSERT_TRUE(RSA_meth_set0_app_data(rsa_meth, nullptr)); + + ASSERT_TRUE(rsa_meth->decrypt && rsa_meth->encrypt && rsa_meth->sign_raw && + rsa_meth->verify_raw); + + // rsa_meth will now be freed with key when rsa_meth->finish is called + // in RSA_free + ASSERT_TRUE(RSA_set_method(key, rsa_meth)); + ASSERT_TRUE(RSA_is_opaque(key)); + + bssl::UniquePtr rsa_key(EVP_PKEY_new()); + ASSERT_TRUE(rsa_key.get()); + // key will now be freed with rsa_key + EVP_PKEY_assign_RSA(rsa_key.get(), key); + bssl::UniquePtr rsa_key_ctx(EVP_PKEY_CTX_new(rsa_key.get(), NULL)); + ASSERT_TRUE(rsa_key_ctx.get()); + + // Encrypt Decrypt Operations (pub_enc & priv_dec) + size_t out_len = EVP_PKEY_size(rsa_key.get()); + uint8_t in, out; + ASSERT_TRUE(EVP_PKEY_encrypt_init(rsa_key_ctx.get())); + ASSERT_TRUE(EVP_PKEY_encrypt(rsa_key_ctx.get(), &out, &out_len, &in, 0)); + // Custom func return 0 since they don't write any data to out + ASSERT_EQ(out_len, (size_t)0); + ASSERT_STREQ(static_cast(RSA_get_ex_data(key, 0)) + , "rsa_pub_enc"); + + // Update before passing into next operation + out_len = EVP_PKEY_size(rsa_key.get()); + ASSERT_TRUE(EVP_PKEY_decrypt_init(rsa_key_ctx.get())); + ASSERT_TRUE(EVP_PKEY_decrypt(rsa_key_ctx.get(), &out, &out_len, &in, 0)); + // Custom func return 0 since they don't write any data to out + ASSERT_EQ(out_len, (size_t)0); + ASSERT_STREQ(static_cast(RSA_get_ex_data(key, 0)) + , "rsa_priv_dec"); + + // Update before passing into next operation + out_len = EVP_PKEY_size(rsa_key.get()); + ASSERT_TRUE(EVP_PKEY_verify_recover_init(rsa_key_ctx.get())); + ASSERT_TRUE(EVP_PKEY_verify_recover(rsa_key_ctx.get(), &out, &out_len, + nullptr, 0)); + // Custom func return 0 since they don't write any data to out + ASSERT_EQ(out_len, (size_t)0); + ASSERT_STREQ(static_cast(RSA_get_ex_data(key, 0)) + , "rsa_pub_dec"); + + // Update before passing into next operation + out_len = EVP_PKEY_size(rsa_key.get()); + // This operation is not plumbed through EVP_PKEY API in OpenSSL or AWS-LC + ASSERT_TRUE(RSA_sign_raw(key, &out_len, &out, 0, nullptr, 0, 0)); + // Custom func return 0 since they don't write any data to out + ASSERT_EQ(out_len, (size_t)0); + ASSERT_STREQ(static_cast(RSA_get_ex_data(key, 0)) + , "rsa_priv_enc"); +} + +TEST(RSATest, RSAEngine) { + ENGINE *engine = ENGINE_new(); + ASSERT_TRUE(engine); + ASSERT_FALSE(ENGINE_get_RSA(engine)); + + RSA_METHOD *eng_funcs = RSA_meth_new(NULL, 0); + ASSERT_TRUE(eng_funcs); + ASSERT_TRUE(RSA_meth_set_priv_dec(eng_funcs, rsa_priv_dec)); + + ASSERT_TRUE(ENGINE_set_RSA(engine, eng_funcs)); + ASSERT_TRUE(ENGINE_get_RSA(engine)); + + RSA *key = RSA_new_method(engine); + ASSERT_TRUE(key); + + size_t out_len = 16; + uint8_t in, out; + // Call custom Engine implementation + ASSERT_TRUE(RSA_decrypt(key, &out_len, &out, out_len, &in, 0, 0)); + ASSERT_EQ(out_len, (size_t)0); + ASSERT_STREQ(static_cast(RSA_get_ex_data(key, 0)) + , "rsa_priv_dec"); + + RSA_free(key); + ENGINE_free(engine); + RSA_meth_free(eng_funcs); +} + #if !defined(AWSLC_FIPS) TEST(RSATest, KeygenFail) { diff --git a/include/openssl/engine.h b/include/openssl/engine.h index beaca578a3..757ed3cbcb 100644 --- a/include/openssl/engine.h +++ b/include/openssl/engine.h @@ -27,9 +27,8 @@ extern "C" { // be overridden via a callback. This can be used, for example, to implement an // RSA* that forwards operations to a hardware module. // -// Methods are reference counted but |ENGINE|s are not. When creating a method, -// you should zero the whole structure and fill in the function pointers that -// you wish before setting it on an |ENGINE|. Any functions pointers that +// Default Methods are zero initialized. You should set the function pointers +// that you wish before setting it on an |ENGINE|. Any functions pointers that // are NULL indicate that the default behaviour should be used. @@ -46,20 +45,24 @@ OPENSSL_EXPORT int ENGINE_free(ENGINE *engine); // Method accessors. // -// Method accessors take a method pointer and set it on the |ENGINE| object. -// AWS-LC does not take ownership of the |method| pointer. The consumer -// must free the |method| pointer after all objects referencing it are +// The setter functions do not take ownership of the |method| pointer. The +// consumer must free the |method| pointer after all objects referencing it are // freed. -// -// Set functions return one on success and zero for failure when -// |engine| is NULL. +// ENGINE_set_RSA takes a |method| pointer and sets it on the |ENGINE| object. +// Returns one on success and zero for failure when |engine| is NULL. OPENSSL_EXPORT int ENGINE_set_RSA(ENGINE *engine, const RSA_METHOD *method); +// ENGINE_get_RSA returns the meth field of |engine|. If |engine| is NULL, +// function returns NULL. OPENSSL_EXPORT const RSA_METHOD *ENGINE_get_RSA(const ENGINE *engine); +// ENGINE_set_EC takes a |method| pointer and sets it on the |ENGINE| object. +// Returns one on success and zero for failure when |engine| is NULL. OPENSSL_EXPORT int ENGINE_set_EC(ENGINE *engine, const EC_KEY_METHOD *method); +// ENGINE_get_EC returns the meth field of |engine|. If |engine| is NULL, +// function returns NULL. OPENSSL_EXPORT const EC_KEY_METHOD *ENGINE_get_EC(const ENGINE *engine); diff --git a/include/openssl/rsa.h b/include/openssl/rsa.h index fc38e901c4..726f19d4ac 100644 --- a/include/openssl/rsa.h +++ b/include/openssl/rsa.h @@ -215,6 +215,94 @@ OPENSSL_EXPORT int RSA_set0_factors(RSA *rsa, BIGNUM *p, BIGNUM *q); OPENSSL_EXPORT int RSA_set0_crt_params(RSA *rsa, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp); +// RSA_METHOD functions + +// RSA_get_default_method returns a zero initialized |RSA_METHOD| object. The +// wrapper functions will select the appropriate |rsa_default_*| implementation. +OPENSSL_EXPORT const RSA_METHOD *RSA_get_default_method(void); + +// RSA_meth_new returns a zero-initialized |RSA_METHOD| object. It sets +// |flags| on the object. Currently, only |RSA_FLAG_OPAQUE| can be set on +// the method structure. The |name| parameter is currently ignored and +// is part of the function signature for OpenSSL compatibility. +OPENSSL_EXPORT RSA_METHOD *RSA_meth_new(const char *name, int flags); + +// RSA_set_method sets |meth| on |rsa|. Returns one on success and zero +// on failure. +OPENSSL_EXPORT int RSA_set_method(RSA *rsa, const RSA_METHOD *meth); + +// RSA_get_method returns the |RSA_METHOD| object associated with |rsa|. +OPENSSL_EXPORT const RSA_METHOD *RSA_get_method(const RSA *rsa); + +// RSA_meth_free frees the memory associated with |meth| +OPENSSL_EXPORT void RSA_meth_free(RSA_METHOD *meth); + +// RSA_METHOD setters +// The following functions set the corresponding fields on |meth|. They return +// one on success and zero on failure. + +// RSA_meth_set_init sets |init| on |meth|. |init| should return one on +// success and zero on failure. +OPENSSL_EXPORT int RSA_meth_set_init(RSA_METHOD *meth, int (*init) (RSA *rsa)); + +// RSA_meth_set_finish sets |finish| on |meth|. The |finish| function +// is called in |RSA_free| before freeing the key. |finish| should return +// one on success and zero on failure. +OPENSSL_EXPORT int RSA_meth_set_finish(RSA_METHOD *meth, + int (*finish) (RSA *rsa)); + +// RSA_meth_set_priv_dec sets |priv_dec| on |meth|. |priv_dec| should decrypt +// |max_out| bytes at |from| using the private key |rsa| and store the plaintext +// in |to|. |priv_dec| should return the size of the recovered plaintext or a +// negative number on error. +OPENSSL_EXPORT int RSA_meth_set_priv_dec(RSA_METHOD *meth, + int (*priv_dec) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)); + +// RSA_meth_set_priv_enc sets |priv_enc| on |meth|. |priv_enc| should sign +// |max_out| bytes at |from| using the private key |rsa| and store the +// signature in |to|. |priv_enc| should return the size of the signature or a +// negative number for error. +OPENSSL_EXPORT int RSA_meth_set_priv_enc(RSA_METHOD *meth, + int (*priv_enc) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)); + +// RSA_meth_set_pub_dec sets |pub_dec| on |meth|. |pub_dec| should recover the +// |max_out| bytes of the message digest at |from| using the signer's public +// key |rsa| and store it in |to|. |pub_dec| should return the size of the +// recovered message digest or a negative number on error. +OPENSSL_EXPORT int RSA_meth_set_pub_dec(RSA_METHOD *meth, + int (*pub_dec) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)); + +// RSA_meth_set_pub_enc sets |pub_enc| on |meth|. |pub_enc| should encrypt +// |max_out| bytes at |from| using the public key |rsa| and stores the +// ciphertext in |to|. |pub_enc| should return the size of the encrypted data +// or a negative number on error. +OPENSSL_EXPORT int RSA_meth_set_pub_enc(RSA_METHOD *meth, + int (*pub_enc) (int max_out, const uint8_t *from, + uint8_t *to, RSA *rsa, + int padding)); + +// RSA_meth_set0_app_data sets |app_data| on |meth|. Although set0 functions +// generally take ownership in AWS-LC, to maintain OpenSSL compatibility, +// this function does not. It is the consumers responsibility to free +// |app_data|. +OPENSSL_EXPORT int RSA_meth_set0_app_data(RSA_METHOD *meth, void *app_data); + + +// RSA_meth_set_sign sets |sign| on |meth|. The function |sign| should return +// one on success and zero on failure. +OPENSSL_EXPORT int RSA_meth_set_sign(RSA_METHOD *meth, + int (*sign) (int type, + const unsigned char *m, + unsigned int m_length, + unsigned char *sigret, + unsigned int *siglen, const RSA *rsa)); + // Key generation. @@ -728,10 +816,21 @@ OPENSSL_EXPORT void *RSA_get_ex_data(const RSA *rsa, int idx); #define RSA_METHOD_FLAG_NO_CHECK RSA_FLAG_OPAQUE +// RSAerr allows consumers to add an error for a given function |f| and reason +// |r|. This macro is added in for OpenSSL compatibility. To avoid exposing +// internals, we ignore the |f| parameter. The |r| parameter is passed into +// |OPENSSL_PUT_ERROR|. +#define RSAerr(f,r) OPENSSL_PUT_ERROR(RSA, r); + // RSA_flags returns the flags for |rsa|. These are a bitwise OR of |RSA_FLAG_*| // constants. OPENSSL_EXPORT int RSA_flags(const RSA *rsa); +// RSA_set_flags sets the flags in the |flags| parameter on the |RSA| +// object. Multiple flags can be passed in one go (bitwise ORed together). +// Any flags that are already set are left set. +OPENSSL_EXPORT void RSA_set_flags(RSA *rsa, int flags); + // RSA_test_flags returns the subset of flags in |flags| which are set in |rsa|. OPENSSL_EXPORT int RSA_test_flags(const RSA *rsa, int flags); @@ -838,43 +937,6 @@ OPENSSL_EXPORT const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *rsa); OPENSSL_EXPORT RSA *RSA_new_method_no_e(const ENGINE *engine, const BIGNUM *n); -struct rsa_meth_st { - void *app_data; - - int (*init)(RSA *rsa); - int (*finish)(RSA *rsa); - - // size returns the size of the RSA modulus in bytes. - size_t (*size)(const RSA *rsa); - - int (*sign)(int type, const uint8_t *m, unsigned int m_length, - uint8_t *sigret, unsigned int *siglen, const RSA *rsa); - - // These functions mirror the |RSA_*| functions of the same name. - int (*sign_raw)(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, - const uint8_t *in, size_t in_len, int padding); - int (*decrypt)(RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, - const uint8_t *in, size_t in_len, int padding); - - // private_transform takes a big-endian integer from |in|, calculates the - // d'th power of it, modulo the RSA modulus and writes the result as a - // big-endian integer to |out|. Both |in| and |out| are |len| bytes long and - // |len| is always equal to |RSA_size(rsa)|. If the result of the transform - // can be represented in fewer than |len| bytes, then |out| must be zero - // padded on the left. - // - // It returns one on success and zero otherwise. - // - // RSA decrypt and sign operations will call this, thus an ENGINE might wish - // to override it in order to avoid having to implement the padding - // functionality demanded by those, higher level, operations. - int (*private_transform)(RSA *rsa, uint8_t *out, const uint8_t *in, - size_t len); - - int flags; -}; - - #if defined(__cplusplus) } // extern C diff --git a/tests/ci/integration/run_openssh_integration.sh b/tests/ci/integration/run_openssh_integration.sh index d679cf3bf3..9c8ffc53e3 100755 --- a/tests/ci/integration/run_openssh_integration.sh +++ b/tests/ci/integration/run_openssh_integration.sh @@ -52,7 +52,7 @@ function openssh_build() { # The RSA_meth_XXX functions are not implemented by AWS-LC, and the implementation provided by OpenSSH also doesn't compile for us. # Fortunately, these functions are only needed for pkcs11 support, which is disabled for our build. # See: https://github.com/openssh/openssh-portable/pull/385 - export CFLAGS="-DAWS_LC_INTERNAL_IGNORE_BN_SET_FLAGS=1 -DHAVE_RSA_METH_FREE=1 -DHAVE_RSA_METH_DUP=1 -DHAVE_RSA_METH_SET1_NAME=1 -DHAVE_RSA_METH_SET_PRIV_ENC=1 -DHAVE_RSA_METH_SET_PRIV_DEC=1" + export CFLAGS="-DAWS_LC_INTERNAL_IGNORE_BN_SET_FLAGS=1 -DHAVE_RSA_METH_FREE=1 -DHAVE_RSA_METH_DUP=1 -DHAVE_RSA_METH_SET1_NAME=1 -DHAVE_RSA_METH_GET_FINISH=1 -DHAVE_RSA_METH_SET_PRIV_ENC=1 -DHAVE_RSA_METH_SET_PRIV_DEC=1 -DHAVE_RSA_METH_SET_FINISH=1 " ./configure --with-ssl-dir="${AWS_LC_INSTALL_FOLDER}" --prefix="${OPENSSH_INSTALL_FOLDER}" --disable-pkcs11 make -j "$NUM_CPU_THREADS" make install diff --git a/tests/ci/integration/run_openvpn_integration.sh b/tests/ci/integration/run_openvpn_integration.sh index 4f480c30ea..b1c66d1a3e 100755 --- a/tests/ci/integration/run_openvpn_integration.sh +++ b/tests/ci/integration/run_openvpn_integration.sh @@ -39,8 +39,7 @@ function openvpn_build() { --prefix="$OPENVPN_BUILD_PREFIX" \ --exec-prefix="$OPENVPN_BUILD_EPREFIX" \ --with-crypto-library=openssl \ - --with-openssl-engine=no \ - --disable-management + --with-openssl-engine=no make -j install