Skip to content

Commit 66eea98

Browse files
authored
🗜️ Simplify reading of EC keys (#185)
* simplify reading of EC keys * fix formatting
1 parent b8e55e2 commit 66eea98

File tree

3 files changed

+150
-52
lines changed

3 files changed

+150
-52
lines changed

include/jwt-cpp/jwt.h

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,111 @@ namespace jwt {
630630
return res;
631631
}
632632

633+
/**
634+
* \brief Load a public key from a string.
635+
*
636+
* The string should contain a pem encoded certificate or public key
637+
*
638+
* \param certstr String containing the certificate encoded as pem
639+
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
640+
* \param ec error_code for error_detection (gets cleared if no error occures)
641+
*/
642+
inline std::shared_ptr<EVP_PKEY>
643+
load_public_ec_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) {
644+
ec.clear();
645+
std::unique_ptr<BIO, decltype(&BIO_free_all)> pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all);
646+
if (!pubkey_bio) {
647+
ec = error::ecdsa_error::create_mem_bio_failed;
648+
return nullptr;
649+
}
650+
if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") {
651+
auto epkey = helper::extract_pubkey_from_cert(key, password, ec);
652+
if (ec) return nullptr;
653+
const int len = static_cast<int>(epkey.size());
654+
if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) {
655+
ec = error::ecdsa_error::load_key_bio_write;
656+
return nullptr;
657+
}
658+
} else {
659+
const int len = static_cast<int>(key.size());
660+
if (BIO_write(pubkey_bio.get(), key.data(), len) != len) {
661+
ec = error::ecdsa_error::load_key_bio_write;
662+
return nullptr;
663+
}
664+
}
665+
666+
std::shared_ptr<EVP_PKEY> pkey(
667+
PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr,
668+
(void*)password.data()), // NOLINT(google-readability-casting) requires `const_cast`
669+
EVP_PKEY_free);
670+
if (!pkey) {
671+
ec = error::ecdsa_error::load_key_bio_read;
672+
return nullptr;
673+
}
674+
return pkey;
675+
}
676+
677+
/**
678+
* \brief Load a public key from a string.
679+
*
680+
* The string should contain a pem encoded certificate or public key
681+
*
682+
* \param certstr String containing the certificate or key encoded as pem
683+
* \param pw Password used to decrypt certificate or key (leave empty if not encrypted)
684+
* \throw ecdsa_exception if an error occurred
685+
*/
686+
inline std::shared_ptr<EVP_PKEY> load_public_ec_key_from_string(const std::string& key,
687+
const std::string& password = "") {
688+
std::error_code ec;
689+
auto res = load_public_ec_key_from_string(key, password, ec);
690+
error::throw_if_error(ec);
691+
return res;
692+
}
693+
694+
/**
695+
* \brief Load a private key from a string.
696+
*
697+
* \param key String containing a private key as pem
698+
* \param pw Password used to decrypt key (leave empty if not encrypted)
699+
* \param ec error_code for error_detection (gets cleared if no error occures)
700+
*/
701+
inline std::shared_ptr<EVP_PKEY>
702+
load_private_ec_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) {
703+
std::unique_ptr<BIO, decltype(&BIO_free_all)> privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all);
704+
if (!privkey_bio) {
705+
ec = error::ecdsa_error::create_mem_bio_failed;
706+
return nullptr;
707+
}
708+
const int len = static_cast<int>(key.size());
709+
if (BIO_write(privkey_bio.get(), key.data(), len) != len) {
710+
ec = error::ecdsa_error::load_key_bio_write;
711+
return nullptr;
712+
}
713+
std::shared_ptr<EVP_PKEY> pkey(
714+
PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast<char*>(password.c_str())),
715+
EVP_PKEY_free);
716+
if (!pkey) {
717+
ec = error::ecdsa_error::load_key_bio_read;
718+
return nullptr;
719+
}
720+
return pkey;
721+
}
722+
723+
/**
724+
* \brief Load a private key from a string.
725+
*
726+
* \param key String containing a private key as pem
727+
* \param pw Password used to decrypt key (leave empty if not encrypted)
728+
* \throw ecdsa_exception if an error occurred
729+
*/
730+
inline std::shared_ptr<EVP_PKEY> load_private_ec_key_from_string(const std::string& key,
731+
const std::string& password = "") {
732+
std::error_code ec;
733+
auto res = load_private_ec_key_from_string(key, password, ec);
734+
error::throw_if_error(ec);
735+
return res;
736+
}
737+
633738
/**
634739
* Convert a OpenSSL BIGNUM to a std::string
635740
* \param bn BIGNUM to convert
@@ -883,46 +988,19 @@ namespace jwt {
883988
ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password,
884989
const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen)
885990
: md(md), alg_name(std::move(name)), signature_length(siglen) {
886-
if (!public_key.empty()) {
887-
std::unique_ptr<BIO, decltype(&BIO_free_all)> pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all);
888-
if (!pubkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed);
889-
if (public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") {
890-
auto epkey = helper::extract_pubkey_from_cert(public_key, public_key_password);
891-
const int len = static_cast<int>(epkey.size());
892-
if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len)
893-
throw ecdsa_exception(error::ecdsa_error::load_key_bio_write);
894-
} else {
895-
const int len = static_cast<int>(public_key.size());
896-
if (BIO_write(pubkey_bio.get(), public_key.data(), len) != len)
897-
throw ecdsa_exception(error::ecdsa_error::load_key_bio_write);
898-
}
899-
900-
pkey.reset(PEM_read_bio_EC_PUBKEY(
901-
pubkey_bio.get(), nullptr, nullptr,
902-
(void*)public_key_password
903-
.c_str()), // NOLINT(google-readability-casting) requires `const_cast`
904-
EC_KEY_free);
905-
if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read);
906-
size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get()));
907-
if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521))
908-
throw ecdsa_exception(error::ecdsa_error::invalid_key_size);
909-
}
910-
911991
if (!private_key.empty()) {
912-
std::unique_ptr<BIO, decltype(&BIO_free_all)> privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all);
913-
if (!privkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed);
914-
const int len = static_cast<int>(private_key.size());
915-
if (BIO_write(privkey_bio.get(), private_key.data(), len) != len)
916-
throw ecdsa_exception(error::ecdsa_error::load_key_bio_write);
917-
pkey.reset(PEM_read_bio_ECPrivateKey(privkey_bio.get(), nullptr, nullptr,
918-
const_cast<char*>(private_key_password.c_str())),
919-
EC_KEY_free);
920-
if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read);
921-
size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get()));
922-
if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521))
923-
throw ecdsa_exception(error::ecdsa_error::invalid_key_size);
992+
auto epkey = helper::load_private_ec_key_from_string(private_key, private_key_password);
993+
pkey.reset(EVP_PKEY_get1_EC_KEY(epkey.get()), EC_KEY_free);
994+
} else if (!public_key.empty()) {
995+
auto epkey = helper::load_public_ec_key_from_string(public_key, public_key_password);
996+
pkey.reset(EVP_PKEY_get1_EC_KEY(epkey.get()), EC_KEY_free);
997+
} else {
998+
throw ecdsa_exception(error::ecdsa_error::no_key_provided);
924999
}
925-
if (!pkey) throw ecdsa_exception(error::ecdsa_error::no_key_provided);
1000+
if (!pkey) throw ecdsa_exception(error::ecdsa_error::invalid_key);
1001+
size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get()));
1002+
if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521))
1003+
throw ecdsa_exception(error::ecdsa_error::invalid_key_size);
9261004

9271005
if (EC_KEY_check_key(pkey.get()) == 0) throw ecdsa_exception(error::ecdsa_error::invalid_key);
9281006
}

tests/OpenSSLErrorTest.cpp

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ static uint64_t fail_EVP_DigestSignInit = 0;
4242
static uint64_t fail_EVP_DigestSign = 0;
4343
static uint64_t fail_EVP_DigestVerifyInit = 0;
4444
static uint64_t fail_EVP_DigestVerify = 0;
45+
static uint64_t fail_EVP_PKEY_get1_EC_KEY = 0;
4546

4647
BIO* BIO_new(const BIO_METHOD* type) {
4748
static BIO* (*origMethod)(const BIO_METHOD*) = nullptr;
@@ -342,6 +343,17 @@ int EVP_DigestVerify(EVP_MD_CTX* ctx, unsigned char* sigret, size_t* siglen, con
342343
return origMethod(ctx, sigret, siglen, tbs, tbslen);
343344
}
344345

346+
EC_KEY* EVP_PKEY_get1_EC_KEY(EVP_PKEY* pkey) {
347+
static EC_KEY* (*origMethod)(EVP_PKEY * pkey) = nullptr;
348+
if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_PKEY_get1_EC_KEY");
349+
bool fail = fail_EVP_PKEY_get1_EC_KEY & 1;
350+
fail_EVP_PKEY_get1_EC_KEY = fail_EVP_PKEY_get1_EC_KEY >> 1;
351+
if (fail)
352+
return nullptr;
353+
else
354+
return origMethod(pkey);
355+
}
356+
345357
/**
346358
* =========== End of black magic ============
347359
*/
@@ -609,21 +621,35 @@ TEST(OpenSSLErrorTest, RS256VerifyErrorCode) {
609621
run_multitest(mapping, [&alg, &signature](std::error_code& ec) { alg.verify("testdata", signature, ec); });
610622
}
611623

612-
TEST(OpenSSLErrorTest, ECDSAKey) {
624+
TEST(OpenSSLErrorTest, LoadECDSAPrivateKeyFromString) {
625+
std::vector<multitest_entry> mapping{
626+
{&fail_BIO_new, 1, jwt::error::ecdsa_error::create_mem_bio_failed},
627+
{&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write},
628+
{&fail_PEM_read_bio_PrivateKey, 1, jwt::error::ecdsa_error::load_key_bio_read},
629+
{&fail_EC_KEY_check_key, 1, jwt::error::ecdsa_error::invalid_key},
630+
{&fail_EVP_PKEY_get1_EC_KEY, 1, jwt::error::ecdsa_error::invalid_key},
631+
};
632+
633+
run_multitest(mapping, [](std::error_code& ec) {
634+
try {
635+
jwt::algorithm::es256 alg{"", ecdsa256_priv_key};
636+
FAIL(); // Should never reach this
637+
} catch (const std::system_error& e) { ec = e.code(); }
638+
});
639+
}
640+
641+
TEST(OpenSSLErrorTest, LoadECDSAPublicKeyFromString) {
613642
std::vector<multitest_entry> mapping{
614643
{&fail_BIO_new, 1, jwt::error::ecdsa_error::create_mem_bio_failed},
615644
{&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write},
616-
{&fail_PEM_read_bio_EC_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read},
617-
// Privkey section
618-
{&fail_BIO_new, 2, jwt::error::ecdsa_error::create_mem_bio_failed},
619-
{&fail_BIO_write, 2, jwt::error::ecdsa_error::load_key_bio_write},
620-
{&fail_PEM_read_bio_ECPrivateKey, 1, jwt::error::ecdsa_error::load_key_bio_read},
645+
{&fail_PEM_read_bio_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read},
621646
{&fail_EC_KEY_check_key, 1, jwt::error::ecdsa_error::invalid_key},
647+
{&fail_EVP_PKEY_get1_EC_KEY, 1, jwt::error::ecdsa_error::invalid_key},
622648
};
623649

624650
run_multitest(mapping, [](std::error_code& ec) {
625651
try {
626-
jwt::algorithm::es256 alg{ecdsa256_pub_key, ecdsa256_priv_key};
652+
jwt::algorithm::es256 alg{ecdsa256_pub_key, ""};
627653
FAIL(); // Should never reach this
628654
} catch (const std::system_error& e) { ec = e.code(); }
629655
});
@@ -632,7 +658,7 @@ TEST(OpenSSLErrorTest, ECDSAKey) {
632658
TEST(OpenSSLErrorTest, ECDSACertificate) {
633659
std::vector<multitest_entry> mapping{{&fail_BIO_new, 1, jwt::error::ecdsa_error::create_mem_bio_failed},
634660
{&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write},
635-
{&fail_PEM_read_bio_EC_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read},
661+
{&fail_PEM_read_bio_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read},
636662
// extract_pubkey_from_cert
637663
{&fail_BIO_new, 2, jwt::error::rsa_error::create_mem_bio_failed},
638664
{&fail_PEM_read_bio_X509, 1, jwt::error::rsa_error::cert_load_failed},

tests/TokenTest.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -748,8 +748,6 @@ TEST(TokenTest, ThrowInvalidKeyLength) {
748748
// But also if only one cert has the wrong size
749749
ASSERT_THROW(jwt::algorithm::es256(ecdsa256_pub_key, ecdsa384_priv_key), jwt::ecdsa_exception);
750750
ASSERT_THROW(jwt::algorithm::es256(ecdsa256_pub_key, ecdsa521_priv_key), jwt::ecdsa_exception);
751-
ASSERT_THROW(jwt::algorithm::es256(ecdsa384_pub_key, ecdsa256_priv_key), jwt::ecdsa_exception);
752-
ASSERT_THROW(jwt::algorithm::es256(ecdsa521_pub_key, ecdsa256_priv_key), jwt::ecdsa_exception);
753751

754752
ASSERT_THROW(jwt::algorithm::es384(ecdsa256_pub_key, ""), jwt::ecdsa_exception);
755753
ASSERT_THROW(jwt::algorithm::es384("", ecdsa256_priv_key), jwt::ecdsa_exception);
@@ -760,8 +758,6 @@ TEST(TokenTest, ThrowInvalidKeyLength) {
760758

761759
ASSERT_THROW(jwt::algorithm::es384(ecdsa384_pub_key, ecdsa256_priv_key), jwt::ecdsa_exception);
762760
ASSERT_THROW(jwt::algorithm::es384(ecdsa384_pub_key, ecdsa521_priv_key), jwt::ecdsa_exception);
763-
ASSERT_THROW(jwt::algorithm::es384(ecdsa256_pub_key, ecdsa384_priv_key), jwt::ecdsa_exception);
764-
ASSERT_THROW(jwt::algorithm::es384(ecdsa521_pub_key, ecdsa384_priv_key), jwt::ecdsa_exception);
765761

766762
ASSERT_THROW(jwt::algorithm::es512(ecdsa256_pub_key, ""), jwt::ecdsa_exception);
767763
ASSERT_THROW(jwt::algorithm::es512("", ecdsa256_priv_key), jwt::ecdsa_exception);
@@ -772,8 +768,6 @@ TEST(TokenTest, ThrowInvalidKeyLength) {
772768

773769
ASSERT_THROW(jwt::algorithm::es512(ecdsa521_pub_key, ecdsa256_priv_key), jwt::ecdsa_exception);
774770
ASSERT_THROW(jwt::algorithm::es512(ecdsa521_pub_key, ecdsa384_priv_key), jwt::ecdsa_exception);
775-
ASSERT_THROW(jwt::algorithm::es512(ecdsa256_pub_key, ecdsa521_priv_key), jwt::ecdsa_exception);
776-
ASSERT_THROW(jwt::algorithm::es512(ecdsa384_pub_key, ecdsa521_priv_key), jwt::ecdsa_exception);
777771

778772
// Make sure we do not throw if the correct params are passed
779773
ASSERT_NO_THROW(jwt::algorithm::es256(ecdsa256_pub_key, ecdsa256_priv_key));

0 commit comments

Comments
 (0)