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