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