Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JWKS parsing #146

Merged
merged 8 commits into from
Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ target_link_libraries(rsa-create jwt-cpp::jwt-cpp)

add_executable(rsa-verify rsa-verify.cpp)
target_link_libraries(rsa-verify jwt-cpp::jwt-cpp)

add_executable(jwks-verify jwks-verify.cpp)
target_link_libraries(jwks-verify jwt-cpp::jwt-cpp)
48 changes: 48 additions & 0 deletions example/jwks-verify.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <iostream>
#include <jwt-cpp/jwt.h>

int main()
{
std::string publicKey = R"({"keys": [{
"kid":"internal-gateway-jwt.api.sc.net",
"alg": "RS256",
"kty": "RSA",
"use": "sig",
"x5c": [
"MIIC+DCCAeCgAwIBAgIJBIGjYW6hFpn2MA0GCSqGSIb3DQEBBQUAMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTAeFw0xNjExMjIyMjIyMDVaFw0zMDA4MDEyMjIyMDVaMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMnjZc5bm/eGIHq09N9HKHahM7Y31P0ul+A2wwP4lSpIwFrWHzxw88/7Dwk9QMc+orGXX95R6av4GF+Es/nG3uK45ooMVMa/hYCh0Mtx3gnSuoTavQEkLzCvSwTqVwzZ+5noukWVqJuMKNwjL77GNcPLY7Xy2/skMCT5bR8UoWaufooQvYq6SyPcRAU4BtdquZRiBT4U5f+4pwNTxSvey7ki50yc1tG49Per/0zA4O6Tlpv8x7Red6m1bCNHt7+Z5nSl3RX/QYyAEUX1a28VcYmR41Osy+o2OUCXYdUAphDaHo4/8rbKTJhlu8jEcc1KoMXAKjgaVZtG/v5ltx6AXY0CAwEAAaMvMC0wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUQxFG602h1cG+pnyvJoy9pGJJoCswDQYJKoZIhvcNAQEFBQADggEBAGvtCbzGNBUJPLICth3mLsX0Z4z8T8iu4tyoiuAshP/Ry/ZBnFnXmhD8vwgMZ2lTgUWwlrvlgN+fAtYKnwFO2G3BOCFw96Nm8So9sjTda9CCZ3dhoH57F/hVMBB0K6xhklAc0b5ZxUpCIN92v/w+xZoz1XQBHe8ZbRHaP1HpRM4M7DJk2G5cgUCyu3UBvYS41sHvzrxQ3z7vIePRA4WF4bEkfX12gvny0RsPkrbVMXX1Rj9t6V7QXrbPYBAO+43JvDGYawxYVvLhz+BJ45x50GFQmHszfY3BR9TPK8xmMmQwtIvLu1PMttNCs7niCYkSiUv2sc2mlq1i3IashGkkgmo="
],
"n": "yeNlzlub94YgerT030codqEztjfU_S6X4DbDA_iVKkjAWtYfPHDzz_sPCT1Axz6isZdf3lHpq_gYX4Sz-cbe4rjmigxUxr-FgKHQy3HeCdK6hNq9ASQvMK9LBOpXDNn7mei6RZWom4wo3CMvvsY1w8tjtfLb-yQwJPltHxShZq5-ihC9irpLI9xEBTgG12q5lGIFPhTl_7inA1PFK97LuSLnTJzW0bj096v_TMDg7pOWm_zHtF53qbVsI0e3v5nmdKXdFf9BjIARRfVrbxVxiZHjU6zL6jY5QJdh1QCmENoejj_ytspMmGW7yMRxzUqgxcAqOBpVm0b-_mW3HoBdjQ",
"e": "AQAB",
"x5t": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg"
},
{
"kid":"internal-123456",
"use":"sig",
"x5c":["MIIG1TCCBL2gAwIBAgIIFvMVGp6t\/cMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCR0IxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMTUwMwYDVQQDDCxTdGFuZGFyZCBDaGFydGVyZWQgQmFuayBTaWduaW5nIENBIEcxIC0gU0hBMjAeFw0xODEwMTAxMTI2MzVaFw0yMjEwMTAxMTI2MzVaMIG9MQswCQYDVQQGEwJTRzESMBAGA1UECAwJU2luZ2Fwb3JlMRIwEAYDVQQHDAlTaW5nYXBvcmUxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMRwwGgYDVQQLDBNGb3VuZGF0aW9uIFNlcnZpY2VzMSgwJgYDVQQDDB9pbnRlcm5hbC1nYXRld2F5LWp3dC5hcGkuc2MubmV0MRwwGgYJKoZIhvcNAQkBFg1BUElQU1NAc2MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVWBoIi3IJ4nOWXu7\/SDxczqMou1B+c4c2FdQrOXrK31HxAaz4WEtma9BLXFdFHJ5mCCPIvdUcVxxnCynqhMOkZ\/a7acQbUD9cDzI8isMB9JL7VooDw0CctxHxffjqQQVIEhC2Q7zsM1pQayR7cl+pbBlvHIoRxq2n1B0fFvfoiosjf4kDiCpgHdM+v5Hw9aVYmUbroHxmQWqhB0iRTJQPPLZqqQVC50A1Q\/96gkwoODyotc46Uy9wYEpdGrtDG\/thWay3fmMsjpWR0U25xFIrxTrfCGBblYpD7juukWWml2E9rtE2rHgUxbymxXjEw7xrMwcGrhOGyqwoBqJy1JVwIDAQABo4ICLTCCAikwZAYIKwYBBQUHAQEEWDBWMFQGCCsGAQUFBzABhkhodHRwOi8vY29yZW9jc3AuZ2xvYmFsLnN0YW5kYXJkY2hhcnRlcmVkLmNvbS9lamJjYS9wdWJsaWN3ZWIvc3RhdHVzL29jc3AwHQYDVR0OBBYEFIinW4BNDeVEFcuLf8YjZjtySoW9MAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUfNZMoZi33nKrcmVU3TFVQnuEi\/4wggFCBgNVHR8EggE5MIIBNTCCATGggcKggb+GgbxodHRwOi8vY29yZWNybC5nbG9iYWwuc3RhbmRhcmRjaGFydGVyZWQuY29tL2VqYmNhL3B1YmxpY3dlYi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1jcmwmaXNzdWVyPUNOPVN0YW5kYXJkJTIwQ2hhcnRlcmVkJTIwQmFuayUyMFNpZ25pbmclMjBDQSUyMEcxJTIwLSUyMFNIQTIsTz1TdGFuZGFyZCUyMENoYXJ0ZXJlZCUyMEJhbmssQz1HQqJqpGgwZjE1MDMGA1UEAwwsU3RhbmRhcmQgQ2hhcnRlcmVkIEJhbmsgU2lnbmluZyBDQSBHMSAtIFNIQTIxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMQswCQYDVQQGEwJHQjAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4ICAQBtsoRlDHuOTDChcWdfdVUtRgP0U0ijDSeJi8vULN1rgYnqqJc4PdJno50aiu9MGlxY02O7HW7ZVD6QEG\/pqHmZ0sbWpb\/fumMgZSjP65IcGuS53zgcNtLYnyXyEv+v5T\/CK3bk4Li6tUW3ScJPUwVWwP1E0\/u6aBSb5k\/h4lTwS1o88ybS5pJOg6XutXByp991QQrrs7tp7fKNynjNZbFuG3J1e09X+zTfJOpjaDUofQTkt8IyMRI6Cs4wI1eZA+dAIL8B0n8ze1mRl1FOJqgdZrAQjoqZkCTnc0Il5VY\/dUXxGVg6D9e5pfck3FWT107K9\/5EZoxytpqYXFCjMXi5hx4YjK17OUgm82mZhvqkNdzF8Yq2vFuB3LPfyelESq99xFLykvinrVm1NtZKeDTT1Jq\/VvZt6stO\/tovq1RfJJcznpYcwOzxlnhGR6E+hxuBx7aDJzGf0JaoRxQILH1B2XV9WDI3HPYQsP7XtriX+QUJ\/aly28QkV48RmaGYCsly43YZu1MKudSsw+dhnbZzRsg\/aes3dzGW2x137bQPtux7k2LCSpsTXgedhOys28YoGlsoe8kUv0myAU4Stt+I3mrwO3BKUn+tJggvlDiiiyT1tg2HiklyU\/2FxQkZRMeB0eRrXTpg3l9x2mpF+dDFxOMKszxwD2kgoEZgo6o58A=="],
"n":"nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ",
"e":"AQAB",
"x5t":"-qC0akuyiHTV5aFsKVWM9da7lzq6DLrj09I",
"alg":"RS256",
"kty":"RSA"
}
]})";

std::string token = "eyJraWQiOiJpbnRlcm5hbC1nYXRld2F5LWp3dC5hcGkuc2MubmV0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE1Mzk3NjcwMTUsImlhdCI6MTUzOTc2Njk5MiwiaXNzIjoia29uZyIsImh0dHA6XC9cL3dzbzIub3JnXC9nYXRld2F5XC9zdWJzY3JpYmVyIjoidXZ0dXNlcjJAY2FyYm9uLnN1cGVyIiwib3JpZ2luYWxfaXNzIjoiaHR0cDpcL1wvd3NvMi5vcmdcL2dhdGV3YXkiLCJzdWIiOiJ1dnR1c2VyMkBjYXJib24uc3VwZXIiLCJodHRwOlwvXC93c28yLm9yZ1wvZ2F0ZXdheVwvZW5kdXNlciI6InV2dHVzZXIyQGNhcmJvbi5zdXBlciIsImp0aSI6IjI0NmJkZTlhLWQ4OGQtNGRlZC1hODhmLTRhMTNhOWJmODQ4ZiIsImh0dHA6XC9cL3dzbzIub3JnXC9nYXRld2F5XC9hcHBsaWNhdGlvbm5hbWUiOiJ1dnR1c2VyMl9hcHBfMSIsImV4cCI6MTUzOTc2NzkxNX0.foxbo6C30yr_wkF-5EkgtYUMG-4SXNfRsmewdT6MbE-RXVkIPkVk8kDP41yRXmnk4OxburCqawiGlzzEhfHoFf0qv0qZEmwEXSdcyRw-czZTs6ACjWYe8kejOCVmpvUrq01NgOhTwgVg6pv93BlcmNY--zytjx_9hlVm5SS1lZ0I21n45BIWu5JvBD51TZXEURb_XhL7RcF9I8mfzrRpB2fSHW38gj-nogsdOPA_y3S-hJKylmmaqmaQgTF-jP-gYr6eqKyGPVwc6fLZ5zqAup59SefdPEY23-WWmHzj968jlsDSEiCp_YiYTnF3tHVLFWDsrprYKwNb0_p95tBmPA";

auto decoded_jwt = jwt::decode(token);
auto jwks = jwt::parse_jwks(publicKey);
auto jwk = jwks.get_jwk(decoded_jwt.get_key_id());

auto issuer = decoded_jwt.get_issuer();
auto x5c = jwk.get_x5c_key_value();

if (!x5c.empty() && !issuer.empty()) {
auto verifier = jwt::verify()
.allow_algorithm(jwt::algorithm::rs256(jwt::helper::convert_base64_der_to_pem(x5c), "", "", ""))
.with_issuer(issuer)
.leeway(60UL); // value in seconds, add some to compensate timeout

verifier.verify(decoded_jwt);
}

}
279 changes: 279 additions & 0 deletions include/jwt-cpp/jwt.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@

#include <algorithm>
#include <chrono>
#include <iterator>
#include <memory>
#include <set>
#include <system_error>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>

#if __cplusplus >= 201402L
#ifdef __has_include
Expand Down Expand Up @@ -2873,6 +2875,254 @@ namespace jwt {
}
};

/**
* \brief JSON Web Key
*
* https://tools.ietf.org/html/rfc7517
*
* A JSON object that represents a cryptographic key. The members of
* the object represent properties of the key, including its value.
*/
template<typename json_traits>
class jwk {
This conversation was marked as resolved.
Show resolved Hide resolved
using basic_claim_t = basic_claim<json_traits>;
const details::map_of_claims<json_traits> jwk_claims;

public:
JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str)
: jwk_claims(details::map_of_claims<json_traits>::parse_claims(str)) {}

JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json)
: jwk_claims(json_traits::as_object(json)) {}

/**
* Get key type claim
*
* This returns the general type (e.g. RSA or EC), not a specific algorithm value.
* \return key type as string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_key_type() const { return get_jwk_claim("kty").as_string(); }

/**
* Get public key usage claim
* \return usage parameter as string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_use() const { return get_jwk_claim("use").as_string(); }

/**
* Get key operation types claim
* \return key operation types as a set of strings
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename basic_claim_t::set_t get_key_operations() const { return get_jwk_claim("key_ops").as_set(); }

/**
* Get algorithm claim
* \return algorithm as string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_algorithm() const { return get_jwk_claim("alg").as_string(); }

/**
* Get key id claim
* \return key id as string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_key_id() const { return get_jwk_claim("kid").as_string(); }

/**
* Get x5c claim
* \return x5c as an array
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a array (Should not happen in a valid token)
*/
typename json_traits::array_type get_x5c() const { return get_jwk_claim("x5c").as_array(); };

/**
* Get X509 URL claim
* \return x5u as string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_x5u() const { return get_jwk_claim("x5u").as_string(); };

/**
* Get X509 thumbprint claim
* \return x5t as string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_x5t() const { return get_jwk_claim("x5t").as_string(); };

/**
* Get X509 SHA256 thumbprint claim
* \return x5t#S256 as string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_x5t_sha256() const { return get_jwk_claim("x5t#S256").as_string(); };

/**
* Get x5c claim as a string
* \return x5c as an string
* \throw std::runtime_error If claim was not present
* \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token)
*/
typename json_traits::string_type get_x5c_key_value() const {
auto x5c_array = get_jwk_claim("x5c").as_array();
if (x5c_array.size() == 0) throw error::claim_not_present_exception();

return json_traits::as_string(x5c_array.front());
};

/**
* Check if a key type is present ("kty")
* \return true if present, false otherwise
*/
bool has_key_type() const noexcept { return has_jwk_claim("kty"); }

/**
* Check if a public key usage indication is present ("use")
* \return true if present, false otherwise
*/
bool has_use() const noexcept { return has_jwk_claim("use"); }

/**
* Check if a key operations parameter is present ("key_ops")
* \return true if present, false otherwise
*/
bool has_key_operations() const noexcept { return has_jwk_claim("key_ops"); }

/**
* Check if algortihm is present ("alg")
* \return true if present, false otherwise
*/
bool has_algorithm() const noexcept { return has_jwk_claim("alg"); }

/**
* Check if key id is present ("kid")
* \return true if present, false otherwise
*/
bool has_key_id() const noexcept { return has_jwk_claim("kid"); }

/**
* Check if X509 URL is present ("x5u")
* \return true if present, false otherwise
*/
bool has_x5u() const noexcept { return has_jwk_claim("x5u"); }

/**
* Check if X509 Chain is present ("x5c")
* \return true if present, false otherwise
*/
bool has_x5c() const noexcept { return has_jwk_claim("x5c"); }

/**
* Check if a X509 thumbprint is present ("x5t")
* \return true if present, false otherwise
*/
bool has_x5t() const noexcept { return has_jwk_claim("x5t"); }

/**
* Check if a X509 SHA256 thumbprint is present ("x5t#S256")
* \return true if present, false otherwise
*/
bool has_x5t_sha256() const noexcept { return has_jwk_claim("x5t#S256"); }

/**
* Check if a jwks claim is present
* \return true if claim was present, false otherwise
*/
bool has_jwk_claim(const typename json_traits::string_type& name) const noexcept {
return jwk_claims.has_claim(name);
}

/**
* Get jwks claim
* \return Requested claim
* \throw std::runtime_error If claim was not present
*/
basic_claim_t get_jwk_claim(const typename json_traits::string_type& name) const {
return jwk_claims.get_claim(name);
}
Thalhammer marked this conversation as resolved.
Show resolved Hide resolved

bool empty() const noexcept { return jwk_claims.empty(); }
};

/**
* \brief JWK Set
*
* https://tools.ietf.org/html/rfc7517
*
* A JSON object that represents a set of JWKs. The JSON object MUST
* have a "keys" member, which is an array of JWKs.
*
* This container takes a JWKs and simplifies it to a vector of JWKs
*/
template<typename json_traits>
class jwks {
This conversation was marked as resolved.
Show resolved Hide resolved
public:
using jwk_t = jwk<json_traits>;
using jwt_vector_t = std::vector<jwk_t>;
using iterator = typename jwt_vector_t::iterator;
using const_iterator = typename jwt_vector_t::const_iterator;

JWT_CLAIM_EXPLICIT jwks(const typename json_traits::string_type& str) {
typename json_traits::value_type val;
if (!json_traits::parse(val, str)) throw error::invalid_json_exception();

const details::map_of_claims<json_traits> jwks_json = json_traits::as_object(val);
if (!jwks_json.has_claim("keys")) throw error::invalid_json_exception();

auto jwk_list = jwks_json.get_claim("keys").as_array();
std::transform(jwk_list.begin(), jwk_list.end(), std::back_inserter(jwk_claims),
[](const typename json_traits::value_type& val) { return jwk_t{val}; });
}

iterator begin() { return jwk_claims.begin(); }
iterator end() { return jwk_claims.end(); }
const_iterator cbegin() const { return jwk_claims.begin(); }
const_iterator cend() const { return jwk_claims.end(); }
const_iterator begin() const { return jwk_claims.begin(); }
const_iterator end() const { return jwk_claims.end(); }

/**
* Check if a jwk with the kid is present
* \return true if jwk was present, false otherwise
*/
bool has_jwk(const typename json_traits::string_type& key_id) const noexcept {
return find_by_kid(key_id) != end();
}

/**
* Get jwk
* \return Requested jwk by key_id
* \throw std::runtime_error If jwk was not present
*/
jwk_t get_jwk(const typename json_traits::string_type& key_id) const {
const auto maybe = find_by_kid(key_id);
if (maybe == end()) throw error::claim_not_present_exception();
return *maybe;
}

private:
jwt_vector_t jwk_claims;

const_iterator find_by_kid(const typename json_traits::string_type& key_id) const noexcept {
return std::find_if(cbegin(), cend(), [key_id](const jwk_t& jwk) {
if (!jwk.has_key_id()) { return false; }
return jwk.get_key_id() == key_id;
});
}
};

/**
* Create a verifier using the given clock
* \param c Clock instance to use
Expand Down Expand Up @@ -2923,6 +3173,16 @@ namespace jwt {
return decoded_jwt<json_traits>(token);
}

template<typename json_traits>
jwk<json_traits> parse_jwk(const typename json_traits::string_type& token) {
return jwk<json_traits>(token);
}

template<typename json_traits>
jwks<json_traits> parse_jwks(const typename json_traits::string_type& token) {
return jwks<json_traits>(token);
}

#ifndef JWT_DISABLE_PICOJSON
struct picojson_traits {
using value_type = picojson::value;
Expand Down Expand Up @@ -3024,6 +3284,25 @@ namespace jwt {
decoded_jwt<picojson_traits> decode(const std::string& token, Decode decode) {
return decoded_jwt<picojson_traits>(token, decode);
}
/**
* Parse a jwk
* \param token JWK Token to parse
* \return Parsed JWK
* \throw std::runtime_error Token is not in correct format
*/
inline jwk<picojson_traits> parse_jwk(const picojson_traits::string_type& token) {
return jwk<picojson_traits>(token);
}

/**
* Parse a jwks
* \param token JWKs Token to parse
* \return Parsed JWKs
* \throw std::runtime_error Token is not in correct format
*/
inline jwks<picojson_traits> parse_jwks(const picojson_traits::string_type& token) {
return jwks<picojson_traits>(token);
}
#endif
} // namespace jwt

Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ set(TEST_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Keys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HelperTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TestMain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TokenFormatTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TokenTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/NlohmannTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/OpenSSLErrorTest.cpp)
${CMAKE_CURRENT_SOURCE_DIR}/OpenSSLErrorTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/JwksTest.cpp)

add_executable(jwt-cpp-test ${TEST_SOURCES})

Expand Down
Loading