-
-
Notifications
You must be signed in to change notification settings - Fork 243
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 (abandoned) #112
Changes from all commits
0366d4c
099c249
1e16c10
a6ccd9a
ffa40c9
901a6a7
df53292
7b370ce
e54dc12
b20a114
cda5166
ed5e307
ca30c1c
11a5717
3db45cb
9939c8c
c1b86f4
f5dad55
d72d592
609f6f6
e06fc33
d50c25e
c5fb84f
ca53842
ac48132
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#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(); | ||
|
||
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); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,9 @@ | |
#include <utility> | ||
#include <type_traits> | ||
#include <system_error> | ||
#include <algorithm> | ||
#include <vector> | ||
#include <iterator> | ||
|
||
#if __cplusplus >= 201402L | ||
#ifdef __has_include | ||
|
@@ -2668,6 +2671,170 @@ namespace jwt { | |
} | ||
} | ||
}; | ||
|
||
template<typename json_traits> | ||
class jwk { | ||
using basic_claim_t = basic_claim<json_traits>; | ||
|
||
public: | ||
JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& jwk_token) { | ||
auto parse_claims = [](const typename json_traits::string_type& str) { | ||
using basic_claim_t = basic_claim<json_traits>; | ||
std::unordered_map<typename json_traits::string_type, basic_claim_t> res; | ||
typename json_traits::value_type val; | ||
if (!json_traits::parse(val, str)) | ||
throw std::runtime_error("Invalid json"); | ||
|
||
for (const auto& e : json_traits::as_object(val)) { | ||
res.emplace(e.first, basic_claim_t(e.second)); | ||
} | ||
|
||
return res; | ||
}; | ||
|
||
jwk_claims = parse_claims(jwk_token); | ||
} | ||
|
||
JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& claim) { | ||
for (const auto& e : json_traits::as_object(claim)) { | ||
jwk_claims.emplace(e.first, basic_claim_t(e.second)); | ||
} | ||
} | ||
|
||
/** | ||
* 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 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() const { return json_traits::as_string(get_jwk_claim("x5c").as_array().front()); } //do not use serialize() instead | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the consumer will/may need the array in larger systems as per the rfc 4.7
What do you think about having two functions? typename json_traits::array_type get_x5c() const;
typename json_traits::string_type get_x5c_key_value() const; Secondly, please throw if empty 💥 |
||
|
||
/** | ||
* 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 x5c is present ("x5c") | ||
* \return true if present, false otherwise | ||
*/ | ||
bool has_x5c() const noexcept { return has_jwk_claim("x5c"); } | ||
|
||
/** | ||
* 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.count(name) != 0; } | ||
|
||
/** | ||
* 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 { | ||
if (!has_jwk_claim(name)) { | ||
throw std::runtime_error("claim not found"); | ||
} | ||
return jwk_claims.at(name); | ||
} | ||
|
||
bool empty() const noexcept { return jwk_claims.empty(); } | ||
|
||
private: | ||
std::unordered_map<typename json_traits::string_type, basic_claim_t> jwk_claims; | ||
}; | ||
|
||
template<typename json_traits> | ||
class jwks { | ||
using basic_claim_t = basic_claim<json_traits>; | ||
using jwk_t = jwk<json_traits>; | ||
|
||
public: | ||
|
||
JWT_CLAIM_EXPLICIT jwks(const typename json_traits::string_type& jwks_token) { | ||
auto parse_claims = [](const typename json_traits::string_type& str) { | ||
std::unordered_map<typename json_traits::string_type, jwk_t> res; | ||
typename json_traits::value_type val; | ||
if (!json_traits::parse(val, str)) | ||
throw std::runtime_error("Invalid json"); | ||
|
||
for (const auto& k : json_traits::as_object(val)) { | ||
size_t id = 0; | ||
for (const typename json_traits::value_type& item : json_traits::as_array(k.second)) { | ||
jwk_t jwk_entry(item); | ||
if (jwk_entry.has_key_id()) | ||
res.emplace(jwk_entry.get_key_id(), std::move(jwk_entry)); | ||
else | ||
res.emplace(std::to_string(id), std::move(jwk_entry)); | ||
++id; | ||
} | ||
} | ||
Comment on lines
+2784
to
+2794
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, so I would really like if we could leave the KID outside of the JWKS logic because it's important to support workflows that do not required them. I will come back with some changes against your branch, I think the biggest limitation is the parsing or "map of claims" I also see we don't check |
||
|
||
return res; | ||
}; | ||
|
||
jwks_claims = parse_claims(jwks_token); | ||
} | ||
|
||
/** | ||
* 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 { | ||
if (!has_jwk(key_id)) { | ||
typename json_traits::string_type error_msg = "jwk with key id \""; | ||
error_msg += key_id; | ||
error_msg += "\" not found"; | ||
throw std::runtime_error(error_msg.c_str()); | ||
} | ||
|
||
return jwks_claims.at(key_id); | ||
} | ||
|
||
/** | ||
* Check if a jwks with the kid is present | ||
* \return true if jwks was present, false otherwise | ||
*/ | ||
bool has_jwk(const typename json_traits::string_type& key_id) const noexcept { return jwks_claims.count(key_id) != 0; } | ||
|
||
bool empty() const noexcept { return jwks_claims.empty(); } | ||
|
||
std::vector<jwk_t> to_vector() { | ||
std::vector<jwk_t> v_jwks_claims; | ||
v_jwks_claims.reserve(jwks_claims.size()); | ||
for (const auto& element : jwks_claims) | ||
v_jwks_claims.push_back(element.second); | ||
return v_jwks_claims; | ||
} | ||
|
||
private: | ||
|
||
std::unordered_map<typename json_traits::string_type, jwk_t> jwks_claims; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only because i've implemented this and used a few different auth servers... I am really unsure about this being a map... What if there are two keys neither have KIDs? One would be lost, most likely the new key that is starting to issue tokens. |
||
}; | ||
|
||
/** | ||
* Create a verifier using the given clock | ||
|
@@ -2720,6 +2887,16 @@ namespace jwt { | |
decoded_jwt<json_traits> decode(const typename json_traits::string_type& token) { | ||
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 { | ||
|
@@ -2839,6 +3016,16 @@ namespace jwt { | |
decoded_jwt<picojson_traits> decode(const std::string& token, Decode decode) { | ||
return decoded_jwt<picojson_traits>(token, decode); | ||
} | ||
|
||
inline | ||
jwk<picojson_traits> parse_jwk(const picojson_traits::string_type& token) { | ||
return jwk<picojson_traits>(token); | ||
} | ||
|
||
inline | ||
jwks<picojson_traits> parse_jwks(const picojson_traits::string_type& token) { | ||
return jwks<picojson_traits>(token); | ||
} | ||
#endif | ||
} // namespace jwt | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this block seems to be copy pasted from the original
📓 for myself to look into a small refactor!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, and it costs an extra move, good point to refactor.