diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index b2b998a2e..dc645a50d 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -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 @@ -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"; } } @@ -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 + 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 cert( PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); if (!cert) { - ec = error::rsa_error::cert_load_failed; + ec = error_category::cert_load_failed; return {}; } std::unique_ptr 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(len)}; @@ -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 + 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(certstr, pw, ec); error::throw_if_error(ec); return res; } @@ -674,30 +688,32 @@ 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 + 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(key, password, ec); if (ec) return {}; const int len = static_cast(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(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 {}; } } @@ -705,7 +721,7 @@ namespace jwt { 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; } @@ -714,13 +730,15 @@ 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 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(key, password, ec); error::throw_if_error(ec); return res; } @@ -728,38 +746,43 @@ namespace jwt { /** * \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 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(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(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(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 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(key, password, ec); error::throw_if_error(ec); return res; } @@ -768,6 +791,8 @@ 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) @@ -775,33 +800,7 @@ namespace jwt { */ 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(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(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(key, password, ec); } /** @@ -809,6 +808,8 @@ namespace jwt { * * 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 @@ -816,13 +817,15 @@ namespace jwt { 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(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) @@ -830,25 +833,14 @@ namespace jwt { */ 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(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(password.c_str()))); - if (!pkey) ec = error::ecdsa_error::load_key_bio_read; - return pkey; + return load_private_key_from_string(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 @@ -856,7 +848,7 @@ namespace jwt { 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(key, password, ec); error::throw_if_error(ec); return res; } diff --git a/tests/HelperTest.cpp b/tests/HelperTest.cpp index 558ca90bb..a806988a6 100644 --- a/tests/HelperTest.cpp +++ b/tests/HelperTest.cpp @@ -59,7 +59,7 @@ TEST(HelperTest, ErrorCodeMessages) { ASSERT_EQ(std::error_code(static_cast(i)).message(), std::error_code(static_cast(-1)).message()); - for (i = 10; i < 17; i++) { + for (i = 10; i < 22; i++) { ASSERT_NE(std::error_code(static_cast(i)).message(), std::error_code(static_cast(-1)).message()); } diff --git a/tests/Keys.cpp b/tests/Keys.cpp index 60abdd251..a5d81f625 100644 --- a/tests/Keys.cpp +++ b/tests/Keys.cpp @@ -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== diff --git a/tests/OpenSSLErrorTest.cpp b/tests/OpenSSLErrorTest.cpp index 45ada667b..00966dad3 100644 --- a/tests/OpenSSLErrorTest.cpp +++ b/tests/OpenSSLErrorTest.cpp @@ -436,6 +436,7 @@ inline namespace test_keys { extern std::string ed25519_pub_key; extern std::string ed25519_pub_key_invalid; extern std::string ed25519_certificate; + extern std::string ed25519_certificate_base64_der; extern std::string ed448_priv_key; extern std::string ed448_pub_key; extern std::string ed448_pub_key_invalid; @@ -457,6 +458,14 @@ TEST(OpenSSLErrorTest, ConvertCertBase64DerToPemReference) { ASSERT_EQ(ec.value(), 0); } +TEST(OpenSSLErrorTest, ConvertEcdsaCertBase64DerToPemReference) { + std::error_code ec; + auto res = jwt::helper::convert_base64_der_to_pem(ed25519_certificate_base64_der, ec); + ASSERT_EQ(res, ed25519_certificate); + ASSERT_FALSE(!(!ec)); + ASSERT_EQ(ec.value(), 0); +} + struct multitest_entry { uint64_t* fail_mask_ptr; uint64_t fail_bitmask; @@ -520,6 +529,19 @@ TEST(OpenSSLErrorTest, ConvertCertBase64DerToPem) { }); } +TEST(OpenSSLErrorTest, ConvertEcdsaCertBase64DerToPem) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_PEM_write_bio_cert, 1, jwt::error::rsa_error::write_cert_failed}, + {&fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed}}; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::helper::convert_base64_der_to_pem(ed25519_certificate_base64_der); + FAIL(); // Should never reach this + } catch (const jwt::error::rsa_exception& e) { ec = e.code(); } + }); +} + TEST(OpenSSLErrorTest, ConvertCertBase64DerToPemErrorCode) { std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, {&fail_PEM_write_bio_cert, 1, jwt::error::rsa_error::write_cert_failed}, @@ -536,6 +558,16 @@ TEST(OpenSSLErrorTest, LoadPublicKeyFromStringReference) { ASSERT_TRUE(res); } +TEST(OpenSSLErrorTest, LoadPublicKeyFromStringReferenceWithEcCert) { + auto res = jwt::helper::load_public_key_from_string(ecdsa256_pub_key, ""); + ASSERT_TRUE(res); +} + +TEST(OpenSSLErrorTest, LoadPublicKeyFromStringReferenceWithEcCertAndErr) { + auto res = jwt::helper::load_public_key_from_string(ecdsa256_pub_key, ""); + ASSERT_TRUE(res); +} + TEST(OpenSSLErrorTest, LoadPublicKeyFromString) { std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, @@ -549,6 +581,19 @@ TEST(OpenSSLErrorTest, LoadPublicKeyFromString) { }); } +TEST(OpenSSLErrorTest, LoadPublicKeyFromStringWithEc) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::ecdsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read}}; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::helper::load_public_key_from_string(ecdsa256_pub_key, ""); + FAIL(); // Should never reach this + } catch (const jwt::error::ecdsa_exception& e) { ec = e.code(); } + }); +} + TEST(OpenSSLErrorTest, LoadPublicKeyFromStringErrorCode) { std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, @@ -741,15 +786,15 @@ TEST(OpenSSLErrorTest, ECDSACertificate) { #if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x3050300fL {&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write}, #else - {&fail_BIO_write, 1, jwt::error::rsa_error::write_key_failed}, + {&fail_BIO_write, 1, jwt::error::ecdsa_error::write_key_failed}, #endif {&fail_PEM_read_bio_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read}, // extract_pubkey_from_cert - {&fail_BIO_new, 2, jwt::error::rsa_error::create_mem_bio_failed}, - {&fail_PEM_read_bio_X509, 1, jwt::error::rsa_error::cert_load_failed}, - {&fail_X509_get_pubkey, 1, jwt::error::rsa_error::get_key_failed}, - {&fail_PEM_write_bio_PUBKEY, 1, jwt::error::rsa_error::write_key_failed}, { - &fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed + {&fail_BIO_new, 2, jwt::error::ecdsa_error::create_mem_bio_failed}, + {&fail_PEM_read_bio_X509, 1, jwt::error::ecdsa_error::cert_load_failed}, + {&fail_X509_get_pubkey, 1, jwt::error::ecdsa_error::get_key_failed}, + {&fail_PEM_write_bio_PUBKEY, 1, jwt::error::ecdsa_error::write_key_failed}, { + &fail_BIO_ctrl, 1, jwt::error::ecdsa_error::convert_to_pem_failed } }; @@ -892,6 +937,9 @@ TEST(OpenSSLErrorTest, EdDSACertificate) { } TEST(OpenSSLErrorTest, Ed25519Reference) { + // No keys should throw + ASSERT_THROW(jwt::algorithm::ed25519("", ""), jwt::error::ecdsa_exception); + jwt::algorithm::ed25519 alg{ed25519_pub_key, ed25519_priv_key}; std::error_code ec; auto res = alg.sign("testdata", ec);