Skip to content

Commit 8cb307a

Browse files
Remove duplicated key handlers (#318)
* add a quick test to see if rsa helpers handle ec certs LoadPublicKeyFromStringReferenceWithEcCert * run all tests with generic helper * template out error category and new tests * linter * missing enum entry * does convert_base64_der_to_pem work with ecdsa? * use correct EC curve functions * missing extern symbol * dont change behavior * typo * fix tests * update private key to be more generic * fixup 2 more tests * update exception for libressl 3.5.3 https://github.com/Thalhammer/jwt-cpp/actions/runs/7161143039/job/19496334109?pr=318#step:8:248 ``` [ RUN ] OpenSSLErrorTest.ECDSACertificate /home/runner/work/jwt-cpp/jwt-cpp/tests/OpenSSLErrorTest.cpp:487: Failure Expected equality of these values: ec Which is: ecdsa_error:19 e.expected_ec Which is: rsa_error:12 ``` * linter * add missing test * fix copy paste * typo part 2
1 parent 08bcf77 commit 8cb307a

File tree

4 files changed

+144
-92
lines changed

4 files changed

+144
-92
lines changed

include/jwt-cpp/jwt.h

+76-84
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,13 @@ namespace jwt {
161161
no_key_provided,
162162
invalid_key_size,
163163
invalid_key,
164-
create_context_failed
164+
create_context_failed,
165+
cert_load_failed,
166+
get_key_failed,
167+
write_key_failed,
168+
write_cert_failed,
169+
convert_to_pem_failed,
170+
165171
};
166172
/**
167173
* \brief Error category for ECDSA errors
@@ -181,6 +187,11 @@ namespace jwt {
181187
case ecdsa_error::invalid_key_size: return "invalid key size";
182188
case ecdsa_error::invalid_key: return "invalid key";
183189
case ecdsa_error::create_context_failed: return "failed to create context";
190+
case ecdsa_error::cert_load_failed: return "error loading cert into memory";
191+
case ecdsa_error::get_key_failed: return "error getting key from certificate";
192+
case ecdsa_error::write_key_failed: return "error writing key data in PEM format";
193+
case ecdsa_error::write_cert_failed: return "error writing cert data in PEM format";
194+
case ecdsa_error::convert_to_pem_failed: return "failed to convert key to pem";
184195
default: return "unknown ECDSA error";
185196
}
186197
}
@@ -492,39 +503,40 @@ namespace jwt {
492503
/**
493504
* \brief Extract the public key of a pem certificate
494505
*
495-
* \param certstr String containing the certificate encoded as pem
496-
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
497-
* \param ec error_code for error_detection (gets cleared if no error occurred)
506+
* \tparam error_category jwt::error enum category to match with the keys being used
507+
* \param certstr String containing the certificate encoded as pem
508+
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
509+
* \param ec error_code for error_detection (gets cleared if no error occurred)
498510
*/
499-
inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw,
500-
std::error_code& ec) {
511+
template<typename error_category = error::rsa_error>
512+
std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, std::error_code& ec) {
501513
ec.clear();
502514
auto certbio = make_mem_buf_bio(certstr);
503515
auto keybio = make_mem_buf_bio();
504516
if (!certbio || !keybio) {
505-
ec = error::rsa_error::create_mem_bio_failed;
517+
ec = error_category::create_mem_bio_failed;
506518
return {};
507519
}
508520

509521
std::unique_ptr<X509, decltype(&X509_free)> cert(
510522
PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast<char*>(pw.c_str())), X509_free);
511523
if (!cert) {
512-
ec = error::rsa_error::cert_load_failed;
524+
ec = error_category::cert_load_failed;
513525
return {};
514526
}
515527
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> key(X509_get_pubkey(cert.get()), EVP_PKEY_free);
516528
if (!key) {
517-
ec = error::rsa_error::get_key_failed;
529+
ec = error_category::get_key_failed;
518530
return {};
519531
}
520532
if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) {
521-
ec = error::rsa_error::write_key_failed;
533+
ec = error_category::write_key_failed;
522534
return {};
523535
}
524536
char* ptr = nullptr;
525537
auto len = BIO_get_mem_data(keybio.get(), &ptr);
526538
if (len <= 0 || ptr == nullptr) {
527-
ec = error::rsa_error::convert_to_pem_failed;
539+
ec = error_category::convert_to_pem_failed;
528540
return {};
529541
}
530542
return {ptr, static_cast<size_t>(len)};
@@ -533,13 +545,15 @@ namespace jwt {
533545
/**
534546
* \brief Extract the public key of a pem certificate
535547
*
536-
* \param certstr String containing the certificate encoded as pem
537-
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
538-
* \throw rsa_exception if an error occurred
548+
* \tparam error_category jwt::error enum category to match with the keys being used
549+
* \param certstr String containing the certificate encoded as pem
550+
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
551+
* \throw templated error_category's type exception if an error occurred
539552
*/
540-
inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") {
553+
template<typename error_category = error::rsa_error>
554+
std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") {
541555
std::error_code ec;
542-
auto res = extract_pubkey_from_cert(certstr, pw, ec);
556+
auto res = extract_pubkey_from_cert<error_category>(certstr, pw, ec);
543557
error::throw_if_error(ec);
544558
return res;
545559
}
@@ -674,38 +688,40 @@ namespace jwt {
674688
*
675689
* The string should contain a pem encoded certificate or public key
676690
*
691+
* \tparam error_category jwt::error enum category to match with the keys being used
677692
* \param key String containing the certificate encoded as pem
678693
* \param password Password used to decrypt certificate (leave empty if not encrypted)
679694
* \param ec error_code for error_detection (gets cleared if no error occurs)
680695
*/
681-
inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password,
682-
std::error_code& ec) {
696+
template<typename error_category = error::rsa_error>
697+
evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password,
698+
std::error_code& ec) {
683699
ec.clear();
684700
auto pubkey_bio = make_mem_buf_bio();
685701
if (!pubkey_bio) {
686-
ec = error::rsa_error::create_mem_bio_failed;
702+
ec = error_category::create_mem_bio_failed;
687703
return {};
688704
}
689705
if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") {
690-
auto epkey = helper::extract_pubkey_from_cert(key, password, ec);
706+
auto epkey = helper::extract_pubkey_from_cert<error_category>(key, password, ec);
691707
if (ec) return {};
692708
const int len = static_cast<int>(epkey.size());
693709
if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) {
694-
ec = error::rsa_error::load_key_bio_write;
710+
ec = error_category::load_key_bio_write;
695711
return {};
696712
}
697713
} else {
698714
const int len = static_cast<int>(key.size());
699715
if (BIO_write(pubkey_bio.get(), key.data(), len) != len) {
700-
ec = error::rsa_error::load_key_bio_write;
716+
ec = error_category::load_key_bio_write;
701717
return {};
702718
}
703719
}
704720

705721
evp_pkey_handle pkey(PEM_read_bio_PUBKEY(
706722
pubkey_bio.get(), nullptr, nullptr,
707723
(void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast`
708-
if (!pkey) ec = error::rsa_error::load_key_bio_read;
724+
if (!pkey) ec = error_category::load_key_bio_read;
709725
return pkey;
710726
}
711727

@@ -714,52 +730,59 @@ namespace jwt {
714730
*
715731
* The string should contain a pem encoded certificate or public key
716732
*
717-
* \param key String containing the certificate or key encoded as pem
718-
* \param password Password used to decrypt certificate or key (leave empty if not encrypted)
719-
* \throw rsa_exception if an error occurred
733+
* \tparam error_category jwt::error enum category to match with the keys being used
734+
* \param key String containing the certificate encoded as pem
735+
* \param password Password used to decrypt certificate (leave empty if not encrypted)
736+
* \throw Templated error_category's type exception if an error occurred
720737
*/
738+
template<typename error_category = error::rsa_error>
721739
inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password = "") {
722740
std::error_code ec;
723-
auto res = load_public_key_from_string(key, password, ec);
741+
auto res = load_public_key_from_string<error_category>(key, password, ec);
724742
error::throw_if_error(ec);
725743
return res;
726744
}
727745

728746
/**
729747
* \brief Load a private key from a string.
730748
*
731-
* \param key String containing a private key as pem
732-
* \param password Password used to decrypt key (leave empty if not encrypted)
733-
* \param ec error_code for error_detection (gets cleared if no error occurs)
749+
* \tparam error_category jwt::error enum category to match with the keys being used
750+
* \param key String containing a private key as pem
751+
* \param password Password used to decrypt key (leave empty if not encrypted)
752+
* \param ec error_code for error_detection (gets cleared if no error occurs)
734753
*/
754+
template<typename error_category = error::rsa_error>
735755
inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password,
736756
std::error_code& ec) {
737-
auto privkey_bio = make_mem_buf_bio();
738-
if (!privkey_bio) {
739-
ec = error::rsa_error::create_mem_bio_failed;
757+
ec.clear();
758+
auto private_key_bio = make_mem_buf_bio();
759+
if (!private_key_bio) {
760+
ec = error_category::create_mem_bio_failed;
740761
return {};
741762
}
742763
const int len = static_cast<int>(key.size());
743-
if (BIO_write(privkey_bio.get(), key.data(), len) != len) {
744-
ec = error::rsa_error::load_key_bio_write;
764+
if (BIO_write(private_key_bio.get(), key.data(), len) != len) {
765+
ec = error_category::load_key_bio_write;
745766
return {};
746767
}
747768
evp_pkey_handle pkey(
748-
PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast<char*>(password.c_str())));
749-
if (!pkey) ec = error::rsa_error::load_key_bio_read;
769+
PEM_read_bio_PrivateKey(private_key_bio.get(), nullptr, nullptr, const_cast<char*>(password.c_str())));
770+
if (!pkey) ec = error_category::load_key_bio_read;
750771
return pkey;
751772
}
752773

753774
/**
754775
* \brief Load a private key from a string.
755776
*
756-
* \param key String containing a private key as pem
757-
* \param password Password used to decrypt key (leave empty if not encrypted)
758-
* \throw rsa_exception if an error occurred
777+
* \tparam error_category jwt::error enum category to match with the keys being used
778+
* \param key String containing a private key as pem
779+
* \param password Password used to decrypt key (leave empty if not encrypted)
780+
* \throw Templated error_category's type exception if an error occurred
759781
*/
782+
template<typename error_category = error::rsa_error>
760783
inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password = "") {
761784
std::error_code ec;
762-
auto res = load_private_key_from_string(key, password, ec);
785+
auto res = load_private_key_from_string<error_category>(key, password, ec);
763786
error::throw_if_error(ec);
764787
return res;
765788
}
@@ -768,95 +791,64 @@ namespace jwt {
768791
* \brief Load a public key from a string.
769792
*
770793
* The string should contain a pem encoded certificate or public key
794+
*
795+
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
771796
*
772797
* \param key String containing the certificate encoded as pem
773798
* \param password Password used to decrypt certificate (leave empty if not encrypted)
774799
* \param ec error_code for error_detection (gets cleared if no error occurs)
775800
*/
776801
inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, const std::string& password,
777802
std::error_code& ec) {
778-
ec.clear();
779-
auto pubkey_bio = make_mem_buf_bio();
780-
if (!pubkey_bio) {
781-
ec = error::ecdsa_error::create_mem_bio_failed;
782-
return {};
783-
}
784-
if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") {
785-
auto epkey = helper::extract_pubkey_from_cert(key, password, ec);
786-
if (ec) return {};
787-
const int len = static_cast<int>(epkey.size());
788-
if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) {
789-
ec = error::ecdsa_error::load_key_bio_write;
790-
return {};
791-
}
792-
} else {
793-
const int len = static_cast<int>(key.size());
794-
if (BIO_write(pubkey_bio.get(), key.data(), len) != len) {
795-
ec = error::ecdsa_error::load_key_bio_write;
796-
return {};
797-
}
798-
}
799-
800-
evp_pkey_handle pkey(PEM_read_bio_PUBKEY(
801-
pubkey_bio.get(), nullptr, nullptr,
802-
(void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast`
803-
if (!pkey) ec = error::ecdsa_error::load_key_bio_read;
804-
return pkey;
803+
return load_public_key_from_string<error::ecdsa_error>(key, password, ec);
805804
}
806805

807806
/**
808807
* \brief Load a public key from a string.
809808
*
810809
* The string should contain a pem encoded certificate or public key
811810
*
811+
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
812+
*
812813
* \param key String containing the certificate or key encoded as pem
813814
* \param password Password used to decrypt certificate or key (leave empty if not encrypted)
814815
* \throw ecdsa_exception if an error occurred
815816
*/
816817
inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key,
817818
const std::string& password = "") {
818819
std::error_code ec;
819-
auto res = load_public_ec_key_from_string(key, password, ec);
820+
auto res = load_public_key_from_string<error::ecdsa_error>(key, password, ec);
820821
error::throw_if_error(ec);
821822
return res;
822823
}
823824

824825
/**
825826
* \brief Load a private key from a string.
827+
*
828+
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
826829
*
827830
* \param key String containing a private key as pem
828831
* \param password Password used to decrypt key (leave empty if not encrypted)
829832
* \param ec error_code for error_detection (gets cleared if no error occurs)
830833
*/
831834
inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, const std::string& password,
832835
std::error_code& ec) {
833-
auto privkey_bio = make_mem_buf_bio();
834-
if (!privkey_bio) {
835-
ec = error::ecdsa_error::create_mem_bio_failed;
836-
return {};
837-
}
838-
const int len = static_cast<int>(key.size());
839-
if (BIO_write(privkey_bio.get(), key.data(), len) != len) {
840-
ec = error::ecdsa_error::load_key_bio_write;
841-
return {};
842-
}
843-
evp_pkey_handle pkey(
844-
PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast<char*>(password.c_str())));
845-
if (!pkey) ec = error::ecdsa_error::load_key_bio_read;
846-
return pkey;
836+
return load_private_key_from_string<error::ecdsa_error>(key, password, ec);
847837
}
848838

849839
/**
850840
* \brief Load a private key from a string.
851841
*
842+
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
843+
*
852844
* \param key String containing a private key as pem
853845
* \param password Password used to decrypt key (leave empty if not encrypted)
854846
* \throw ecdsa_exception if an error occurred
855847
*/
856848
inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key,
857849
const std::string& password = "") {
858850
std::error_code ec;
859-
auto res = load_private_ec_key_from_string(key, password, ec);
851+
auto res = load_private_key_from_string<error::ecdsa_error>(key, password, ec);
860852
error::throw_if_error(ec);
861853
return res;
862854
}

tests/HelperTest.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ TEST(HelperTest, ErrorCodeMessages) {
5959
ASSERT_EQ(std::error_code(static_cast<jwt::error::rsa_error>(i)).message(),
6060
std::error_code(static_cast<jwt::error::rsa_error>(-1)).message());
6161

62-
for (i = 10; i < 17; i++) {
62+
for (i = 10; i < 22; i++) {
6363
ASSERT_NE(std::error_code(static_cast<jwt::error::ecdsa_error>(i)).message(),
6464
std::error_code(static_cast<jwt::error::ecdsa_error>(-1)).message());
6565
}

tests/Keys.cpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,19 @@ d3QtY3BwMQ8wDQYDVQQLDAZnaXRodWIxFDASBgNVBAMMC2V4YW1wbGUuY29tMCow
157157
BQYDK2VwAyEAUdLe1SUWxc/95f39pfmuwe1SLHpFXf5gcRQlMH2sjgwwBQYDK2Vw
158158
A0EAezYcLIUnyy86uUnAZdAMPn7wTruNKtG36GrTF3PF4dtdoGF1OV5DLnNK0Hbs
159159
3GyYtaZs6AEHwDXl/INXu2zoCQ==
160-
-----END CERTIFICATE-----)";
160+
-----END CERTIFICATE-----
161+
)";
162+
// openssl x509 -outform der -in ed25519_certificate.pem -out ed25519_certificate.der
163+
// openssl base64 -in ed25519_certificate.der -out ed25519_certificate.b64
164+
std::string ed25519_certificate_base64_der = "MIIBjzCCAUECFCQlWQxMEMe4c3OOimH4/y+o/HpfMAUGAytlcDBqMQswCQYDVQQG"
165+
"EwJDQTEPMA0GA1UECAwGUXVlYmVjMREwDwYDVQQHDAhNb250cmVhbDEQMA4GA1UE"
166+
"CgwHand0LWNwcDEPMA0GA1UECwwGZ2l0aHViMRQwEgYDVQQDDAtleGFtcGxlLmNv"
167+
"bTAeFw0yMDA3MzAyMTIwMDBaFw0yMjA2MzAyMTIwMDBaMGoxCzAJBgNVBAYTAkNB"
168+
"MQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1vbnRyZWFsMRAwDgYDVQQKDAdq"
169+
"d3QtY3BwMQ8wDQYDVQQLDAZnaXRodWIxFDASBgNVBAMMC2V4YW1wbGUuY29tMCow"
170+
"BQYDK2VwAyEAUdLe1SUWxc/95f39pfmuwe1SLHpFXf5gcRQlMH2sjgwwBQYDK2Vw"
171+
"A0EAezYcLIUnyy86uUnAZdAMPn7wTruNKtG36GrTF3PF4dtdoGF1OV5DLnNK0Hbs"
172+
"3GyYtaZs6AEHwDXl/INXu2zoCQ==";
161173
std::string ed448_priv_key = R"(-----BEGIN PRIVATE KEY-----
162174
MEcCAQAwBQYDK2VxBDsEOZNyV4kIWehIWSsPCnDEZbBF+g2WoUgUwox8eQJTq8Hz
163175
y4okU+JZAV8RqQ270fJL/Safvvc1SbbF1A==

0 commit comments

Comments
 (0)