CCF
Loading...
Searching...
No Matches
jwt_management.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
7#include "ccf/ds/hex.h"
10#include "ccf/tx.h"
11#include "http/http_jwt.h"
12
13#include <set>
14#include <sstream>
15
17{
18 inline std::vector<uint8_t> try_parse_raw_rsa(
20 {
21 if (
22 !jwk.kid.has_value() || !jwk.e.has_value() || jwk.e->empty() ||
23 !jwk.n.has_value() || jwk.n->empty())
24 {
25 return {};
26 }
27
28 std::vector<uint8_t> der;
31 data.kid = *jwk.kid;
32 data.n = *jwk.n;
33 data.e = *jwk.e;
34 try
35 {
36 const auto pubkey = ccf::crypto::make_rsa_public_key(data);
37 return pubkey->public_key_der();
38 }
39 catch (const std::invalid_argument& exc)
40 {
41 throw std::logic_error(
42 fmt::format("Failed to construct RSA public key: {}", exc.what()));
43 }
44 }
45
46 inline std::vector<uint8_t> try_parse_raw_ec(
48 {
49 if (
50 !jwk.kid.has_value() || !jwk.x.has_value() || jwk.x->empty() ||
51 !jwk.y.has_value() || jwk.y->empty() || !jwk.crv.has_value())
52 {
53 return {};
54 }
55
58 data.kid = *jwk.kid;
59 data.crv = *jwk.crv;
60 data.x = *jwk.x;
61 data.y = *jwk.y;
62 try
63 {
64 const auto pubkey = ccf::crypto::make_ec_public_key(data);
65 return pubkey->public_key_der();
66 }
67 catch (const std::invalid_argument& exc)
68 {
69 throw std::logic_error(
70 fmt::format("Failed to construct EC public key: {}", exc.what()));
71 }
72 }
73
74 inline std::vector<uint8_t> try_parse_x5c(
76 {
77 if (!jwk.kid.has_value() || !jwk.x5c.has_value() || jwk.x5c->empty())
78 {
79 return {};
80 }
81
82 const auto& kid = *jwk.kid;
83 const auto& der_base64 = (*jwk.x5c)[0];
84 ccf::Cert der;
85 try
86 {
87 der = ccf::crypto::raw_from_b64(der_base64);
88 }
89 catch (const std::invalid_argument& e)
90 {
91 throw std::logic_error(
92 fmt::format("Could not parse x5c of key id {}: {}", kid, e.what()));
93 }
94 try
95 {
96 auto verifier = ccf::crypto::make_unique_verifier(der);
97 return verifier->public_key_der();
98 }
99 catch (std::invalid_argument& exc)
100 {
101 throw std::logic_error(fmt::format(
102 "JWKS kid {} has an invalid X.509 certificate: {}", kid, exc.what()));
103 }
104 }
105
106 inline std::vector<uint8_t> try_parse_jwk(
108 {
109 if (!jwk.kid.has_value())
110 {
111 throw std::logic_error("Missing kid for JWT signing key");
112 }
113 const auto& kid = *jwk.kid;
114 auto key = try_parse_raw_rsa(jwk);
115 if (!key.empty())
116 {
117 return key;
118 }
119 key = try_parse_raw_ec(jwk);
120 if (!key.empty())
121 {
122 return key;
123 }
124 key = try_parse_x5c(jwk);
125 if (!key.empty())
126 {
127 return key;
128 }
129
130 throw std::logic_error(
131 fmt::format("JWKS kid {} has neither RSA/EC public key or x5c", kid));
132 }
133}
134
135namespace ccf
136{
137 static bool check_issuer_constraint(
138 const std::string& issuer, const std::string& constraint)
139 {
140 // Only accept key constraints for the same (sub)domain. This is to avoid
141 // setting keys from issuer A which will be used to validate iss claims
142 // for issuer B, so this doesn't make sense (at least for now).
143
144 const auto issuer_domain = ::http::parse_url_full(issuer).host;
145 const auto constraint_domain = ::http::parse_url_full(constraint).host;
146
147 if (constraint_domain.empty())
148 {
149 return false;
150 }
151
152 // Either constraint's domain == issuer's domain or it is a subdomain,
153 // e.g.: limited.facebook.com
154 // .facebook.com
155 //
156 // It may make sense to support vice-versa too, but we haven't found any
157 // instances of that so far, so leaving it only-way only for
158 // facebook-like cases.
159 if (issuer_domain != constraint_domain)
160 {
161 const auto pattern = "." + constraint_domain;
162 return issuer_domain.ends_with(pattern);
163 }
164
165 return true;
166 }
167
168 static void remove_jwt_public_signing_keys(
169 ccf::kv::Tx& tx, std::string issuer)
170 {
171 auto* keys = tx.rw<JwtPublicSigningKeysMetadata>(
172 Tables::JWT_PUBLIC_SIGNING_KEYS_METADATA);
173
174 keys->foreach([&issuer, &keys](const auto& k, const auto& v) {
175 auto it = find_if(v.begin(), v.end(), [&](const auto& metadata) {
176 return metadata.issuer == issuer;
177 });
178
179 if (it != v.end())
180 {
181 std::vector<OpenIDJWKMetadata> updated(v.begin(), it);
182 updated.insert(updated.end(), ++it, v.end());
183
184 if (!updated.empty())
185 {
186 keys->put(k, updated);
187 }
188 else
189 {
190 keys->remove(k);
191 }
192 }
193 return true;
194 });
195 }
196
197 static bool set_jwt_public_signing_keys(
198 ccf::kv::Tx& tx,
199 const std::string& log_prefix,
200 std::string issuer,
201 const JwtIssuerMetadata& /*issuer_metadata*/,
202 const JsonWebKeySet& jwks)
203 {
204 auto* keys = tx.rw<JwtPublicSigningKeysMetadata>(
205 Tables::JWT_PUBLIC_SIGNING_KEYS_METADATA);
206 // add keys
207 if (jwks.keys.empty())
208 {
209 LOG_FAIL_FMT("{}: JWKS has no keys", log_prefix);
210 return false;
211 }
212 std::map<std::string, ECPublicKey> new_keys;
213 std::map<std::string, JwtIssuer> issuer_constraints;
214
215 try
216 {
217 for (const auto& jwk : jwks.keys)
218 {
219 if (!jwk.kid.has_value())
220 {
221 throw std::logic_error("Missing kid for JWT signing key");
222 }
223
224 const auto& kid = *jwk.kid;
225 auto key_der = jwt_management_detail::try_parse_jwk(jwk);
226
227 if (jwk.issuer)
228 {
229 if (!check_issuer_constraint(issuer, *jwk.issuer))
230 {
231 throw std::logic_error(fmt::format(
232 "JWKS kid {} with issuer constraint {} fails validation "
233 "against "
234 "issuer {}",
235 kid,
236 *jwk.issuer,
237 issuer));
238 }
239
240 issuer_constraints.emplace(kid, *jwk.issuer);
241 }
242
243 new_keys.emplace(kid, key_der);
244 }
245 }
246 catch (const std::exception& exc)
247 {
248 LOG_FAIL_FMT("{}: {}", log_prefix, exc.what());
249 return false;
250 }
251
252 if (new_keys.empty())
253 {
254 LOG_FAIL_FMT("{}: no keys left after applying filter", log_prefix);
255 return false;
256 }
257
258 std::set<std::string> existing_kids;
259 keys->foreach([&existing_kids, &issuer](const auto& k, const auto& v) {
260 if (find_if(v.begin(), v.end(), [&](const auto& metadata) {
261 return metadata.issuer == issuer;
262 }) != v.end())
263 {
264 existing_kids.insert(k);
265 }
266
267 return true;
268 });
269
270 for (auto& [kid, der] : new_keys)
271 {
272 OpenIDJWKMetadata value{
273 .public_key = der, .issuer = issuer, .constraint = std::nullopt};
274 value.public_key = der;
275
276 const auto it = issuer_constraints.find(kid);
277 if (it != issuer_constraints.end())
278 {
279 value.constraint = it->second;
280 }
281
282 if (existing_kids.contains(kid))
283 {
284 const auto& keys_for_kid = keys->get(kid);
285 if (
286 keys_for_kid.has_value() &&
287 find_if(
288 keys_for_kid->begin(),
289 keys_for_kid->end(),
290 [&value](const auto& metadata) {
291 return metadata.public_key == value.public_key &&
292 metadata.issuer == value.issuer &&
293 metadata.constraint == value.constraint;
294 }) != keys_for_kid->end())
295 {
296 // Avoid redundant writes. Thus, preserve the behaviour from #5027.
297 continue;
298 }
299 }
300
302 "Save JWT key kid={} issuer={}, constraint={}",
303 kid,
304 value.issuer,
305 value.constraint);
306
307 auto existing_keys = keys->get(kid);
308 if (existing_keys)
309 {
310 const auto prev = find_if(
311 existing_keys->begin(),
312 existing_keys->end(),
313 [&](const auto& issuer_with_constraint) {
314 return issuer_with_constraint.issuer == issuer;
315 });
316
317 if (prev != existing_keys->end())
318 {
319 *prev = value;
320 }
321 else
322 {
323 existing_keys->push_back(std::move(value));
324 }
325 keys->put(kid, *existing_keys);
326 }
327 else
328 {
329 keys->put(kid, std::vector<OpenIDJWKMetadata>{value});
330 }
331 }
332
333 for (const auto& kid : existing_kids)
334 {
335 if (!new_keys.contains(kid))
336 {
337 auto updated = keys->get(kid);
338 if (!updated.has_value())
339 {
340 continue;
341 }
342 updated->erase(
343 std::remove_if(
344 updated->begin(),
345 updated->end(),
346 [&](const auto& metadata) { return metadata.issuer == issuer; }),
347 updated->end());
348
349 if (updated->empty())
350 {
351 keys->remove(kid);
352 }
353 else
354 {
355 keys->put(kid, *updated);
356 }
357 }
358 }
359
360 return true;
361 }
362}
Definition tx.h:200
M::Handle * rw(M &m)
Definition tx.h:211
#define LOG_DEBUG_FMT
Definition internal_logger.h:14
#define LOG_FAIL_FMT
Definition internal_logger.h:16
VerifierUniquePtr make_unique_verifier(const std::vector< uint8_t > &cert)
Definition verifier.cpp:13
std::vector< uint8_t > raw_from_b64(const std::string_view &b64_string)
Definition base64.cpp:12
RSAPublicKeyPtr make_rsa_public_key(const uint8_t *data, size_t size)
Definition rsa_public_key.cpp:283
ECPublicKeyPtr make_ec_public_key(const Pem &pem)
Definition ec_public_key.cpp:331
Definition jwt_management.h:17
std::vector< uint8_t > try_parse_raw_rsa(const ccf::crypto::JsonWebKeyData &jwk)
Definition jwt_management.h:18
std::vector< uint8_t > try_parse_raw_ec(const ccf::crypto::JsonWebKeyData &jwk)
Definition jwt_management.h:46
std::vector< uint8_t > try_parse_jwk(const ccf::crypto::JsonWebKeyData &jwk)
Definition jwt_management.h:106
std::vector< uint8_t > try_parse_x5c(const ccf::crypto::JsonWebKeyData &jwk)
Definition jwt_management.h:74
Definition app_interface.h:14
std::vector< uint8_t > Cert
Definition jwt.h:37
ServiceMap< JwtKeyId, std::vector< OpenIDJWKMetadata > > JwtPublicSigningKeysMetadata
Definition jwt.h:51
URL parse_url_full(const std::string &url)
Definition http_parser.h:151
Definition jwk.h:50
std::optional< std::string > n
Definition jwk.h:54
std::optional< std::string > x
Definition jwk.h:56
std::optional< std::vector< std::string > > x5c
Definition jwk.h:53
std::optional< JsonWebKeyECCurve > crv
Definition jwk.h:58
std::optional< std::string > kid
Definition jwk.h:52
std::optional< std::string > e
Definition jwk.h:55
std::optional< std::string > y
Definition jwk.h:57
std::string x
Definition jwk.h:120
JsonWebKeyECCurve crv
Definition jwk.h:119
std::string y
Definition jwk.h:121
std::string e
Definition jwk.h:140
std::string n
Definition jwk.h:139
std::optional< std::string > kid
Definition jwk.h:28
JsonWebKeyType kty
Definition jwk.h:27
std::string host
Definition http_parser.h:144