1 /*
2 *
3 * Copyright 2015 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include <grpc/support/port_platform.h>
20
21 #include "src/core/lib/security/credentials/jwt/jwt_verifier.h"
22
23 #include <limits.h>
24 #include <string.h>
25
26 #include <grpc/support/alloc.h>
27 #include <grpc/support/log.h>
28 #include <grpc/support/string_util.h>
29 #include <grpc/support/sync.h>
30
31 extern "C" {
32 #include <openssl/bn.h>
33 #include <openssl/pem.h>
34 #include <openssl/rsa.h>
35 }
36
37 #include "src/core/lib/gpr/string.h"
38 #include "src/core/lib/gprpp/manual_constructor.h"
39 #include "src/core/lib/http/httpcli.h"
40 #include "src/core/lib/iomgr/polling_entity.h"
41 #include "src/core/lib/slice/b64.h"
42 #include "src/core/lib/slice/slice_internal.h"
43 #include "src/core/tsi/ssl_types.h"
44
45 using grpc_core::Json;
46
47 /* --- Utils. --- */
48
grpc_jwt_verifier_status_to_string(grpc_jwt_verifier_status status)49 const char* grpc_jwt_verifier_status_to_string(
50 grpc_jwt_verifier_status status) {
51 switch (status) {
52 case GRPC_JWT_VERIFIER_OK:
53 return "OK";
54 case GRPC_JWT_VERIFIER_BAD_SIGNATURE:
55 return "BAD_SIGNATURE";
56 case GRPC_JWT_VERIFIER_BAD_FORMAT:
57 return "BAD_FORMAT";
58 case GRPC_JWT_VERIFIER_BAD_AUDIENCE:
59 return "BAD_AUDIENCE";
60 case GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR:
61 return "KEY_RETRIEVAL_ERROR";
62 case GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE:
63 return "TIME_CONSTRAINT_FAILURE";
64 case GRPC_JWT_VERIFIER_GENERIC_ERROR:
65 return "GENERIC_ERROR";
66 default:
67 return "UNKNOWN";
68 }
69 }
70
evp_md_from_alg(const char * alg)71 static const EVP_MD* evp_md_from_alg(const char* alg) {
72 if (strcmp(alg, "RS256") == 0) {
73 return EVP_sha256();
74 } else if (strcmp(alg, "RS384") == 0) {
75 return EVP_sha384();
76 } else if (strcmp(alg, "RS512") == 0) {
77 return EVP_sha512();
78 } else {
79 return nullptr;
80 }
81 }
82
parse_json_part_from_jwt(const char * str,size_t len)83 static Json parse_json_part_from_jwt(const char* str, size_t len) {
84 grpc_slice slice = grpc_base64_decode_with_len(str, len, 1);
85 if (GRPC_SLICE_IS_EMPTY(slice)) {
86 gpr_log(GPR_ERROR, "Invalid base64.");
87 return Json(); // JSON null
88 }
89 absl::string_view string = grpc_core::StringViewFromSlice(slice);
90 grpc_error* error = GRPC_ERROR_NONE;
91 Json json = Json::Parse(string, &error);
92 if (error != GRPC_ERROR_NONE) {
93 gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
94 GRPC_ERROR_UNREF(error);
95 json = Json(); // JSON null
96 }
97 grpc_slice_unref_internal(slice);
98 return json;
99 }
100
validate_string_field(const Json & json,const char * key)101 static const char* validate_string_field(const Json& json, const char* key) {
102 if (json.type() != Json::Type::STRING) {
103 gpr_log(GPR_ERROR, "Invalid %s field", key);
104 return nullptr;
105 }
106 return json.string_value().c_str();
107 }
108
validate_time_field(const Json & json,const char * key)109 static gpr_timespec validate_time_field(const Json& json, const char* key) {
110 gpr_timespec result = gpr_time_0(GPR_CLOCK_REALTIME);
111 if (json.type() != Json::Type::NUMBER) {
112 gpr_log(GPR_ERROR, "Invalid %s field", key);
113 return result;
114 }
115 result.tv_sec = strtol(json.string_value().c_str(), nullptr, 10);
116 return result;
117 }
118
119 /* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */
120
121 struct jose_header {
122 const char* alg;
123 const char* kid;
124 const char* typ;
125 /* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */
126 grpc_core::ManualConstructor<Json> json;
127 };
jose_header_destroy(jose_header * h)128 static void jose_header_destroy(jose_header* h) {
129 h->json.Destroy();
130 gpr_free(h);
131 }
132
jose_header_from_json(Json json)133 static jose_header* jose_header_from_json(Json json) {
134 const char* alg_value;
135 Json::Object::const_iterator it;
136 jose_header* h = static_cast<jose_header*>(gpr_zalloc(sizeof(jose_header)));
137 if (json.type() != Json::Type::OBJECT) {
138 gpr_log(GPR_ERROR, "JSON value is not an object");
139 goto error;
140 }
141 // Check alg field.
142 it = json.object_value().find("alg");
143 if (it == json.object_value().end()) {
144 gpr_log(GPR_ERROR, "Missing alg field.");
145 goto error;
146 }
147 /* We only support RSA-1.5 signatures for now.
148 Beware of this if we add HMAC support:
149 https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
150 */
151 alg_value = it->second.string_value().c_str();
152 if (it->second.type() != Json::Type::STRING || strncmp(alg_value, "RS", 2) ||
153 evp_md_from_alg(alg_value) == nullptr) {
154 gpr_log(GPR_ERROR, "Invalid alg field");
155 goto error;
156 }
157 h->alg = alg_value;
158 // Check typ field.
159 it = json.object_value().find("typ");
160 if (it != json.object_value().end()) {
161 h->typ = validate_string_field(it->second, "typ");
162 if (h->typ == nullptr) goto error;
163 }
164 // Check kid field.
165 it = json.object_value().find("kid");
166 if (it != json.object_value().end()) {
167 h->kid = validate_string_field(it->second, "kid");
168 if (h->kid == nullptr) goto error;
169 }
170 h->json.Init(std::move(json));
171 return h;
172
173 error:
174 jose_header_destroy(h);
175 return nullptr;
176 }
177
178 /* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */
179
180 struct grpc_jwt_claims {
181 /* Well known properties already parsed. */
182 const char* sub;
183 const char* iss;
184 const char* aud;
185 const char* jti;
186 gpr_timespec iat;
187 gpr_timespec exp;
188 gpr_timespec nbf;
189
190 grpc_core::ManualConstructor<Json> json;
191 };
192
grpc_jwt_claims_destroy(grpc_jwt_claims * claims)193 void grpc_jwt_claims_destroy(grpc_jwt_claims* claims) {
194 claims->json.Destroy();
195 gpr_free(claims);
196 }
197
grpc_jwt_claims_json(const grpc_jwt_claims * claims)198 const Json* grpc_jwt_claims_json(const grpc_jwt_claims* claims) {
199 if (claims == nullptr) return nullptr;
200 return claims->json.get();
201 }
202
grpc_jwt_claims_subject(const grpc_jwt_claims * claims)203 const char* grpc_jwt_claims_subject(const grpc_jwt_claims* claims) {
204 if (claims == nullptr) return nullptr;
205 return claims->sub;
206 }
207
grpc_jwt_claims_issuer(const grpc_jwt_claims * claims)208 const char* grpc_jwt_claims_issuer(const grpc_jwt_claims* claims) {
209 if (claims == nullptr) return nullptr;
210 return claims->iss;
211 }
212
grpc_jwt_claims_id(const grpc_jwt_claims * claims)213 const char* grpc_jwt_claims_id(const grpc_jwt_claims* claims) {
214 if (claims == nullptr) return nullptr;
215 return claims->jti;
216 }
217
grpc_jwt_claims_audience(const grpc_jwt_claims * claims)218 const char* grpc_jwt_claims_audience(const grpc_jwt_claims* claims) {
219 if (claims == nullptr) return nullptr;
220 return claims->aud;
221 }
222
grpc_jwt_claims_issued_at(const grpc_jwt_claims * claims)223 gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims* claims) {
224 if (claims == nullptr) return gpr_inf_past(GPR_CLOCK_REALTIME);
225 return claims->iat;
226 }
227
grpc_jwt_claims_expires_at(const grpc_jwt_claims * claims)228 gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims* claims) {
229 if (claims == nullptr) return gpr_inf_future(GPR_CLOCK_REALTIME);
230 return claims->exp;
231 }
232
grpc_jwt_claims_not_before(const grpc_jwt_claims * claims)233 gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims* claims) {
234 if (claims == nullptr) return gpr_inf_past(GPR_CLOCK_REALTIME);
235 return claims->nbf;
236 }
237
grpc_jwt_claims_from_json(Json json)238 grpc_jwt_claims* grpc_jwt_claims_from_json(Json json) {
239 grpc_jwt_claims* claims =
240 static_cast<grpc_jwt_claims*>(gpr_zalloc(sizeof(grpc_jwt_claims)));
241 claims->json.Init(std::move(json));
242 claims->iat = gpr_inf_past(GPR_CLOCK_REALTIME);
243 claims->nbf = gpr_inf_past(GPR_CLOCK_REALTIME);
244 claims->exp = gpr_inf_future(GPR_CLOCK_REALTIME);
245
246 /* Per the spec, all fields are optional. */
247 for (const auto& p : claims->json->object_value()) {
248 if (p.first == "sub") {
249 claims->sub = validate_string_field(p.second, "sub");
250 if (claims->sub == nullptr) goto error;
251 } else if (p.first == "iss") {
252 claims->iss = validate_string_field(p.second, "iss");
253 if (claims->iss == nullptr) goto error;
254 } else if (p.first == "aud") {
255 claims->aud = validate_string_field(p.second, "aud");
256 if (claims->aud == nullptr) goto error;
257 } else if (p.first == "jti") {
258 claims->jti = validate_string_field(p.second, "jti");
259 if (claims->jti == nullptr) goto error;
260 } else if (p.first == "iat") {
261 claims->iat = validate_time_field(p.second, "iat");
262 if (gpr_time_cmp(claims->iat, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) {
263 goto error;
264 }
265 } else if (p.first == "exp") {
266 claims->exp = validate_time_field(p.second, "exp");
267 if (gpr_time_cmp(claims->exp, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) {
268 goto error;
269 }
270 } else if (p.first == "nbf") {
271 claims->nbf = validate_time_field(p.second, "nbf");
272 if (gpr_time_cmp(claims->nbf, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) {
273 goto error;
274 }
275 }
276 }
277 return claims;
278
279 error:
280 grpc_jwt_claims_destroy(claims);
281 return nullptr;
282 }
283
grpc_jwt_claims_check(const grpc_jwt_claims * claims,const char * audience)284 grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims* claims,
285 const char* audience) {
286 gpr_timespec skewed_now;
287 int audience_ok;
288
289 GPR_ASSERT(claims != nullptr);
290
291 skewed_now =
292 gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
293 if (gpr_time_cmp(skewed_now, claims->nbf) < 0) {
294 gpr_log(GPR_ERROR, "JWT is not valid yet.");
295 return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
296 }
297 skewed_now =
298 gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
299 if (gpr_time_cmp(skewed_now, claims->exp) > 0) {
300 gpr_log(GPR_ERROR, "JWT is expired.");
301 return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
302 }
303
304 /* This should be probably up to the upper layer to decide but let's harcode
305 the 99% use case here for email issuers, where the JWT must be self
306 issued. */
307 if (grpc_jwt_issuer_email_domain(claims->iss) != nullptr &&
308 claims->sub != nullptr && strcmp(claims->iss, claims->sub) != 0) {
309 gpr_log(GPR_ERROR,
310 "Email issuer (%s) cannot assert another subject (%s) than itself.",
311 claims->iss, claims->sub);
312 return GRPC_JWT_VERIFIER_BAD_SUBJECT;
313 }
314
315 if (audience == nullptr) {
316 audience_ok = claims->aud == nullptr;
317 } else {
318 audience_ok = claims->aud != nullptr && strcmp(audience, claims->aud) == 0;
319 }
320 if (!audience_ok) {
321 gpr_log(GPR_ERROR, "Audience mismatch: expected %s and found %s.",
322 audience == nullptr ? "NULL" : audience,
323 claims->aud == nullptr ? "NULL" : claims->aud);
324 return GRPC_JWT_VERIFIER_BAD_AUDIENCE;
325 }
326 return GRPC_JWT_VERIFIER_OK;
327 }
328
329 /* --- verifier_cb_ctx object. --- */
330
331 typedef enum {
332 HTTP_RESPONSE_OPENID = 0,
333 HTTP_RESPONSE_KEYS,
334 HTTP_RESPONSE_COUNT /* must be last */
335 } http_response_index;
336
337 struct verifier_cb_ctx {
338 grpc_jwt_verifier* verifier;
339 grpc_polling_entity pollent;
340 jose_header* header;
341 grpc_jwt_claims* claims;
342 char* audience;
343 grpc_slice signature;
344 grpc_slice signed_data;
345 void* user_data;
346 grpc_jwt_verification_done_cb user_cb;
347 grpc_http_response responses[HTTP_RESPONSE_COUNT];
348 };
349 /* Takes ownership of the header, claims and signature. */
verifier_cb_ctx_create(grpc_jwt_verifier * verifier,grpc_pollset * pollset,jose_header * header,grpc_jwt_claims * claims,const char * audience,const grpc_slice & signature,const char * signed_jwt,size_t signed_jwt_len,void * user_data,grpc_jwt_verification_done_cb cb)350 static verifier_cb_ctx* verifier_cb_ctx_create(
351 grpc_jwt_verifier* verifier, grpc_pollset* pollset, jose_header* header,
352 grpc_jwt_claims* claims, const char* audience, const grpc_slice& signature,
353 const char* signed_jwt, size_t signed_jwt_len, void* user_data,
354 grpc_jwt_verification_done_cb cb) {
355 grpc_core::ApplicationCallbackExecCtx callback_exec_ctx;
356 grpc_core::ExecCtx exec_ctx;
357 verifier_cb_ctx* ctx =
358 static_cast<verifier_cb_ctx*>(gpr_zalloc(sizeof(verifier_cb_ctx)));
359 ctx->verifier = verifier;
360 ctx->pollent = grpc_polling_entity_create_from_pollset(pollset);
361 ctx->header = header;
362 ctx->audience = gpr_strdup(audience);
363 ctx->claims = claims;
364 ctx->signature = signature;
365 ctx->signed_data = grpc_slice_from_copied_buffer(signed_jwt, signed_jwt_len);
366 ctx->user_data = user_data;
367 ctx->user_cb = cb;
368
369 return ctx;
370 }
371
verifier_cb_ctx_destroy(verifier_cb_ctx * ctx)372 void verifier_cb_ctx_destroy(verifier_cb_ctx* ctx) {
373 if (ctx->audience != nullptr) gpr_free(ctx->audience);
374 if (ctx->claims != nullptr) grpc_jwt_claims_destroy(ctx->claims);
375 grpc_slice_unref_internal(ctx->signature);
376 grpc_slice_unref_internal(ctx->signed_data);
377 jose_header_destroy(ctx->header);
378 for (size_t i = 0; i < HTTP_RESPONSE_COUNT; i++) {
379 grpc_http_response_destroy(&ctx->responses[i]);
380 }
381 /* TODO: see what to do with claims... */
382 gpr_free(ctx);
383 }
384
385 /* --- grpc_jwt_verifier object. --- */
386
387 /* Clock skew defaults to one minute. */
388 gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN};
389
390 /* Max delay defaults to one minute. */
391 grpc_millis grpc_jwt_verifier_max_delay = 60 * GPR_MS_PER_SEC;
392
393 struct email_key_mapping {
394 char* email_domain;
395 char* key_url_prefix;
396 };
397 struct grpc_jwt_verifier {
398 email_key_mapping* mappings;
399 size_t num_mappings; /* Should be very few, linear search ok. */
400 size_t allocated_mappings;
401 grpc_httpcli_context http_ctx;
402 };
403
json_from_http(const grpc_httpcli_response * response)404 static Json json_from_http(const grpc_httpcli_response* response) {
405 if (response == nullptr) {
406 gpr_log(GPR_ERROR, "HTTP response is NULL.");
407 return Json(); // JSON null
408 }
409 if (response->status != 200) {
410 gpr_log(GPR_ERROR, "Call to http server failed with error %d.",
411 response->status);
412 return Json(); // JSON null
413 }
414 grpc_error* error = GRPC_ERROR_NONE;
415 Json json = Json::Parse(
416 absl::string_view(response->body, response->body_length), &error);
417 if (error != GRPC_ERROR_NONE) {
418 gpr_log(GPR_ERROR, "Invalid JSON found in response.");
419 return Json(); // JSON null
420 }
421 return json;
422 }
423
find_property_by_name(const Json & json,const char * name)424 static const Json* find_property_by_name(const Json& json, const char* name) {
425 auto it = json.object_value().find(name);
426 if (it == json.object_value().end()) {
427 return nullptr;
428 }
429 return &it->second;
430 }
431
extract_pkey_from_x509(const char * x509_str)432 static EVP_PKEY* extract_pkey_from_x509(const char* x509_str) {
433 X509* x509 = nullptr;
434 EVP_PKEY* result = nullptr;
435 BIO* bio = BIO_new(BIO_s_mem());
436 size_t len = strlen(x509_str);
437 GPR_ASSERT(len < INT_MAX);
438 BIO_write(bio, x509_str, static_cast<int>(len));
439 x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
440 if (x509 == nullptr) {
441 gpr_log(GPR_ERROR, "Unable to parse x509 cert.");
442 goto end;
443 }
444 result = X509_get_pubkey(x509);
445 if (result == nullptr) {
446 gpr_log(GPR_ERROR, "Cannot find public key in X509 cert.");
447 }
448
449 end:
450 BIO_free(bio);
451 X509_free(x509);
452 return result;
453 }
454
bignum_from_base64(const char * b64)455 static BIGNUM* bignum_from_base64(const char* b64) {
456 BIGNUM* result = nullptr;
457 grpc_slice bin;
458
459 if (b64 == nullptr) return nullptr;
460 bin = grpc_base64_decode(b64, 1);
461 if (GRPC_SLICE_IS_EMPTY(bin)) {
462 gpr_log(GPR_ERROR, "Invalid base64 for big num.");
463 return nullptr;
464 }
465 result = BN_bin2bn(GRPC_SLICE_START_PTR(bin),
466 TSI_SIZE_AS_SIZE(GRPC_SLICE_LENGTH(bin)), nullptr);
467 grpc_slice_unref_internal(bin);
468 return result;
469 }
470
471 #if OPENSSL_VERSION_NUMBER < 0x10100000L
472
473 // Provide compatibility across OpenSSL 1.02 and 1.1.
RSA_set0_key(RSA * r,BIGNUM * n,BIGNUM * e,BIGNUM * d)474 static int RSA_set0_key(RSA* r, BIGNUM* n, BIGNUM* e, BIGNUM* d) {
475 /* If the fields n and e in r are NULL, the corresponding input
476 * parameters MUST be non-NULL for n and e. d may be
477 * left NULL (in case only the public key is used).
478 */
479 if ((r->n == nullptr && n == nullptr) || (r->e == nullptr && e == nullptr)) {
480 return 0;
481 }
482
483 if (n != nullptr) {
484 BN_free(r->n);
485 r->n = n;
486 }
487 if (e != nullptr) {
488 BN_free(r->e);
489 r->e = e;
490 }
491 if (d != nullptr) {
492 BN_free(r->d);
493 r->d = d;
494 }
495
496 return 1;
497 }
498 #endif // OPENSSL_VERSION_NUMBER < 0x10100000L
499
pkey_from_jwk(const Json & json,const char * kty)500 static EVP_PKEY* pkey_from_jwk(const Json& json, const char* kty) {
501 RSA* rsa = nullptr;
502 EVP_PKEY* result = nullptr;
503 BIGNUM* tmp_n = nullptr;
504 BIGNUM* tmp_e = nullptr;
505 Json::Object::const_iterator it;
506
507 GPR_ASSERT(json.type() == Json::Type::OBJECT);
508 GPR_ASSERT(kty != nullptr);
509 if (strcmp(kty, "RSA") != 0) {
510 gpr_log(GPR_ERROR, "Unsupported key type %s.", kty);
511 goto end;
512 }
513 rsa = RSA_new();
514 if (rsa == nullptr) {
515 gpr_log(GPR_ERROR, "Could not create rsa key.");
516 goto end;
517 }
518 it = json.object_value().find("n");
519 if (it == json.object_value().end()) {
520 gpr_log(GPR_ERROR, "Missing RSA public key field.");
521 goto end;
522 }
523 tmp_n = bignum_from_base64(validate_string_field(it->second, "n"));
524 if (tmp_n == nullptr) goto end;
525 it = json.object_value().find("e");
526 if (it == json.object_value().end()) {
527 gpr_log(GPR_ERROR, "Missing RSA public key field.");
528 goto end;
529 }
530 tmp_e = bignum_from_base64(validate_string_field(it->second, "e"));
531 if (tmp_e == nullptr) goto end;
532 if (!RSA_set0_key(rsa, tmp_n, tmp_e, nullptr)) {
533 gpr_log(GPR_ERROR, "Cannot set RSA key from inputs.");
534 goto end;
535 }
536 /* RSA_set0_key takes ownership on success. */
537 tmp_n = nullptr;
538 tmp_e = nullptr;
539 result = EVP_PKEY_new();
540 EVP_PKEY_set1_RSA(result, rsa); /* uprefs rsa. */
541
542 end:
543 RSA_free(rsa);
544 BN_free(tmp_n);
545 BN_free(tmp_e);
546 return result;
547 }
548
find_verification_key(const Json & json,const char * header_alg,const char * header_kid)549 static EVP_PKEY* find_verification_key(const Json& json, const char* header_alg,
550 const char* header_kid) {
551 /* Try to parse the json as a JWK set:
552 https://tools.ietf.org/html/rfc7517#section-5. */
553 const Json* jwt_keys = find_property_by_name(json, "keys");
554 if (jwt_keys == nullptr) {
555 /* Use the google proprietary format which is:
556 { <kid1>: <x5091>, <kid2>: <x5092>, ... } */
557 const Json* cur = find_property_by_name(json, header_kid);
558 if (cur == nullptr) return nullptr;
559 return extract_pkey_from_x509(cur->string_value().c_str());
560 }
561 if (jwt_keys->type() != Json::Type::ARRAY) {
562 gpr_log(GPR_ERROR,
563 "Unexpected value type of keys property in jwks key set.");
564 return nullptr;
565 }
566 /* Key format is specified in:
567 https://tools.ietf.org/html/rfc7518#section-6. */
568 for (const Json& jkey : jwt_keys->array_value()) {
569 if (jkey.type() != Json::Type::OBJECT) continue;
570 const char* alg = nullptr;
571 auto it = jkey.object_value().find("alg");
572 if (it != jkey.object_value().end()) {
573 alg = validate_string_field(it->second, "alg");
574 }
575 const char* kid = nullptr;
576 it = jkey.object_value().find("kid");
577 if (it != jkey.object_value().end()) {
578 kid = validate_string_field(it->second, "kid");
579 }
580 const char* kty = nullptr;
581 it = jkey.object_value().find("kty");
582 if (it != jkey.object_value().end()) {
583 kty = validate_string_field(it->second, "kty");
584 }
585 if (alg != nullptr && kid != nullptr && kty != nullptr &&
586 strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) {
587 return pkey_from_jwk(jkey, kty);
588 }
589 }
590 gpr_log(GPR_ERROR,
591 "Could not find matching key in key set for kid=%s and alg=%s",
592 header_kid, header_alg);
593 return nullptr;
594 }
595
verify_jwt_signature(EVP_PKEY * key,const char * alg,const grpc_slice & signature,const grpc_slice & signed_data)596 static int verify_jwt_signature(EVP_PKEY* key, const char* alg,
597 const grpc_slice& signature,
598 const grpc_slice& signed_data) {
599 EVP_MD_CTX* md_ctx = EVP_MD_CTX_create();
600 const EVP_MD* md = evp_md_from_alg(alg);
601 int result = 0;
602
603 GPR_ASSERT(md != nullptr); /* Checked before. */
604 if (md_ctx == nullptr) {
605 gpr_log(GPR_ERROR, "Could not create EVP_MD_CTX.");
606 goto end;
607 }
608 if (EVP_DigestVerifyInit(md_ctx, nullptr, md, nullptr, key) != 1) {
609 gpr_log(GPR_ERROR, "EVP_DigestVerifyInit failed.");
610 goto end;
611 }
612 if (EVP_DigestVerifyUpdate(md_ctx, GRPC_SLICE_START_PTR(signed_data),
613 GRPC_SLICE_LENGTH(signed_data)) != 1) {
614 gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed.");
615 goto end;
616 }
617 if (EVP_DigestVerifyFinal(md_ctx, GRPC_SLICE_START_PTR(signature),
618 GRPC_SLICE_LENGTH(signature)) != 1) {
619 gpr_log(GPR_ERROR, "JWT signature verification failed.");
620 goto end;
621 }
622 result = 1;
623
624 end:
625 EVP_MD_CTX_destroy(md_ctx);
626 return result;
627 }
628
on_keys_retrieved(void * user_data,grpc_error *)629 static void on_keys_retrieved(void* user_data, grpc_error* /*error*/) {
630 verifier_cb_ctx* ctx = static_cast<verifier_cb_ctx*>(user_data);
631 Json json = json_from_http(&ctx->responses[HTTP_RESPONSE_KEYS]);
632 EVP_PKEY* verification_key = nullptr;
633 grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR;
634 grpc_jwt_claims* claims = nullptr;
635
636 if (json.type() == Json::Type::JSON_NULL) {
637 status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
638 goto end;
639 }
640 verification_key =
641 find_verification_key(json, ctx->header->alg, ctx->header->kid);
642 if (verification_key == nullptr) {
643 gpr_log(GPR_ERROR, "Could not find verification key with kid %s.",
644 ctx->header->kid);
645 status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
646 goto end;
647 }
648
649 if (!verify_jwt_signature(verification_key, ctx->header->alg, ctx->signature,
650 ctx->signed_data)) {
651 status = GRPC_JWT_VERIFIER_BAD_SIGNATURE;
652 goto end;
653 }
654
655 status = grpc_jwt_claims_check(ctx->claims, ctx->audience);
656 if (status == GRPC_JWT_VERIFIER_OK) {
657 /* Pass ownership. */
658 claims = ctx->claims;
659 ctx->claims = nullptr;
660 }
661
662 end:
663 EVP_PKEY_free(verification_key);
664 ctx->user_cb(ctx->user_data, status, claims);
665 verifier_cb_ctx_destroy(ctx);
666 }
667
on_openid_config_retrieved(void * user_data,grpc_error *)668 static void on_openid_config_retrieved(void* user_data, grpc_error* /*error*/) {
669 verifier_cb_ctx* ctx = static_cast<verifier_cb_ctx*>(user_data);
670 const grpc_http_response* response = &ctx->responses[HTTP_RESPONSE_OPENID];
671 Json json = json_from_http(response);
672 grpc_httpcli_request req;
673 const char* jwks_uri;
674 grpc_resource_quota* resource_quota = nullptr;
675 const Json* cur;
676
677 /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */
678 if (json.type() == Json::Type::JSON_NULL) goto error;
679 cur = find_property_by_name(json, "jwks_uri");
680 if (cur == nullptr) {
681 gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config.");
682 goto error;
683 }
684 jwks_uri = validate_string_field(*cur, "jwks_uri");
685 if (jwks_uri == nullptr) goto error;
686 if (strstr(jwks_uri, "https://") != jwks_uri) {
687 gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri);
688 goto error;
689 }
690 jwks_uri += 8;
691 req.handshaker = &grpc_httpcli_ssl;
692 req.host = gpr_strdup(jwks_uri);
693 req.http.path = const_cast<char*>(strchr(jwks_uri, '/'));
694 if (req.http.path == nullptr) {
695 req.http.path = (char*)"";
696 } else {
697 *(req.host + (req.http.path - jwks_uri)) = '\0';
698 }
699
700 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
701 channel. This would allow us to cancel an authentication query when under
702 extreme memory pressure. */
703 resource_quota = grpc_resource_quota_create("jwt_verifier");
704 grpc_httpcli_get(
705 &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req,
706 grpc_core::ExecCtx::Get()->Now() + grpc_jwt_verifier_max_delay,
707 GRPC_CLOSURE_CREATE(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx),
708 &ctx->responses[HTTP_RESPONSE_KEYS]);
709 grpc_resource_quota_unref_internal(resource_quota);
710 gpr_free(req.host);
711 return;
712
713 error:
714 ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, nullptr);
715 verifier_cb_ctx_destroy(ctx);
716 }
717
verifier_get_mapping(grpc_jwt_verifier * v,const char * email_domain)718 static email_key_mapping* verifier_get_mapping(grpc_jwt_verifier* v,
719 const char* email_domain) {
720 size_t i;
721 if (v->mappings == nullptr) return nullptr;
722 for (i = 0; i < v->num_mappings; i++) {
723 if (strcmp(email_domain, v->mappings[i].email_domain) == 0) {
724 return &v->mappings[i];
725 }
726 }
727 return nullptr;
728 }
729
verifier_put_mapping(grpc_jwt_verifier * v,const char * email_domain,const char * key_url_prefix)730 static void verifier_put_mapping(grpc_jwt_verifier* v, const char* email_domain,
731 const char* key_url_prefix) {
732 email_key_mapping* mapping = verifier_get_mapping(v, email_domain);
733 GPR_ASSERT(v->num_mappings < v->allocated_mappings);
734 if (mapping != nullptr) {
735 gpr_free(mapping->key_url_prefix);
736 mapping->key_url_prefix = gpr_strdup(key_url_prefix);
737 return;
738 }
739 v->mappings[v->num_mappings].email_domain = gpr_strdup(email_domain);
740 v->mappings[v->num_mappings].key_url_prefix = gpr_strdup(key_url_prefix);
741 v->num_mappings++;
742 GPR_ASSERT(v->num_mappings <= v->allocated_mappings);
743 }
744
745 /* Very non-sophisticated way to detect an email address. Should be good
746 enough for now... */
grpc_jwt_issuer_email_domain(const char * issuer)747 const char* grpc_jwt_issuer_email_domain(const char* issuer) {
748 const char* at_sign = strchr(issuer, '@');
749 if (at_sign == nullptr) return nullptr;
750 const char* email_domain = at_sign + 1;
751 if (*email_domain == '\0') return nullptr;
752 const char* dot = strrchr(email_domain, '.');
753 if (dot == nullptr || dot == email_domain) return email_domain;
754 GPR_ASSERT(dot > email_domain);
755 /* There may be a subdomain, we just want the domain. */
756 dot = static_cast<const char*>(gpr_memrchr(
757 (void*)email_domain, '.', static_cast<size_t>(dot - email_domain)));
758 if (dot == nullptr) return email_domain;
759 return dot + 1;
760 }
761
762 /* Takes ownership of ctx. */
retrieve_key_and_verify(verifier_cb_ctx * ctx)763 static void retrieve_key_and_verify(verifier_cb_ctx* ctx) {
764 const char* email_domain;
765 grpc_closure* http_cb;
766 char* path_prefix = nullptr;
767 const char* iss;
768 grpc_httpcli_request req;
769 grpc_resource_quota* resource_quota = nullptr;
770 memset(&req, 0, sizeof(grpc_httpcli_request));
771 req.handshaker = &grpc_httpcli_ssl;
772 http_response_index rsp_idx;
773
774 GPR_ASSERT(ctx != nullptr && ctx->header != nullptr &&
775 ctx->claims != nullptr);
776 iss = ctx->claims->iss;
777 if (ctx->header->kid == nullptr) {
778 gpr_log(GPR_ERROR, "Missing kid in jose header.");
779 goto error;
780 }
781 if (iss == nullptr) {
782 gpr_log(GPR_ERROR, "Missing iss in claims.");
783 goto error;
784 }
785
786 /* This code relies on:
787 https://openid.net/specs/openid-connect-discovery-1_0.html
788 Nobody seems to implement the account/email/webfinger part 2. of the spec
789 so we will rely instead on email/url mappings if we detect such an issuer.
790 Part 4, on the other hand is implemented by both google and salesforce. */
791 email_domain = grpc_jwt_issuer_email_domain(iss);
792 if (email_domain != nullptr) {
793 email_key_mapping* mapping;
794 GPR_ASSERT(ctx->verifier != nullptr);
795 mapping = verifier_get_mapping(ctx->verifier, email_domain);
796 if (mapping == nullptr) {
797 gpr_log(GPR_ERROR, "Missing mapping for issuer email.");
798 goto error;
799 }
800 req.host = gpr_strdup(mapping->key_url_prefix);
801 path_prefix = strchr(req.host, '/');
802 if (path_prefix == nullptr) {
803 gpr_asprintf(&req.http.path, "/%s", iss);
804 } else {
805 *(path_prefix++) = '\0';
806 gpr_asprintf(&req.http.path, "/%s/%s", path_prefix, iss);
807 }
808 http_cb =
809 GRPC_CLOSURE_CREATE(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx);
810 rsp_idx = HTTP_RESPONSE_KEYS;
811 } else {
812 req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss);
813 path_prefix = strchr(req.host, '/');
814 if (path_prefix == nullptr) {
815 req.http.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX);
816 } else {
817 *(path_prefix++) = 0;
818 gpr_asprintf(&req.http.path, "/%s%s", path_prefix,
819 GRPC_OPENID_CONFIG_URL_SUFFIX);
820 }
821 http_cb = GRPC_CLOSURE_CREATE(on_openid_config_retrieved, ctx,
822 grpc_schedule_on_exec_ctx);
823 rsp_idx = HTTP_RESPONSE_OPENID;
824 }
825
826 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
827 channel. This would allow us to cancel an authentication query when under
828 extreme memory pressure. */
829 resource_quota = grpc_resource_quota_create("jwt_verifier");
830 grpc_httpcli_get(
831 &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req,
832 grpc_core::ExecCtx::Get()->Now() + grpc_jwt_verifier_max_delay, http_cb,
833 &ctx->responses[rsp_idx]);
834 grpc_resource_quota_unref_internal(resource_quota);
835 gpr_free(req.host);
836 gpr_free(req.http.path);
837 return;
838
839 error:
840 ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, nullptr);
841 verifier_cb_ctx_destroy(ctx);
842 }
843
grpc_jwt_verifier_verify(grpc_jwt_verifier * verifier,grpc_pollset * pollset,const char * jwt,const char * audience,grpc_jwt_verification_done_cb cb,void * user_data)844 void grpc_jwt_verifier_verify(grpc_jwt_verifier* verifier,
845 grpc_pollset* pollset, const char* jwt,
846 const char* audience,
847 grpc_jwt_verification_done_cb cb,
848 void* user_data) {
849 const char* dot = nullptr;
850 jose_header* header = nullptr;
851 grpc_jwt_claims* claims = nullptr;
852 grpc_slice signature;
853 size_t signed_jwt_len;
854 const char* cur = jwt;
855 Json json;
856
857 GPR_ASSERT(verifier != nullptr && jwt != nullptr && audience != nullptr &&
858 cb != nullptr);
859 dot = strchr(cur, '.');
860 if (dot == nullptr) goto error;
861 json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur));
862 if (json.type() == Json::Type::JSON_NULL) goto error;
863 header = jose_header_from_json(std::move(json));
864 if (header == nullptr) goto error;
865
866 cur = dot + 1;
867 dot = strchr(cur, '.');
868 if (dot == nullptr) goto error;
869 json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur));
870 if (json.type() == Json::Type::JSON_NULL) goto error;
871 claims = grpc_jwt_claims_from_json(std::move(json));
872 if (claims == nullptr) goto error;
873
874 signed_jwt_len = static_cast<size_t>(dot - jwt);
875 cur = dot + 1;
876 signature = grpc_base64_decode(cur, 1);
877 if (GRPC_SLICE_IS_EMPTY(signature)) goto error;
878 retrieve_key_and_verify(
879 verifier_cb_ctx_create(verifier, pollset, header, claims, audience,
880 signature, jwt, signed_jwt_len, user_data, cb));
881 return;
882
883 error:
884 if (header != nullptr) jose_header_destroy(header);
885 if (claims != nullptr) grpc_jwt_claims_destroy(claims);
886 cb(user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, nullptr);
887 }
888
grpc_jwt_verifier_create(const grpc_jwt_verifier_email_domain_key_url_mapping * mappings,size_t num_mappings)889 grpc_jwt_verifier* grpc_jwt_verifier_create(
890 const grpc_jwt_verifier_email_domain_key_url_mapping* mappings,
891 size_t num_mappings) {
892 grpc_jwt_verifier* v =
893 static_cast<grpc_jwt_verifier*>(gpr_zalloc(sizeof(grpc_jwt_verifier)));
894 grpc_httpcli_context_init(&v->http_ctx);
895
896 /* We know at least of one mapping. */
897 v->allocated_mappings = 1 + num_mappings;
898 v->mappings = static_cast<email_key_mapping*>(
899 gpr_malloc(v->allocated_mappings * sizeof(email_key_mapping)));
900 verifier_put_mapping(v, GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN,
901 GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX);
902 /* User-Provided mappings. */
903 if (mappings != nullptr) {
904 size_t i;
905 for (i = 0; i < num_mappings; i++) {
906 verifier_put_mapping(v, mappings[i].email_domain,
907 mappings[i].key_url_prefix);
908 }
909 }
910 return v;
911 }
912
grpc_jwt_verifier_destroy(grpc_jwt_verifier * v)913 void grpc_jwt_verifier_destroy(grpc_jwt_verifier* v) {
914 size_t i;
915 if (v == nullptr) return;
916 grpc_httpcli_context_destroy(&v->http_ctx);
917 if (v->mappings != nullptr) {
918 for (i = 0; i < v->num_mappings; i++) {
919 gpr_free(v->mappings[i].email_domain);
920 gpr_free(v->mappings[i].key_url_prefix);
921 }
922 gpr_free(v->mappings);
923 }
924 gpr_free(v);
925 }
926