Skip to content

Commit 275c750

Browse files
🔧 Replace deprecated OpenSSL functions (#191)
* do not use deprecated functions in ecdsa context * check ecdsa key on creation * add test * set compiler flags correctly * set compiler flags correctly attempt 2 Co-authored-by: Chris McArthur <prince.chrismc@gmail.com>
1 parent 55aaf31 commit 275c750

File tree

4 files changed

+261
-99
lines changed

4 files changed

+261
-99
lines changed

.github/workflows/ssl.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ jobs:
4747
label: ${{ matrix.openssl.name }}
4848
github_token: ${{ secrets.GITHUB_TOKEN }}
4949

50+
openssl-no-deprecated:
51+
runs-on: ubuntu-latest
52+
name: OpenSSL 3.0 No Deprecated
53+
steps:
54+
- uses: actions/checkout@v2
55+
- uses: lukka/get-cmake@latest
56+
- uses: ./.github/actions/install/gtest
57+
- uses: ./.github/actions/install/openssl
58+
with:
59+
version: "openssl-3.0.0"
60+
61+
- name: configure
62+
run: cmake . -DJWT_BUILD_TESTS=ON -DOPENSSL_ROOT_DIR=/tmp -DCMAKE_CXX_FLAGS="-DOPENSSL_NO_DEPRECATED_3_0=1" -DCMAKE_C_FLAGS="-DOPENSSL_NO_DEPRECATED_3_0=1"
63+
- run: make
64+
5065
libressl:
5166
runs-on: ubuntu-latest
5267
strategy:

include/jwt-cpp/jwt.h

Lines changed: 144 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ namespace jwt {
146146
create_mem_bio_failed,
147147
no_key_provided,
148148
invalid_key_size,
149-
invalid_key
149+
invalid_key,
150+
create_context_failed
150151
};
151152
/**
152153
* \brief Error category for ECDSA errors
@@ -165,6 +166,7 @@ namespace jwt {
165166
return "at least one of public or private key need to be present";
166167
case ecdsa_error::invalid_key_size: return "invalid key size";
167168
case ecdsa_error::invalid_key: return "invalid key";
169+
case ecdsa_error::create_context_failed: return "failed to create context";
168170
default: return "unknown ECDSA error";
169171
}
170172
}
@@ -186,7 +188,8 @@ namespace jwt {
186188
verifyupdate_failed,
187189
verifyfinal_failed,
188190
get_key_failed,
189-
set_rsa_pss_saltlen_failed
191+
set_rsa_pss_saltlen_failed,
192+
signature_encoding_failed
190193
};
191194
/**
192195
* \brief Error category for verification errors
@@ -211,6 +214,8 @@ namespace jwt {
211214
return "failed to verify signature: Could not get key";
212215
case signature_verification_error::set_rsa_pss_saltlen_failed:
213216
return "failed to verify signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed";
217+
case signature_verification_error::signature_encoding_failed:
218+
return "failed to verify signature: i2d_ECDSA_SIG failed";
214219
default: return "unknown signature verification error";
215220
}
216221
}
@@ -241,6 +246,7 @@ namespace jwt {
241246
rsa_private_encrypt_failed,
242247
get_key_failed,
243248
set_rsa_pss_saltlen_failed,
249+
signature_decoding_failed
244250
};
245251
/**
246252
* \brief Error category for signature generation errors
@@ -276,6 +282,8 @@ namespace jwt {
276282
return "failed to generate signature: Could not get key";
277283
case signature_generation_error::set_rsa_pss_saltlen_failed:
278284
return "failed to create signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed";
285+
case signature_generation_error::signature_decoding_failed:
286+
return "failed to create signature: d2i_ECDSA_SIG failed";
279287
default: return "unknown signature generation error";
280288
}
281289
}
@@ -995,21 +1003,21 @@ namespace jwt {
9951003
const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen)
9961004
: md(md), alg_name(std::move(name)), signature_length(siglen) {
9971005
if (!private_key.empty()) {
998-
auto epkey = helper::load_private_ec_key_from_string(private_key, private_key_password);
999-
pkey.reset(EVP_PKEY_get1_EC_KEY(epkey.get()), EC_KEY_free);
1006+
pkey = helper::load_private_ec_key_from_string(private_key, private_key_password);
1007+
check_private_key(pkey.get());
10001008
} else if (!public_key.empty()) {
1001-
auto epkey = helper::load_public_ec_key_from_string(public_key, public_key_password);
1002-
pkey.reset(EVP_PKEY_get1_EC_KEY(epkey.get()), EC_KEY_free);
1009+
pkey = helper::load_public_ec_key_from_string(public_key, public_key_password);
1010+
check_public_key(pkey.get());
10031011
} else {
10041012
throw ecdsa_exception(error::ecdsa_error::no_key_provided);
10051013
}
10061014
if (!pkey) throw ecdsa_exception(error::ecdsa_error::invalid_key);
1007-
size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get()));
1015+
1016+
size_t keysize = EVP_PKEY_bits(pkey.get());
10081017
if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521))
10091018
throw ecdsa_exception(error::ecdsa_error::invalid_key_size);
1010-
1011-
if (EC_KEY_check_key(pkey.get()) == 0) throw ecdsa_exception(error::ecdsa_error::invalid_key);
10121019
}
1020+
10131021
/**
10141022
* Sign jwt data
10151023
* \param data The data to sign
@@ -1018,17 +1026,122 @@ namespace jwt {
10181026
*/
10191027
std::string sign(const std::string& data, std::error_code& ec) const {
10201028
ec.clear();
1021-
const std::string hash = generate_hash(data, ec);
1022-
if (ec) return {};
1029+
#ifdef JWT_OPENSSL_1_0_0
1030+
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_destroy)> ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy);
1031+
#else
1032+
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free);
1033+
#endif
1034+
if (!ctx) {
1035+
ec = error::signature_generation_error::create_context_failed;
1036+
return {};
1037+
}
1038+
if (!EVP_DigestSignInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) {
1039+
ec = error::signature_generation_error::signinit_failed;
1040+
return {};
1041+
}
1042+
if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) {
1043+
ec = error::signature_generation_error::digestupdate_failed;
1044+
return {};
1045+
}
1046+
1047+
size_t len = 0;
1048+
if (!EVP_DigestSignFinal(ctx.get(), nullptr, &len)) {
1049+
ec = error::signature_generation_error::signfinal_failed;
1050+
return {};
1051+
}
1052+
std::string res(len, '\0');
1053+
if (!EVP_DigestSignFinal(ctx.get(), (unsigned char*)res.data(), &len)) {
1054+
ec = error::signature_generation_error::signfinal_failed;
1055+
return {};
1056+
}
10231057

1058+
res.resize(len);
1059+
return der_to_p1363_signature(res, ec);
1060+
}
1061+
1062+
/**
1063+
* Check if signature is valid
1064+
* \param data The data to check signature against
1065+
* \param signature Signature provided by the jwt
1066+
* \param ec Filled with details on error
1067+
*/
1068+
void verify(const std::string& data, const std::string& signature, std::error_code& ec) const {
1069+
ec.clear();
1070+
std::string der_signature = p1363_to_der_signature(signature, ec);
1071+
if (ec) { return; }
1072+
1073+
#ifdef JWT_OPENSSL_1_0_0
1074+
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_destroy)> ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy);
1075+
#else
1076+
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free);
1077+
#endif
1078+
if (!ctx) {
1079+
ec = error::signature_verification_error::create_context_failed;
1080+
return;
1081+
}
1082+
if (!EVP_DigestVerifyInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) {
1083+
ec = error::signature_verification_error::verifyinit_failed;
1084+
return;
1085+
}
1086+
if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) {
1087+
ec = error::signature_verification_error::verifyupdate_failed;
1088+
return;
1089+
}
1090+
1091+
auto res =
1092+
EVP_DigestVerifyFinal(ctx.get(), reinterpret_cast<const unsigned char*>(der_signature.data()),
1093+
static_cast<unsigned int>(der_signature.length()));
1094+
if (res == 0) {
1095+
ec = error::signature_verification_error::invalid_signature;
1096+
return;
1097+
}
1098+
if (res == -1) {
1099+
ec = error::signature_verification_error::verifyfinal_failed;
1100+
return;
1101+
}
1102+
}
1103+
/**
1104+
* Returns the algorithm name provided to the constructor
1105+
* \return algorithm's name
1106+
*/
1107+
std::string name() const { return alg_name; }
1108+
1109+
private:
1110+
static void check_public_key(EVP_PKEY* pkey) {
1111+
#ifdef JWT_OPENSSL_3_0
1112+
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> ctx(
1113+
EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free);
1114+
if (!ctx) { throw ecdsa_exception(error::ecdsa_error::create_context_failed); }
1115+
if (EVP_PKEY_public_check(ctx.get()) != 1) { throw ecdsa_exception(error::ecdsa_error::invalid_key); }
1116+
#else
1117+
std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)> eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free);
1118+
if (!eckey) { throw ecdsa_exception(error::ecdsa_error::invalid_key); }
1119+
if (EC_KEY_check_key(eckey.get()) == 0) throw ecdsa_exception(error::ecdsa_error::invalid_key);
1120+
#endif
1121+
}
1122+
1123+
static void check_private_key(EVP_PKEY* pkey) {
1124+
#ifdef JWT_OPENSSL_3_0
1125+
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> ctx(
1126+
EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free);
1127+
if (!ctx) { throw ecdsa_exception(error::ecdsa_error::create_context_failed); }
1128+
if (EVP_PKEY_private_check(ctx.get()) != 1) { throw ecdsa_exception(error::ecdsa_error::invalid_key); }
1129+
#else
1130+
std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)> eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free);
1131+
if (!eckey) { throw ecdsa_exception(error::ecdsa_error::invalid_key); }
1132+
if (EC_KEY_check_key(eckey.get()) == 0) throw ecdsa_exception(error::ecdsa_error::invalid_key);
1133+
#endif
1134+
}
1135+
1136+
std::string der_to_p1363_signature(const std::string& der_signature, std::error_code& ec) const {
1137+
const unsigned char* possl_signature = reinterpret_cast<const unsigned char*>(der_signature.data());
10241138
std::unique_ptr<ECDSA_SIG, decltype(&ECDSA_SIG_free)> sig(
1025-
ECDSA_do_sign(reinterpret_cast<const unsigned char*>(hash.data()), static_cast<int>(hash.size()),
1026-
pkey.get()),
1027-
ECDSA_SIG_free);
1139+
d2i_ECDSA_SIG(nullptr, &possl_signature, der_signature.length()), ECDSA_SIG_free);
10281140
if (!sig) {
1029-
ec = error::signature_generation_error::ecdsa_do_sign_failed;
1141+
ec = error::signature_generation_error::signature_decoding_failed;
10301142
return {};
10311143
}
1144+
10321145
#ifdef JWT_OPENSSL_1_0_0
10331146

10341147
auto rr = helper::bn2raw(sig->r);
@@ -1047,91 +1160,45 @@ namespace jwt {
10471160
return rr + rs;
10481161
}
10491162

1050-
/**
1051-
* Check if signature is valid
1052-
* \param data The data to check signature against
1053-
* \param signature Signature provided by the jwt
1054-
* \param ec Filled with details on error
1055-
*/
1056-
void verify(const std::string& data, const std::string& signature, std::error_code& ec) const {
1163+
std::string p1363_to_der_signature(const std::string& signature, std::error_code& ec) const {
10571164
ec.clear();
1058-
const std::string hash = generate_hash(data, ec);
1059-
if (ec) return;
10601165
auto r = helper::raw2bn(signature.substr(0, signature.size() / 2));
10611166
auto s = helper::raw2bn(signature.substr(signature.size() / 2));
10621167

1168+
ECDSA_SIG* psig;
10631169
#ifdef JWT_OPENSSL_1_0_0
10641170
ECDSA_SIG sig;
10651171
sig.r = r.get();
10661172
sig.s = s.get();
1067-
1068-
if (ECDSA_do_verify((const unsigned char*)hash.data(), static_cast<int>(hash.size()), &sig,
1069-
pkey.get()) != 1) {
1070-
ec = error::signature_verification_error::invalid_signature;
1071-
return;
1072-
}
1173+
psig = &sig;
10731174
#else
10741175
std::unique_ptr<ECDSA_SIG, decltype(&ECDSA_SIG_free)> sig(ECDSA_SIG_new(), ECDSA_SIG_free);
10751176
if (!sig) {
10761177
ec = error::signature_verification_error::create_context_failed;
1077-
return;
1178+
return {};
10781179
}
1079-
10801180
ECDSA_SIG_set0(sig.get(), r.release(), s.release());
1081-
1082-
if (ECDSA_do_verify(reinterpret_cast<const unsigned char*>(hash.data()), static_cast<int>(hash.size()),
1083-
sig.get(), pkey.get()) != 1) {
1084-
ec = error::signature_verification_error::invalid_signature;
1085-
return;
1086-
}
1181+
psig = sig.get();
10871182
#endif
1088-
}
1089-
/**
1090-
* Returns the algorithm name provided to the constructor
1091-
* \return algorithm's name
1092-
*/
1093-
std::string name() const { return alg_name; }
10941183

1095-
private:
1096-
/**
1097-
* Hash the provided data using the hash function specified in constructor
1098-
* \param data Data to hash
1099-
* \return Hash of data
1100-
*/
1101-
std::string generate_hash(const std::string& data, std::error_code& ec) const {
1102-
#ifdef JWT_OPENSSL_1_0_0
1103-
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_destroy)> ctx(EVP_MD_CTX_create(),
1104-
&EVP_MD_CTX_destroy);
1105-
#else
1106-
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
1107-
#endif
1108-
if (!ctx) {
1109-
ec = error::signature_generation_error::create_context_failed;
1110-
return {};
1111-
}
1112-
if (EVP_DigestInit(ctx.get(), md()) == 0) {
1113-
ec = error::signature_generation_error::digestinit_failed;
1184+
int length = i2d_ECDSA_SIG(psig, nullptr);
1185+
if (length < 0) {
1186+
ec = error::signature_verification_error::signature_encoding_failed;
11141187
return {};
11151188
}
1116-
if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) {
1117-
ec = error::signature_generation_error::digestupdate_failed;
1189+
std::string der_signature(length, '\0');
1190+
unsigned char* psbuffer = (unsigned char*)der_signature.data();
1191+
length = i2d_ECDSA_SIG(psig, &psbuffer);
1192+
if (length < 0) {
1193+
ec = error::signature_verification_error::signature_encoding_failed;
11181194
return {};
11191195
}
1120-
unsigned int len = 0;
1121-
std::string res(EVP_MD_CTX_size(ctx.get()), '\0');
1122-
if (EVP_DigestFinal(
1123-
ctx.get(),
1124-
(unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast`
1125-
&len) == 0) {
1126-
ec = error::signature_generation_error::digestfinal_failed;
1127-
return {};
1128-
}
1129-
res.resize(len);
1130-
return res;
1196+
der_signature.resize(length);
1197+
return der_signature;
11311198
}
11321199

11331200
/// OpenSSL struct containing keys
1134-
std::shared_ptr<EC_KEY> pkey;
1201+
std::shared_ptr<EVP_PKEY> pkey;
11351202
/// Hash generator function
11361203
const EVP_MD* (*md)();
11371204
/// algorithm's name

tests/HelperTest.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,21 @@ TEST(HelperTest, ErrorCodeMessages) {
5252
ASSERT_EQ(std::error_code(static_cast<jwt::error::rsa_error>(i)).message(),
5353
std::error_code(static_cast<jwt::error::rsa_error>(-1)).message());
5454

55-
for (i = 10; i < 16; i++) {
55+
for (i = 10; i < 17; i++) {
5656
ASSERT_NE(std::error_code(static_cast<jwt::error::ecdsa_error>(i)).message(),
5757
std::error_code(static_cast<jwt::error::ecdsa_error>(-1)).message());
5858
}
5959
ASSERT_EQ(std::error_code(static_cast<jwt::error::ecdsa_error>(i)).message(),
6060
std::error_code(static_cast<jwt::error::ecdsa_error>(-1)).message());
6161

62-
for (i = 10; i < 17; i++) {
62+
for (i = 10; i < 18; i++) {
6363
ASSERT_NE(std::error_code(static_cast<jwt::error::signature_verification_error>(i)).message(),
6464
std::error_code(static_cast<jwt::error::signature_verification_error>(-1)).message());
6565
}
6666
ASSERT_EQ(std::error_code(static_cast<jwt::error::signature_verification_error>(i)).message(),
6767
std::error_code(static_cast<jwt::error::signature_verification_error>(-1)).message());
6868

69-
for (i = 10; i < 23; i++) {
69+
for (i = 10; i < 24; i++) {
7070
ASSERT_NE(std::error_code(static_cast<jwt::error::signature_generation_error>(i)).message(),
7171
std::error_code(static_cast<jwt::error::signature_generation_error>(-1)).message());
7272
}

0 commit comments

Comments
 (0)