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