Skip to content

Commit

Permalink
Remove duplicated key handlers (#318)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
prince-chrismc authored Dec 12, 2023
1 parent 08bcf77 commit 8cb307a
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 92 deletions.
160 changes: 76 additions & 84 deletions include/jwt-cpp/jwt.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,13 @@ namespace jwt {
no_key_provided,
invalid_key_size,
invalid_key,
create_context_failed
create_context_failed,
cert_load_failed,
get_key_failed,
write_key_failed,
write_cert_failed,
convert_to_pem_failed,

};
/**
* \brief Error category for ECDSA errors
Expand All @@ -181,6 +187,11 @@ namespace jwt {
case ecdsa_error::invalid_key_size: return "invalid key size";
case ecdsa_error::invalid_key: return "invalid key";
case ecdsa_error::create_context_failed: return "failed to create context";
case ecdsa_error::cert_load_failed: return "error loading cert into memory";
case ecdsa_error::get_key_failed: return "error getting key from certificate";
case ecdsa_error::write_key_failed: return "error writing key data in PEM format";
case ecdsa_error::write_cert_failed: return "error writing cert data in PEM format";
case ecdsa_error::convert_to_pem_failed: return "failed to convert key to pem";
default: return "unknown ECDSA error";
}
}
Expand Down Expand Up @@ -492,39 +503,40 @@ namespace jwt {
/**
* \brief Extract the public key of a pem certificate
*
* \param certstr String containing the certificate encoded as pem
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
* \param ec error_code for error_detection (gets cleared if no error occurred)
* \tparam error_category jwt::error enum category to match with the keys being used
* \param certstr String containing the certificate encoded as pem
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
* \param ec error_code for error_detection (gets cleared if no error occurred)
*/
inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw,
std::error_code& ec) {
template<typename error_category = error::rsa_error>
std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, std::error_code& ec) {
ec.clear();
auto certbio = make_mem_buf_bio(certstr);
auto keybio = make_mem_buf_bio();
if (!certbio || !keybio) {
ec = error::rsa_error::create_mem_bio_failed;
ec = error_category::create_mem_bio_failed;
return {};
}

std::unique_ptr<X509, decltype(&X509_free)> cert(
PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast<char*>(pw.c_str())), X509_free);
if (!cert) {
ec = error::rsa_error::cert_load_failed;
ec = error_category::cert_load_failed;
return {};
}
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> key(X509_get_pubkey(cert.get()), EVP_PKEY_free);
if (!key) {
ec = error::rsa_error::get_key_failed;
ec = error_category::get_key_failed;
return {};
}
if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) {
ec = error::rsa_error::write_key_failed;
ec = error_category::write_key_failed;
return {};
}
char* ptr = nullptr;
auto len = BIO_get_mem_data(keybio.get(), &ptr);
if (len <= 0 || ptr == nullptr) {
ec = error::rsa_error::convert_to_pem_failed;
ec = error_category::convert_to_pem_failed;
return {};
}
return {ptr, static_cast<size_t>(len)};
Expand All @@ -533,13 +545,15 @@ namespace jwt {
/**
* \brief Extract the public key of a pem certificate
*
* \param certstr String containing the certificate encoded as pem
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
* \throw rsa_exception if an error occurred
* \tparam error_category jwt::error enum category to match with the keys being used
* \param certstr String containing the certificate encoded as pem
* \param pw Password used to decrypt certificate (leave empty if not encrypted)
* \throw templated error_category's type exception if an error occurred
*/
inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") {
template<typename error_category = error::rsa_error>
std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") {
std::error_code ec;
auto res = extract_pubkey_from_cert(certstr, pw, ec);
auto res = extract_pubkey_from_cert<error_category>(certstr, pw, ec);
error::throw_if_error(ec);
return res;
}
Expand Down Expand Up @@ -674,38 +688,40 @@ namespace jwt {
*
* The string should contain a pem encoded certificate or public key
*
* \tparam error_category jwt::error enum category to match with the keys being used
* \param key String containing the certificate encoded as pem
* \param password Password used to decrypt certificate (leave empty if not encrypted)
* \param ec error_code for error_detection (gets cleared if no error occurs)
*/
inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password,
std::error_code& ec) {
template<typename error_category = error::rsa_error>
evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password,
std::error_code& ec) {
ec.clear();
auto pubkey_bio = make_mem_buf_bio();
if (!pubkey_bio) {
ec = error::rsa_error::create_mem_bio_failed;
ec = error_category::create_mem_bio_failed;
return {};
}
if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") {
auto epkey = helper::extract_pubkey_from_cert(key, password, ec);
auto epkey = helper::extract_pubkey_from_cert<error_category>(key, password, ec);
if (ec) return {};
const int len = static_cast<int>(epkey.size());
if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) {
ec = error::rsa_error::load_key_bio_write;
ec = error_category::load_key_bio_write;
return {};
}
} else {
const int len = static_cast<int>(key.size());
if (BIO_write(pubkey_bio.get(), key.data(), len) != len) {
ec = error::rsa_error::load_key_bio_write;
ec = error_category::load_key_bio_write;
return {};
}
}

evp_pkey_handle pkey(PEM_read_bio_PUBKEY(
pubkey_bio.get(), nullptr, nullptr,
(void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast`
if (!pkey) ec = error::rsa_error::load_key_bio_read;
if (!pkey) ec = error_category::load_key_bio_read;
return pkey;
}

Expand All @@ -714,52 +730,59 @@ namespace jwt {
*
* The string should contain a pem encoded certificate or public key
*
* \param key String containing the certificate or key encoded as pem
* \param password Password used to decrypt certificate or key (leave empty if not encrypted)
* \throw rsa_exception if an error occurred
* \tparam error_category jwt::error enum category to match with the keys being used
* \param key String containing the certificate encoded as pem
* \param password Password used to decrypt certificate (leave empty if not encrypted)
* \throw Templated error_category's type exception if an error occurred
*/
template<typename error_category = error::rsa_error>
inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password = "") {
std::error_code ec;
auto res = load_public_key_from_string(key, password, ec);
auto res = load_public_key_from_string<error_category>(key, password, ec);
error::throw_if_error(ec);
return res;
}

/**
* \brief Load a private key from a string.
*
* \param key String containing a private key as pem
* \param password Password used to decrypt key (leave empty if not encrypted)
* \param ec error_code for error_detection (gets cleared if no error occurs)
* \tparam error_category jwt::error enum category to match with the keys being used
* \param key String containing a private key as pem
* \param password Password used to decrypt key (leave empty if not encrypted)
* \param ec error_code for error_detection (gets cleared if no error occurs)
*/
template<typename error_category = error::rsa_error>
inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password,
std::error_code& ec) {
auto privkey_bio = make_mem_buf_bio();
if (!privkey_bio) {
ec = error::rsa_error::create_mem_bio_failed;
ec.clear();
auto private_key_bio = make_mem_buf_bio();
if (!private_key_bio) {
ec = error_category::create_mem_bio_failed;
return {};
}
const int len = static_cast<int>(key.size());
if (BIO_write(privkey_bio.get(), key.data(), len) != len) {
ec = error::rsa_error::load_key_bio_write;
if (BIO_write(private_key_bio.get(), key.data(), len) != len) {
ec = error_category::load_key_bio_write;
return {};
}
evp_pkey_handle pkey(
PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast<char*>(password.c_str())));
if (!pkey) ec = error::rsa_error::load_key_bio_read;
PEM_read_bio_PrivateKey(private_key_bio.get(), nullptr, nullptr, const_cast<char*>(password.c_str())));
if (!pkey) ec = error_category::load_key_bio_read;
return pkey;
}

/**
* \brief Load a private key from a string.
*
* \param key String containing a private key as pem
* \param password Password used to decrypt key (leave empty if not encrypted)
* \throw rsa_exception if an error occurred
* \tparam error_category jwt::error enum category to match with the keys being used
* \param key String containing a private key as pem
* \param password Password used to decrypt key (leave empty if not encrypted)
* \throw Templated error_category's type exception if an error occurred
*/
template<typename error_category = error::rsa_error>
inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password = "") {
std::error_code ec;
auto res = load_private_key_from_string(key, password, ec);
auto res = load_private_key_from_string<error_category>(key, password, ec);
error::throw_if_error(ec);
return res;
}
Expand All @@ -768,95 +791,64 @@ namespace jwt {
* \brief Load a public key from a string.
*
* The string should contain a pem encoded certificate or public key
*
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
*
* \param key String containing the certificate encoded as pem
* \param password Password used to decrypt certificate (leave empty if not encrypted)
* \param ec error_code for error_detection (gets cleared if no error occurs)
*/
inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, const std::string& password,
std::error_code& ec) {
ec.clear();
auto pubkey_bio = make_mem_buf_bio();
if (!pubkey_bio) {
ec = error::ecdsa_error::create_mem_bio_failed;
return {};
}
if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") {
auto epkey = helper::extract_pubkey_from_cert(key, password, ec);
if (ec) return {};
const int len = static_cast<int>(epkey.size());
if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) {
ec = error::ecdsa_error::load_key_bio_write;
return {};
}
} else {
const int len = static_cast<int>(key.size());
if (BIO_write(pubkey_bio.get(), key.data(), len) != len) {
ec = error::ecdsa_error::load_key_bio_write;
return {};
}
}

evp_pkey_handle pkey(PEM_read_bio_PUBKEY(
pubkey_bio.get(), nullptr, nullptr,
(void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast`
if (!pkey) ec = error::ecdsa_error::load_key_bio_read;
return pkey;
return load_public_key_from_string<error::ecdsa_error>(key, password, ec);
}

/**
* \brief Load a public key from a string.
*
* The string should contain a pem encoded certificate or public key
*
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
*
* \param key String containing the certificate or key encoded as pem
* \param password Password used to decrypt certificate or key (leave empty if not encrypted)
* \throw ecdsa_exception if an error occurred
*/
inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key,
const std::string& password = "") {
std::error_code ec;
auto res = load_public_ec_key_from_string(key, password, ec);
auto res = load_public_key_from_string<error::ecdsa_error>(key, password, ec);
error::throw_if_error(ec);
return res;
}

/**
* \brief Load a private key from a string.
*
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
*
* \param key String containing a private key as pem
* \param password Password used to decrypt key (leave empty if not encrypted)
* \param ec error_code for error_detection (gets cleared if no error occurs)
*/
inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, const std::string& password,
std::error_code& ec) {
auto privkey_bio = make_mem_buf_bio();
if (!privkey_bio) {
ec = error::ecdsa_error::create_mem_bio_failed;
return {};
}
const int len = static_cast<int>(key.size());
if (BIO_write(privkey_bio.get(), key.data(), len) != len) {
ec = error::ecdsa_error::load_key_bio_write;
return {};
}
evp_pkey_handle pkey(
PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast<char*>(password.c_str())));
if (!pkey) ec = error::ecdsa_error::load_key_bio_read;
return pkey;
return load_private_key_from_string<error::ecdsa_error>(key, password, ec);
}

/**
* \brief Load a private key from a string.
*
* \deprecated Use the templated version load_private_key_from_string with error::ecdsa_error
*
* \param key String containing a private key as pem
* \param password Password used to decrypt key (leave empty if not encrypted)
* \throw ecdsa_exception if an error occurred
*/
inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key,
const std::string& password = "") {
std::error_code ec;
auto res = load_private_ec_key_from_string(key, password, ec);
auto res = load_private_key_from_string<error::ecdsa_error>(key, password, ec);
error::throw_if_error(ec);
return res;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/HelperTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ TEST(HelperTest, ErrorCodeMessages) {
ASSERT_EQ(std::error_code(static_cast<jwt::error::rsa_error>(i)).message(),
std::error_code(static_cast<jwt::error::rsa_error>(-1)).message());

for (i = 10; i < 17; i++) {
for (i = 10; i < 22; i++) {
ASSERT_NE(std::error_code(static_cast<jwt::error::ecdsa_error>(i)).message(),
std::error_code(static_cast<jwt::error::ecdsa_error>(-1)).message());
}
Expand Down
14 changes: 13 additions & 1 deletion tests/Keys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,19 @@ d3QtY3BwMQ8wDQYDVQQLDAZnaXRodWIxFDASBgNVBAMMC2V4YW1wbGUuY29tMCow
BQYDK2VwAyEAUdLe1SUWxc/95f39pfmuwe1SLHpFXf5gcRQlMH2sjgwwBQYDK2Vw
A0EAezYcLIUnyy86uUnAZdAMPn7wTruNKtG36GrTF3PF4dtdoGF1OV5DLnNK0Hbs
3GyYtaZs6AEHwDXl/INXu2zoCQ==
-----END CERTIFICATE-----)";
-----END CERTIFICATE-----
)";
// openssl x509 -outform der -in ed25519_certificate.pem -out ed25519_certificate.der
// openssl base64 -in ed25519_certificate.der -out ed25519_certificate.b64
std::string ed25519_certificate_base64_der = "MIIBjzCCAUECFCQlWQxMEMe4c3OOimH4/y+o/HpfMAUGAytlcDBqMQswCQYDVQQG"
"EwJDQTEPMA0GA1UECAwGUXVlYmVjMREwDwYDVQQHDAhNb250cmVhbDEQMA4GA1UE"
"CgwHand0LWNwcDEPMA0GA1UECwwGZ2l0aHViMRQwEgYDVQQDDAtleGFtcGxlLmNv"
"bTAeFw0yMDA3MzAyMTIwMDBaFw0yMjA2MzAyMTIwMDBaMGoxCzAJBgNVBAYTAkNB"
"MQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1vbnRyZWFsMRAwDgYDVQQKDAdq"
"d3QtY3BwMQ8wDQYDVQQLDAZnaXRodWIxFDASBgNVBAMMC2V4YW1wbGUuY29tMCow"
"BQYDK2VwAyEAUdLe1SUWxc/95f39pfmuwe1SLHpFXf5gcRQlMH2sjgwwBQYDK2Vw"
"A0EAezYcLIUnyy86uUnAZdAMPn7wTruNKtG36GrTF3PF4dtdoGF1OV5DLnNK0Hbs"
"3GyYtaZs6AEHwDXl/INXu2zoCQ==";
std::string ed448_priv_key = R"(-----BEGIN PRIVATE KEY-----
MEcCAQAwBQYDK2VxBDsEOZNyV4kIWehIWSsPCnDEZbBF+g2WoUgUwox8eQJTq8Hz
y4okU+JZAV8RqQ270fJL/Safvvc1SbbF1A==
Expand Down
Loading

0 comments on commit 8cb307a

Please sign in to comment.