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

soter: Fix misuse of OpenSSL API for EC key generation (and more) #875

Merged
merged 78 commits into from
Nov 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
f5df81c
sign/verify: Introduce EVP_PKEY into soter_sign_ctx_t
ilammy Nov 5, 2021
d584886
verify/ec: Retain imported EVP_PKEY
ilammy Nov 5, 2021
dddb5de
verify/ec: Use EVP_PKEY directly
ilammy Nov 5, 2021
5b2fd63
verify/ec: Do not retain EVP_PKEY_CTX
ilammy Nov 5, 2021
b7a033d
verify/ec: Drop unused cleanup label
ilammy Nov 5, 2021
1db92dd
verify/ec: soter_sign_ctx_t initialized only once
ilammy Nov 6, 2021
a397d20
verify/ec: Check soter_sign_ctx_t for NULL
ilammy Nov 6, 2021
5e117ae
verify/ec: Cleaner check EVP_PKEY for NULL
ilammy Nov 6, 2021
8d78f4a
verify/ec: Check other arguments for NULL/zero too
ilammy Nov 6, 2021
afec504
verify/ec: Check EVP_PKEY types as well
ilammy Nov 6, 2021
7d26fa2
verify/ec: Simplify EVP_DigestVerifyFinal() check
ilammy Nov 6, 2021
d6fe9ef
verify/ec: Paranoid OpenSSL return value checks
ilammy Nov 6, 2021
cfac368
verify/rsa: Retain imported EVP_PKEY
ilammy Nov 5, 2021
75eed90
verify/rsa: Use EVP_PKEY directly
ilammy Nov 5, 2021
1aac793
verify/rsa: Do not retain EVP_PKEY_CTX
ilammy Nov 5, 2021
3e75937
verify/rsa: Drop unused cleanup label
ilammy Nov 5, 2021
74dc62d
verify/rsa: soter_sign_ctx_t initialized only once
ilammy Nov 6, 2021
7e10f13
verify/rsa: Check soter_sign_ctx_t for NULL
ilammy Nov 6, 2021
430f4d1
verify/rsa: Cleaner check EVP_PKEY for NULL
ilammy Nov 6, 2021
38528e7
verify/rsa: Check other arguments for NULL/zero too
ilammy Nov 6, 2021
20045ff
verify/rsa: Check EVP_PKEY types as well
ilammy Nov 6, 2021
641b2b4
verify/rsa: Correct EVP_DigestVerifyFinal() check
ilammy Nov 6, 2021
07857ef
verify/rsa: Paranoid OpenSSL return value checks
ilammy Nov 6, 2021
bd761cb
sign/rsa: Retain imported EVP_PKEY
ilammy Nov 5, 2021
71d8f54
sign/rsa: Return generated EVP_PKEY via out-parameter
ilammy Nov 5, 2021
958a2da
sign/rsa: Use EVP_PKEY directly
ilammy Nov 5, 2021
8fcebc7
sign/rsa: Local EVP_PKEY_CTX for keygen
ilammy Nov 6, 2021
249df47
sign/rsa: Introduce "soter_status_t res"
ilammy Nov 6, 2021
4309d10
sign/rsa: Introduce "err" label and code path
ilammy Nov 6, 2021
5057246
sign/rsa: Use "goto err" consistently
ilammy Nov 6, 2021
e776d38
sign/rsa: Remove lying comments
ilammy Nov 6, 2021
a17b904
sign/rsa: Initialize local variables
ilammy Nov 6, 2021
8dcf4cf
sign/rsa: Create EVP_PKEY_CTX only for key generation
ilammy Nov 6, 2021
0493e01
sign/rsa: Drop unused cleanup label
ilammy Nov 6, 2021
5ffd622
sign/rsa: Create EVP_PKEY for importing separately
ilammy Nov 6, 2021
c21361f
sign/rsa: Move EVP_PKEY allocation to key generation
ilammy Nov 6, 2021
53d8dd6
sign/rsa: Use EVP_PKEY_CTX_new_id() for key generation
ilammy Nov 6, 2021
2815d50
sign/rsa: soter_sign_ctx_t initialized only once
ilammy Nov 6, 2021
4301971
sign/rsa: Use "goto err" consistently
ilammy Nov 6, 2021
f5191ab
sign/rsa: Replace local variable with direct use
ilammy Nov 6, 2021
4e13e9e
sign/rsa: Move local variable declaration
ilammy Nov 6, 2021
e2f296f
sign/rsa: Check soter_sign_ctx_t for NULL
ilammy Nov 6, 2021
69e0640
sign/rsa: Check other arguments for NULL/zero too
ilammy Nov 6, 2021
8e8ed18
sign/rsa: Check EVP_PKEY types as well
ilammy Nov 6, 2021
818f84a
sign/rsa: Check EVP_PKEY_size() correctly
ilammy Nov 6, 2021
f1f3c64
sign/rsa: Pass EVP_PKEY directly to soter_rsa_export_key()
ilammy Nov 6, 2021
9c7323d
sign/rsa: Correct OpenSSL return value checks
ilammy Nov 6, 2021
464568b
sign/ec: Retain imported EVP_PKEY
ilammy Nov 6, 2021
1f2fe5f
sign/ec: Return generated EVP_PKEY via out-parameter
ilammy Nov 6, 2021
773a317
sign/ec: Use EVP_PKEY directly
ilammy Nov 6, 2021
a88c040
sign/ec: Local EVP_PKEY_CTX for keygen
ilammy Nov 6, 2021
18c01ff
sign/ec: Introduce "soter_status_t res"
ilammy Nov 6, 2021
5fa6b5d
sign/ec: Use "goto err" consistently
ilammy Nov 6, 2021
32e5cf6
sign/ec: Initialize local variables
ilammy Nov 6, 2021
da25170
sign/ec: Create EVP_PKEY_CTX only for key generation
ilammy Nov 6, 2021
c4f514b
sign/ec: Drop unused cleanup label
ilammy Nov 6, 2021
d55e305
sign/ec: Create EVP_PKEY for importing separately
ilammy Nov 6, 2021
86fb806
sign/ec: Move EVP_PKEY allocation to key generation
ilammy Nov 6, 2021
7dcc4ce
sign/ec: Drop redundant EVP_PKEY type check
ilammy Nov 6, 2021
5de6be2
sign/ec: Use EVP_PKEY_keygen() for key generation
ilammy Nov 6, 2021
e6fc385
sign/ec: Use separate EVP_PKEY_CTX for EVP_PKEY_paramgen()
ilammy Nov 6, 2021
108e666
sign/ec: Key parameters use distinct EVP_PKEY
ilammy Nov 6, 2021
fa65591
sign/ec: Use EVP_PKEY_CTX_new_id() for parameter generation
ilammy Nov 6, 2021
ddcb7e6
sign/ec: soter_sign_ctx_t initialized only once
ilammy Nov 6, 2021
7b1c40f
sign/ec: Use "goto err" consistently
ilammy Nov 6, 2021
f077bab
sign/ec: Replace local variable with direct use
ilammy Nov 6, 2021
c350c4e
sign/ec: Return resulting status code directly
ilammy Nov 6, 2021
a8dbfe9
sign/ec: Pull out a local variable
ilammy Nov 6, 2021
78e60b7
sign/ec: Flatten "if-else" level
ilammy Nov 6, 2021
bce1c76
sign/ec: Check soter_sign_ctx_t for NULL
ilammy Nov 6, 2021
1a10739
sign/ec: Check other arguments for NULL/zero too
ilammy Nov 6, 2021
14fca9b
sign/ec: Check EVP_PKEY types as well
ilammy Nov 6, 2021
c6cbfb5
sign/ec: Check EVP_PKEY_size() correctly
ilammy Nov 6, 2021
a611770
sign/ec: Pass EVP_PKEY directly to soter_ec_export_key()
ilammy Nov 6, 2021
6255590
sign/ec: Correct OpenSSL return value checks
ilammy Nov 6, 2021
da03406
sign/verify: Drop EVP_PKEY_CTX from soter_sign_ctx_t
ilammy Nov 6, 2021
0e5793a
changelog: Jot down a note about improvements
ilammy Nov 6, 2021
bd6a952
CI: Work around legacy Node.js 8.x issues
ilammy Nov 6, 2021
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
11 changes: 10 additions & 1 deletion .github/workflows/test-nodejs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,19 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: Check out code
uses: actions/checkout@v2
with:
submodules: true
- name: Install Themis Core
env:
NODE_VERSION: ${{ matrix.node-version }}
run: |
# Workaround for Node.js 8.x using OpenSSL 1.0.2 when Themis is built against OpenSSL 1.1
# https://github.com/cossacklabs/themis/issues/657
if [[ "$NODE_VERSION" = "8.x" ]]; then
export ENGINE=boringssl
fi
make
sudo make install
sudo -E make install
- name: Run test suite
run: |
echo Node.js: $(node --version)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ _Code:_
- Fixed cross-compilation on macOS by setting `ARCH` and `SDK` variables ([#849](https://github.com/cossacklabs/themis/pull/849)).
- Updated embedded BoringSSL to the latest version ([#812](https://github.com/cossacklabs/themis/pull/812)).
- Builds with OpenSSL 3.0 will result in a compilation error for the time being ([#872](https://github.com/cossacklabs/themis/pull/872)).
- Hardened EC/RSA key generation and Secure Message sign/verify code paths ([#875](https://github.com/cossacklabs/themis/pull/875)).

- **Android**

Expand Down
64 changes: 47 additions & 17 deletions src/soter/openssl/soter_ecdsa_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,59 @@
#include "soter/openssl/soter_engine.h"
#include "soter/soter_ec_key.h"

soter_status_t soter_ec_gen_key(EVP_PKEY_CTX* pkey_ctx)
soter_status_t soter_ec_gen_key(EVP_PKEY** ppkey)
{
EVP_PKEY* pkey;
EC_KEY* ec;
if (!pkey_ctx) {
soter_status_t res = SOTER_FAIL;
EVP_PKEY* param = NULL;
EVP_PKEY_CTX* param_ctx = NULL;
EVP_PKEY_CTX* pkey_ctx = NULL;

if (!ppkey) {
return SOTER_INVALID_PARAMETER;
}
pkey = EVP_PKEY_CTX_get0_pkey(pkey_ctx);
if (!pkey) {
return SOTER_INVALID_PARAMETER;

param_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
if (!param_ctx) {
res = SOTER_NO_MEMORY;
goto err;
}
if (EVP_PKEY_EC != EVP_PKEY_id(pkey)) {
return SOTER_INVALID_PARAMETER;

if (EVP_PKEY_paramgen_init(param_ctx) != 1) {
res = SOTER_FAIL;
goto err;
}
ec = EVP_PKEY_get0(pkey);
if (NULL == ec) {
return SOTER_INVALID_PARAMETER;
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(param_ctx, NID_X9_62_prime256v1) != 1) {
res = SOTER_FAIL;
goto err;
}
if (1 == EC_KEY_generate_key(ec)) {
return SOTER_SUCCESS;
if (EVP_PKEY_paramgen(param_ctx, &param) != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

my original preference was to have constants in comparison operators first, but if the current code style changed - I won't fight for it

res = SOTER_FAIL;
goto err;
}
return SOTER_FAIL;

pkey_ctx = EVP_PKEY_CTX_new(param, NULL);
if (!pkey_ctx) {
res = SOTER_NO_MEMORY;
goto err;
}

if (EVP_PKEY_keygen_init(pkey_ctx) != 1) {
res = SOTER_FAIL;
goto err;
}
if (EVP_PKEY_keygen(pkey_ctx, ppkey) != 1) {
res = SOTER_FAIL;
goto err;
}

res = SOTER_SUCCESS;

err:
EVP_PKEY_CTX_free(param_ctx);
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume these functions check for NULL internally, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I assume these functions check for NULL internally, right?

That is so. They follow the free() convention: NULL argument is a no-op.

EVP_PKEY_CTX_free(pkey_ctx);
EVP_PKEY_free(param);

return res;
}

soter_status_t soter_ec_import_key(EVP_PKEY* pkey, const void* key, const size_t key_length)
Expand Down Expand Up @@ -71,9 +102,8 @@ soter_status_t soter_ec_import_key(EVP_PKEY* pkey, const void* key, const size_t
return SOTER_INVALID_PARAMETER;
}

soter_status_t soter_ec_export_key(soter_sign_ctx_t* ctx, void* key, size_t* key_length, bool isprivate)
soter_status_t soter_ec_export_key(EVP_PKEY* pkey, void* key, size_t* key_length, bool isprivate)
{
EVP_PKEY* pkey = EVP_PKEY_CTX_get0_pkey(ctx->pkey_ctx);
if (!pkey) {
return SOTER_INVALID_PARAMETER;
}
Expand Down
4 changes: 2 additions & 2 deletions src/soter/openssl/soter_ecdsa_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
#include "soter/soter_ec_key.h"
#include "soter/soter_error.h"

soter_status_t soter_ec_gen_key(EVP_PKEY_CTX* pkey_ctx);
soter_status_t soter_ec_gen_key(EVP_PKEY** ppkey);
soter_status_t soter_ec_import_key(EVP_PKEY* pkey, const void* key, size_t key_length);
soter_status_t soter_ec_export_key(soter_sign_ctx_t* ctx, void* key, size_t* key_length, bool isprivate);
soter_status_t soter_ec_export_key(EVP_PKEY* pkey, void* key, size_t* key_length, bool isprivate);

#endif /* SOTER_OPENSSL_ECDSA_COMMON_H */
2 changes: 1 addition & 1 deletion src/soter/openssl/soter_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ struct soter_asym_ka_type {
};

struct soter_sign_ctx_type {
EVP_PKEY_CTX* pkey_ctx;
EVP_PKEY* pkey;
Copy link
Collaborator

Choose a reason for hiding this comment

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

correct me if I am wrong. as I understand, we stop storing EVP_KEY_CTX because if used only once for key generation and initialization and after that we don't use it directly, only to extract private/public key from this private structure. And now we store EVP_PKEY, which can store private and public keys, that are only used in all further function calls, yes?
and that is main reason, and we stop store more than we need data, correct?

Copy link
Collaborator Author

@ilammy ilammy Nov 11, 2021

Choose a reason for hiding this comment

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

we stop storing EVP_KEY_CTX because if used only once for key generation and initialization and after that we don't use it directly

Yes.

And now we store EVP_PKEY, which can store private and public keys, that are only used in all further function calls, yes?

Correct.

and that is main reason, and we stop store more than we need data, correct?

Yes and no. We don't need to free EVP_PKEY_CTX right away but we don't have to retain it either. That said, it is a bit more heavy data structure than EVP_PKEY, but not very much.

As you said, all following methods on soter_sign_ctx_t do not use EVP_PKEY_CTX. We only need it for key generation, which is performed exactly once on initialization (at least for soter_sign_ctx_t). After that we don't need the context around, only the keypair (or the public key alone). It's not possible to, say, generate a new keypair once again, like soter_asym_ka_alg_t allows. In that case it could be viable to retain a preconfigured EVP_PKEY_CTX that can be used for key generation again and again. However, even then I'd retain only generated parameters and create EVP_PKEY_CTX on demand.

The main motivation for removing EVP_PKEY_CTX is to prevent the flawed notion from spreading further. EVP_PKEY_CTX is not a general-purpose container for EVP_PKEY, but rather a transient helper. It matters how EVP_PKEY_CTX is created, what for it has been initialized, etc. It's much easier to check correctness of EVP_PKEY_CTX handling when it's constrained to one function, not retained in an object with multiple methods that can affect and reconfigure the context.

EVP_MD_CTX* md_ctx;
soter_sign_alg_t alg;
};
Expand Down
57 changes: 35 additions & 22 deletions src/soter/openssl/soter_rsa_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,51 +24,66 @@
#define SOTER_RSA_KEY_LENGTH 2048
#endif

soter_status_t soter_rsa_gen_key(EVP_PKEY_CTX* pkey_ctx)
soter_status_t soter_rsa_gen_key(EVP_PKEY** ppkey)
{
/* it is copy-paste from /src/soter/openssl/soter_asym_cipher.c */
Copy link
Contributor

Choose a reason for hiding this comment

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

it's not anymore? :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it's not anymore? :)

You know the story about the ship of Theseus?

Right now soter_asym_cipher.c does not even have generation in it. Whatever there was, this implementation does not really have much left in common with it, I think, so the comment only misleads the reader.

BIGNUM* pub_exp;
EVP_PKEY* pkey = EVP_PKEY_CTX_get0_pkey(pkey_ctx);
if (!pkey) {
soter_status_t res = SOTER_FAIL;
BIGNUM* pub_exp = NULL;
EVP_PKEY_CTX* pkey_ctx = NULL;

if (!ppkey) {
return SOTER_INVALID_PARAMETER;
}

if (EVP_PKEY_RSA != EVP_PKEY_id(pkey)) {
return SOTER_INVALID_PARAMETER;
pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!pkey_ctx) {
res = SOTER_NO_MEMORY;
goto err;
}

if (!EVP_PKEY_keygen_init(pkey_ctx)) {
return SOTER_INVALID_PARAMETER;
if (EVP_PKEY_keygen_init(pkey_ctx) != 1) {
res = SOTER_INVALID_PARAMETER;
goto err;
}

/* Although it seems that OpenSSL/LibreSSL use 0x10001 as default public exponent, we will set
* it explicitly just in case */
pub_exp = BN_new();
if (!pub_exp) {
return SOTER_NO_MEMORY;
res = SOTER_NO_MEMORY;
goto err;
}

if (!BN_set_word(pub_exp, RSA_F4)) {
BN_free(pub_exp);
return SOTER_FAIL;
res = SOTER_FAIL;
goto err;
}

/* Passing ownership over pub_exp to EVP_PKEY_CTX */
if (1 > EVP_PKEY_CTX_ctrl(pkey_ctx, -1, -1, EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP, 0, pub_exp)) {
BN_free(pub_exp);
return SOTER_FAIL;
res = SOTER_FAIL;
goto err;
}
pub_exp = NULL;

/* Override default key size for RSA key. Currently OpenSSL has default key size of 1024.
* LibreSSL has 2048. We will put 2048 explicitly */
if (1 > EVP_PKEY_CTX_ctrl(pkey_ctx, -1, -1, EVP_PKEY_CTRL_RSA_KEYGEN_BITS, SOTER_RSA_KEY_LENGTH, NULL)) {
return SOTER_FAIL;
res = SOTER_FAIL;
goto err;
}

if (!EVP_PKEY_keygen(pkey_ctx, &pkey)) {
return SOTER_FAIL;
if (EVP_PKEY_keygen(pkey_ctx, ppkey) != 1) {
res = SOTER_FAIL;
goto err;
}
return SOTER_SUCCESS;
/* end of copy-paste from /src/soter/openssl/soter_asym_cipher.c*/

res = SOTER_SUCCESS;

err:
BN_free(pub_exp);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
BN_free(pub_exp);
pub_exp = NULL;
BN_free(pub_exp);

shouldn't we also null the pub_exp?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

shouldn't we also null the pub_exp?

I don't think there is much point in doing that to a local variable on the cleanup code path. For a field – sure, that keeps invalid pointers out. But here the variable will cease to exist with return, so might as well spare the work.

EVP_PKEY_CTX_free(pkey_ctx);

return res;
}

soter_status_t soter_rsa_import_key(EVP_PKEY* pkey, const void* key, const size_t key_length)
Expand All @@ -94,10 +109,8 @@ soter_status_t soter_rsa_import_key(EVP_PKEY* pkey, const void* key, const size_
return SOTER_INVALID_PARAMETER;
}

soter_status_t soter_rsa_export_key(soter_sign_ctx_t* ctx, void* key, size_t* key_length, bool isprivate)
soter_status_t soter_rsa_export_key(EVP_PKEY* pkey, void* key, size_t* key_length, bool isprivate)
{
EVP_PKEY* pkey = EVP_PKEY_CTX_get0_pkey(ctx->pkey_ctx);

if (!pkey) {
return SOTER_INVALID_PARAMETER;
}
Expand Down
4 changes: 2 additions & 2 deletions src/soter/openssl/soter_rsa_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
#include "soter/soter_error.h"
#include "soter/soter_rsa_key.h"

soter_status_t soter_rsa_gen_key(EVP_PKEY_CTX* pkey_ctx);
soter_status_t soter_rsa_gen_key(EVP_PKEY** ppkey);
soter_status_t soter_rsa_import_key(EVP_PKEY* pkey, const void* key, size_t key_length);
soter_status_t soter_rsa_export_key(soter_sign_ctx_t* ctx, void* key, size_t* key_length, bool isprivate);
soter_status_t soter_rsa_export_key(EVP_PKEY* pkey, void* key, size_t* key_length, bool isprivate);

#endif /* SOTER_OPENSSL_RSA_COMMON_H */
Loading