24#ifdef PACS_WITH_DIGITAL_SIGNATURES
25#include <openssl/bio.h>
26#include <openssl/bn.h>
27#include <openssl/ec.h>
28#include <openssl/evp.h>
29#include <openssl/pem.h>
30#include <openssl/rsa.h>
39#ifdef PACS_WITH_DIGITAL_SIGNATURES
45 void operator()(BIGNUM* bn)
const { BN_free(bn); }
47using bn_ptr = std::unique_ptr<BIGNUM, bn_deleter>;
51 void operator()(EVP_PKEY* key)
const { EVP_PKEY_free(key); }
53using pkey_ptr = std::unique_ptr<EVP_PKEY, pkey_deleter>;
57 void operator()(BIO* bio)
const { BIO_free_all(bio); }
59using bio_ptr = std::unique_ptr<BIO, bio_deleter>;
62struct pkey_ctx_deleter {
63 void operator()(EVP_PKEY_CTX* ctx)
const { EVP_PKEY_CTX_free(ctx); }
65using pkey_ctx_ptr = std::unique_ptr<EVP_PKEY_CTX, pkey_ctx_deleter>;
68bn_ptr bytes_to_bn(
const std::string& bytes) {
69 return bn_ptr(BN_bin2bn(
70 reinterpret_cast<const unsigned char*
>(bytes.data()),
71 static_cast<int>(bytes.size()),
nullptr));
75std::string pkey_to_pem(EVP_PKEY* key) {
76 bio_ptr bio(BIO_new(BIO_s_mem()));
79 if (PEM_write_bio_PUBKEY(bio.get(), key) != 1)
return {};
82 long len = BIO_get_mem_data(bio.get(), &data);
83 if (len <= 0 || !data)
return {};
85 return std::string(data,
static_cast<size_t>(len));
89std::string rsa_jwk_to_pem(
const std::string& n_b64,
const std::string& e_b64) {
92 if (!n_bytes || !e_bytes)
return {};
94 auto n = bytes_to_bn(*n_bytes);
95 auto e = bytes_to_bn(*e_bytes);
96 if (!n || !e)
return {};
99 pkey_ctx_ptr ctx(EVP_PKEY_CTX_new_from_name(
nullptr,
"RSA",
nullptr));
102 if (EVP_PKEY_fromdata_init(ctx.get()) != 1)
return {};
104 OSSL_PARAM params[3];
105 params[0] = OSSL_PARAM_construct_BN(
"n",
106 const_cast<unsigned char*
>(
reinterpret_cast<const unsigned char*
>(n_bytes->data())),
108 params[1] = OSSL_PARAM_construct_BN(
"e",
109 const_cast<unsigned char*
>(
reinterpret_cast<const unsigned char*
>(e_bytes->data())),
111 params[2] = OSSL_PARAM_construct_end();
113 EVP_PKEY* pkey =
nullptr;
114 if (EVP_PKEY_fromdata(ctx.get(), &pkey, EVP_PKEY_PUBLIC_KEY, params) != 1) {
119 return pkey_to_pem(key.get());
123std::string ec_jwk_to_pem(
const std::string& x_b64,
const std::string& y_b64,
124 const std::string& crv) {
127 if (!x_bytes || !y_bytes)
return {};
130 if (crv !=
"P-256")
return {};
134 point.reserve(1 + x_bytes->size() + y_bytes->size());
140 pkey_ctx_ptr ctx(EVP_PKEY_CTX_new_from_name(
nullptr,
"EC",
nullptr));
143 if (EVP_PKEY_fromdata_init(ctx.get()) != 1)
return {};
145 const char* group_name =
"P-256";
146 OSSL_PARAM params[3];
147 params[0] = OSSL_PARAM_construct_utf8_string(
"group",
148 const_cast<char*
>(group_name), 0);
149 params[1] = OSSL_PARAM_construct_octet_string(
"pub",
150 const_cast<char*
>(point.data()), point.size());
151 params[2] = OSSL_PARAM_construct_end();
153 EVP_PKEY* pkey =
nullptr;
154 if (EVP_PKEY_fromdata(ctx.get(), &pkey, EVP_PKEY_PUBLIC_KEY, params) != 1) {
159 return pkey_to_pem(key.get());
163std::optional<jwk_key> parse_jwk(
const crow::json::rvalue& jwk) {
166 if (jwk.has(
"kid") && jwk[
"kid"].t() == crow::json::type::String) {
167 key.kid = std::string(jwk[
"kid"].s());
169 if (jwk.has(
"kty") && jwk[
"kty"].t() == crow::json::type::String) {
170 key.kty = std::string(jwk[
"kty"].s());
172 if (jwk.has(
"alg") && jwk[
"alg"].t() == crow::json::type::String) {
173 key.alg = std::string(jwk[
"alg"].s());
175 if (jwk.has(
"use") && jwk[
"use"].t() == crow::json::type::String) {
176 key.use = std::string(jwk[
"use"].s());
180 if (!key.use.empty() && key.use !=
"sig")
return std::nullopt;
182 if (key.kty ==
"RSA") {
183 if (!jwk.has(
"n") || !jwk.has(
"e"))
return std::nullopt;
184 auto n = std::string(jwk[
"n"].s());
185 auto e = std::string(jwk[
"e"].s());
186 key.pem = rsa_jwk_to_pem(n, e);
187 if (key.pem.empty())
return std::nullopt;
190 if (key.alg.empty()) key.alg =
"RS256";
191 }
else if (key.kty ==
"EC") {
192 if (!jwk.has(
"x") || !jwk.has(
"y"))
return std::nullopt;
193 auto x = std::string(jwk[
"x"].s());
194 auto y = std::string(jwk[
"y"].s());
195 auto crv = jwk.has(
"crv") ? std::string(jwk[
"crv"].s()) : std::string(
"P-256");
196 key.pem = ec_jwk_to_pem(x, y, crv);
197 if (key.pem.empty())
return std::nullopt;
199 if (key.alg.empty()) key.alg =
"ES256";
213std::optional<jwk_key> parse_jwk(
const crow::json::rvalue& ) {
228 auto json = crow::json::load(std::string(jwks_json));
229 if (!json || !json.has(
"keys"))
return false;
231 auto& keys_arr = json[
"keys"];
232 if (keys_arr.t() != crow::json::type::List)
return false;
234 std::vector<jwk_key> new_keys;
235 for (
size_t i = 0; i < keys_arr.size(); ++i) {
236 auto key = parse_jwk(keys_arr[i]);
238 new_keys.push_back(std::move(*key));
242 if (new_keys.empty())
return false;
244 std::lock_guard<std::mutex> lock(
mutex_);
245 keys_ = std::move(new_keys);
251 std::lock_guard<std::mutex> lock(
mutex_);
256 std::lock_guard<std::mutex> lock(
mutex_);
261 std::lock_guard<std::mutex> lock(
mutex_);
268 for (
const auto& key :
keys_) {
269 if (key.kid == kid)
return key;
275 std::string_view alg)
const {
276 std::lock_guard<std::mutex> lock(
mutex_);
282 for (
const auto& key :
keys_) {
283 if (key.alg == alg)
return key;
289 std::lock_guard<std::mutex> lock(
mutex_);
302 if (
last_fetch_ == std::chrono::steady_clock::time_point{})
return true;
304 auto elapsed = std::chrono::steady_clock::now() -
last_fetch_;
312 if (!json_body)
return false;
314 auto json = crow::json::load(*json_body);
315 if (!json || !json.has(
"keys"))
return false;
317 auto& keys_arr = json[
"keys"];
318 if (keys_arr.t() != crow::json::type::List)
return false;
320 std::vector<jwk_key> new_keys;
321 for (
size_t i = 0; i < keys_arr.size(); ++i) {
322 auto key = parse_jwk(keys_arr[i]);
324 new_keys.push_back(std::move(*key));
328 if (new_keys.empty())
return false;
330 keys_ = std::move(new_keys);
jwks_fetch_callback fetcher_
std::vector< jwk_key > keys_
bool is_cache_expired() const
Check if the cache has expired.
std::chrono::steady_clock::time_point last_fetch_
bool load_from_json(std::string_view jwks_json)
Load keys from a JWKS JSON string.
const std::vector< jwk_key > & keys() const
Get all currently loaded keys.
std::optional< jwk_key > get_key(std::string_view kid) const
Get a key by Key ID (kid)
std::uint32_t cache_ttl_seconds_
void set_jwks_url(const std::string &url)
Set the JWKS endpoint URL for fetching.
void set_fetcher(jwks_fetch_callback fetcher)
Set a callback for fetching JWKS from a URL.
bool refresh()
Force refresh keys from the configured JWKS URL.
void set_cache_ttl(std::uint32_t seconds)
Set cache TTL in seconds (default: 3600)
std::optional< jwk_key > get_key_by_alg(std::string_view alg) const
Get the first key matching the given algorithm.
JSON Web Key Set (JWKS) provider with key caching.
JWT (JSON Web Token) validation for OAuth 2.0.
std::function< std::optional< std::string >(const std::string &url)> jwks_fetch_callback
Callback type for fetching JWKS JSON from a URL.
std::optional< std::string > base64url_decode(std::string_view input)
Decode a Base64url-encoded string (RFC 4648 Section 5)