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
6#include "ccf/ds/hex.h"
9#include "ccf/tx.h"
10#include "http/http_jwt.h"
11
12#ifdef SGX_ATTESTATION_VERIFICATION
13# include <openenclave/attestation/verifier.h>
14#endif
15
16#include <set>
17#include <sstream>
18#if defined(INSIDE_ENCLAVE) && !defined(VIRTUAL_ENCLAVE)
19# include <openenclave/enclave.h>
20#elif defined(SGX_ATTESTATION_VERIFICATION)
21# include <openenclave/host_verify.h>
22#endif
23
24namespace ccf
25{
26 static void legacy_remove_jwt_public_signing_keys(
27 ccf::kv::Tx& tx, std::string issuer)
28 {
29 auto keys =
30 tx.rw<JwtPublicSigningKeys>(Tables::Legacy::JWT_PUBLIC_SIGNING_KEYS);
32 Tables::Legacy::JWT_PUBLIC_SIGNING_KEY_ISSUER);
33
34 key_issuer->foreach(
35 [&issuer, &keys, &key_issuer](const auto& k, const auto& v) {
36 if (v == issuer)
37 {
38 keys->remove(k);
39 key_issuer->remove(k);
40 }
41 return true;
42 });
43 }
44
45 static bool check_issuer_constraint(
46 const std::string& issuer, const std::string& constraint)
47 {
48 // Only accept key constraints for the same (sub)domain. This is to avoid
49 // setting keys from issuer A which will be used to validate iss claims for
50 // issuer B, so this doesn't make sense (at least for now).
51
52 const auto issuer_domain = ::http::parse_url_full(issuer).host;
53 const auto constraint_domain = ::http::parse_url_full(constraint).host;
54
55 if (constraint_domain.empty())
56 {
57 return false;
58 }
59
60 // Either constraint's domain == issuer's domain or it is a subdomain, e.g.:
61 // limited.facebook.com
62 // .facebook.com
63 //
64 // It may make sense to support vice-versa too, but we haven't found any
65 // instances of that so far, so leaveing it only-way only for facebook-like
66 // cases.
67 if (issuer_domain != constraint_domain)
68 {
69 const auto pattern = "." + constraint_domain;
70 return issuer_domain.ends_with(pattern);
71 }
72
73 return true;
74 }
75
76 static void remove_jwt_public_signing_keys(
77 ccf::kv::Tx& tx, std::string issuer)
78 {
79 // Unlike resetting JWT keys for a particular issuer, removing keys can be
80 // safely done on both table revisions, as soon as the application shouldn't
81 // use them anyway after being ask about that explicitly.
82 legacy_remove_jwt_public_signing_keys(tx, issuer);
83
84 auto keys =
85 tx.rw<JwtPublicSigningKeys>(Tables::JWT_PUBLIC_SIGNING_KEYS_METADATA);
86
87 keys->foreach([&issuer, &keys](const auto& k, const auto& v) {
88 auto it = find_if(v.begin(), v.end(), [&](const auto& metadata) {
89 return metadata.issuer == issuer;
90 });
91
92 if (it != v.end())
93 {
94 std::vector<OpenIDJWKMetadata> updated(v.begin(), it);
95 updated.insert(updated.end(), ++it, v.end());
96
97 if (!updated.empty())
98 {
99 keys->put(k, updated);
100 }
101 else
102 {
103 keys->remove(k);
104 }
105 }
106 return true;
107 });
108 }
109
110#ifdef SGX_ATTESTATION_VERIFICATION
111 static oe_result_t oe_verify_attestation_certificate_with_evidence_cb(
112 oe_claim_t* claims, size_t claims_length, void* arg)
113 {
114 auto claims_map = (std::map<std::string, std::vector<uint8_t>>*)arg;
115 for (size_t i = 0; i < claims_length; i++)
116 {
117 std::string claim_name(claims[i].name);
118 std::vector<uint8_t> claim_value(
119 claims[i].value, claims[i].value + claims[i].value_size);
120 claims_map->emplace(std::move(claim_name), std::move(claim_value));
121 }
122 return OE_OK;
123 }
124#endif
125
126 static bool set_jwt_public_signing_keys(
127 ccf::kv::Tx& tx,
128 const std::string& log_prefix,
129 std::string issuer,
130 const JwtIssuerMetadata& issuer_metadata,
131 const JsonWebKeySet& jwks)
132 {
133 auto keys =
134 tx.rw<JwtPublicSigningKeys>(Tables::JWT_PUBLIC_SIGNING_KEYS_METADATA);
135 // add keys
136 if (jwks.keys.empty())
137 {
138 LOG_FAIL_FMT("{}: JWKS has no keys", log_prefix);
139 return false;
140 }
141 std::map<std::string, std::vector<uint8_t>> new_keys;
142 std::map<std::string, JwtIssuer> issuer_constraints;
143 for (auto& jwk : jwks.keys)
144 {
145 if (!jwk.kid.has_value())
146 {
147 LOG_FAIL_FMT("No kid for JWT signing key");
148 return false;
149 }
150
151 if (!jwk.x5c.has_value() && jwk.x5c->empty())
152 {
153 LOG_FAIL_FMT("{}: JWKS is invalid (empty x5c)", log_prefix);
154 return false;
155 }
156
157 auto& der_base64 = jwk.x5c.value()[0];
158 ccf::Cert der;
159 auto const& kid = jwk.kid.value();
160 try
161 {
162 der = ccf::crypto::raw_from_b64(der_base64);
163 }
164 catch (const std::invalid_argument& e)
165 {
167 "{}: Could not parse x5c of key id {}: {}",
168 log_prefix,
169 kid,
170 e.what());
171 return false;
172 }
173
174 std::map<std::string, std::vector<uint8_t>> claims;
175 bool has_key_policy_sgx_claims = issuer_metadata.key_policy.has_value() &&
176 issuer_metadata.key_policy.value().sgx_claims.has_value() &&
177 !issuer_metadata.key_policy.value().sgx_claims.value().empty();
178 if (
179 issuer_metadata.key_filter == JwtIssuerKeyFilter::SGX ||
180 has_key_policy_sgx_claims)
181 {
182#ifdef SGX_ATTESTATION_VERIFICATION
183 oe_verify_attestation_certificate_with_evidence(
184 der.data(),
185 der.size(),
186 oe_verify_attestation_certificate_with_evidence_cb,
187 &claims);
188#else
189 LOG_FAIL_FMT("{}: SGX claims not supported", log_prefix);
190 return false;
191#endif
192 }
193
194 if (
195 issuer_metadata.key_filter == JwtIssuerKeyFilter::SGX && claims.empty())
196 {
198 "{}: Skipping JWT signing key with kid {} (not OE "
199 "attested)",
200 log_prefix,
201 kid);
202 continue;
203 }
204
205 if (has_key_policy_sgx_claims)
206 {
207 for (auto& [claim_name, expected_claim_val_hex] :
208 issuer_metadata.key_policy.value().sgx_claims.value())
209 {
210 if (claims.find(claim_name) == claims.end())
211 {
213 "{}: JWKS kid {} is missing the {} SGX claim",
214 log_prefix,
215 kid,
216 claim_name);
217 return false;
218 }
219 auto& actual_claim_val = claims[claim_name];
220 auto actual_claim_val_hex = ds::to_hex(actual_claim_val);
221 if (expected_claim_val_hex != actual_claim_val_hex)
222 {
224 "{}: JWKS kid {} has a mismatching {} SGX claim: {} != {}",
225 log_prefix,
226 kid,
227 claim_name,
228 expected_claim_val_hex,
229 actual_claim_val_hex);
230 return false;
231 }
232 }
233 }
234 else
235 {
236 try
237 {
239 (std::vector<uint8_t>)der); // throws on error
240 }
241 catch (std::invalid_argument& exc)
242 {
244 "{}: JWKS kid {} has an invalid X.509 certificate: {}",
245 log_prefix,
246 kid,
247 exc.what());
248 return false;
249 }
250 }
251 LOG_INFO_FMT("{}: Storing JWT signing key with kid {}", log_prefix, kid);
252 new_keys.emplace(kid, der);
253
254 if (jwk.issuer)
255 {
256 if (!check_issuer_constraint(issuer, *jwk.issuer))
257 {
259 "{}: JWKS kid {} with issuer constraint {} fails validation "
260 "against issuer {}",
261 log_prefix,
262 kid,
263 *jwk.issuer,
264 issuer);
265 return false;
266 }
267
268 issuer_constraints.emplace(kid, *jwk.issuer);
269 }
270 }
271
272 if (new_keys.empty())
273 {
274 LOG_FAIL_FMT("{}: no keys left after applying filter", log_prefix);
275 return false;
276 }
277
278 std::set<std::string> existing_kids;
279 keys->foreach([&existing_kids, &issuer_constraints, &issuer](
280 const auto& k, const auto& v) {
281 if (find_if(v.begin(), v.end(), [&](const auto& metadata) {
282 return metadata.issuer == issuer;
283 }) != v.end())
284 {
285 existing_kids.insert(k);
286 }
287
288 return true;
289 });
290
291 for (auto& [kid, der] : new_keys)
292 {
293 OpenIDJWKMetadata value{der, issuer, std::nullopt};
294 const auto it = issuer_constraints.find(kid);
295 if (it != issuer_constraints.end())
296 {
297 value.constraint = it->second;
298 }
299
300 if (existing_kids.count(kid))
301 {
302 const auto& keys_for_kid = keys->get(kid);
303 if (
304 find_if(
305 keys_for_kid->begin(),
306 keys_for_kid->end(),
307 [&value](const auto& metadata) {
308 return metadata.cert == value.cert &&
309 metadata.issuer == value.issuer &&
310 metadata.constraint == value.constraint;
311 }) != keys_for_kid->end())
312 {
313 // Avoid redundant writes. Thus, preserve the behaviour from #5027.
314 continue;
315 }
316 }
317
319 "Save JWT key kid={} issuer={}, constraint={}",
320 kid,
321 value.issuer,
322 value.constraint);
323
324 auto existing_keys = keys->get(kid);
325 if (existing_keys)
326 {
327 const auto prev = find_if(
328 existing_keys->begin(),
329 existing_keys->end(),
330 [&](const auto& issuer_with_constraint) {
331 return issuer_with_constraint.issuer == issuer;
332 });
333
334 if (prev != existing_keys->end())
335 {
336 *prev = value;
337 }
338 else
339 {
340 existing_keys->push_back(std::move(value));
341 }
342 keys->put(kid, *existing_keys);
343 }
344 else
345 {
346 keys->put(kid, std::vector<OpenIDJWKMetadata>{value});
347 }
348 }
349
350 for (auto& kid : existing_kids)
351 {
352 if (!new_keys.contains(kid))
353 {
354 auto updated = keys->get(kid);
355 updated->erase(
356 std::remove_if(
357 updated->begin(),
358 updated->end(),
359 [&](const auto& metadata) { return metadata.issuer == issuer; }),
360 updated->end());
361
362 if (updated->empty())
363 {
364 keys->remove(kid);
365 }
366 else
367 {
368 keys->put(kid, *updated);
369 }
370 }
371 }
372
373 return true;
374 }
375}
Definition tx.h:202
M::Handle * rw(M &m)
Definition tx.h:213
#define LOG_INFO_FMT
Definition logger.h:395
#define LOG_DEBUG_FMT
Definition logger.h:380
#define LOG_FAIL_FMT
Definition logger.h:396
ccf::kv::RawCopySerialisedMap< JwtKeyId, JwtIssuer > JwtPublicSigningKeyIssuer
Definition jwt.h:92
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
Definition app_interface.h:15
std::vector< uint8_t > Cert
Definition jwt.h:59
ServiceMap< JwtKeyId, std::vector< OpenIDJWKMetadata > > JwtPublicSigningKeys
Definition jwt.h:73
URL parse_url_full(const std::string &url)
Definition http_parser.h:145
std::string host
Definition http_parser.h:138
constexpr oe_result_t OE_OK
Definition virtual_enclave.h:41
int oe_result_t
Definition virtual_enclave.h:40