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