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