LCOV - code coverage report
Current view: top level - src/core/security - jwt_verifier.c (source / functions) Hit Total Coverage
Test: tmp.zDYK9MVh93 Lines: 381 459 83.0 %
Date: 2015-10-10 Functions: 34 35 97.1 %

          Line data    Source code
       1             : /*
       2             :  *
       3             :  * Copyright 2015, Google Inc.
       4             :  * All rights reserved.
       5             :  *
       6             :  * Redistribution and use in source and binary forms, with or without
       7             :  * modification, are permitted provided that the following conditions are
       8             :  * met:
       9             :  *
      10             :  *     * Redistributions of source code must retain the above copyright
      11             :  * notice, this list of conditions and the following disclaimser.
      12             :  *     * Redistributions in binary form must reproduce the above
      13             :  * copyright notice, this list of conditions and the following disclaimser
      14             :  * in the documentation and/or other materials provided with the
      15             :  * distribution.
      16             :  *     * Neither the name of Google Inc. nor the names of its
      17             :  * contributors may be used to endorse or promote products derived from
      18             :  * this software without specific prior written permission.
      19             :  *
      20             :  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      21             :  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      22             :  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      23             :  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      24             :  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      25             :  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      26             :  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      27             :  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      28             :  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      29             :  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      30             :  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      31             :  *
      32             :  */
      33             : 
      34             : #include "src/core/security/jwt_verifier.h"
      35             : 
      36             : #include <limits.h>
      37             : #include <string.h>
      38             : 
      39             : #include "src/core/httpcli/httpcli.h"
      40             : #include "src/core/security/base64.h"
      41             : 
      42             : #include <grpc/support/alloc.h>
      43             : #include <grpc/support/log.h>
      44             : #include <grpc/support/string_util.h>
      45             : #include <grpc/support/sync.h>
      46             : #include <openssl/pem.h>
      47             : 
      48             : /* --- Utils. --- */
      49             : 
      50           0 : const char *grpc_jwt_verifier_status_to_string(
      51             :     grpc_jwt_verifier_status status) {
      52           0 :   switch (status) {
      53             :     case GRPC_JWT_VERIFIER_OK:
      54           0 :       return "OK";
      55             :     case GRPC_JWT_VERIFIER_BAD_SIGNATURE:
      56           0 :       return "BAD_SIGNATURE";
      57             :     case GRPC_JWT_VERIFIER_BAD_FORMAT:
      58           0 :       return "BAD_FORMAT";
      59             :     case GRPC_JWT_VERIFIER_BAD_AUDIENCE:
      60           0 :       return "BAD_AUDIENCE";
      61             :     case GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR:
      62           0 :       return "KEY_RETRIEVAL_ERROR";
      63             :     case GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE:
      64           0 :       return "TIME_CONSTRAINT_FAILURE";
      65             :     case GRPC_JWT_VERIFIER_GENERIC_ERROR:
      66           0 :       return "GENERIC_ERROR";
      67             :     default:
      68           0 :       return "UNKNOWN";
      69             :   }
      70             : }
      71             : 
      72          10 : static const EVP_MD *evp_md_from_alg(const char *alg) {
      73          10 :   if (strcmp(alg, "RS256") == 0) {
      74          10 :     return EVP_sha256();
      75           0 :   } else if (strcmp(alg, "RS384") == 0) {
      76           0 :     return EVP_sha384();
      77           0 :   } else if (strcmp(alg, "RS512") == 0) {
      78           0 :     return EVP_sha512();
      79             :   } else {
      80           0 :     return NULL;
      81             :   }
      82             : }
      83             : 
      84          12 : static grpc_json *parse_json_part_from_jwt(const char *str, size_t len,
      85             :                                            gpr_slice *buffer) {
      86             :   grpc_json *json;
      87             : 
      88          12 :   *buffer = grpc_base64_decode_with_len(str, len, 1);
      89          12 :   if (GPR_SLICE_IS_EMPTY(*buffer)) {
      90           0 :     gpr_log(GPR_ERROR, "Invalid base64.");
      91           0 :     return NULL;
      92             :   }
      93          12 :   json = grpc_json_parse_string_with_len((char *)GPR_SLICE_START_PTR(*buffer),
      94          12 :                                          GPR_SLICE_LENGTH(*buffer));
      95          12 :   if (json == NULL) {
      96           0 :     gpr_slice_unref(*buffer);
      97           0 :     gpr_log(GPR_ERROR, "JSON parsing error.");
      98             :   }
      99          12 :   return json;
     100             : }
     101             : 
     102          52 : static const char *validate_string_field(const grpc_json *json,
     103             :                                          const char *key) {
     104          52 :   if (json->type != GRPC_JSON_STRING) {
     105           1 :     gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
     106           1 :     return NULL;
     107             :   }
     108          51 :   return json->value;
     109             : }
     110             : 
     111          15 : static gpr_timespec validate_time_field(const grpc_json *json,
     112             :                                         const char *key) {
     113          15 :   gpr_timespec result = gpr_time_0(GPR_CLOCK_REALTIME);
     114          15 :   if (json->type != GRPC_JSON_NUMBER) {
     115           0 :     gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
     116           0 :     return result;
     117             :   }
     118          15 :   result.tv_sec = strtol(json->value, NULL, 10);
     119          15 :   return result;
     120             : }
     121             : 
     122             : /* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */
     123             : 
     124             : typedef struct {
     125             :   const char *alg;
     126             :   const char *kid;
     127             :   const char *typ;
     128             :   /* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */
     129             :   gpr_slice buffer;
     130             : } jose_header;
     131             : 
     132           6 : static void jose_header_destroy(jose_header *h) {
     133           6 :   gpr_slice_unref(h->buffer);
     134           6 :   gpr_free(h);
     135           6 : }
     136             : 
     137             : /* Takes ownership of json and buffer. */
     138           6 : static jose_header *jose_header_from_json(grpc_json *json, gpr_slice buffer) {
     139             :   grpc_json *cur;
     140           6 :   jose_header *h = gpr_malloc(sizeof(jose_header));
     141           6 :   memset(h, 0, sizeof(jose_header));
     142           6 :   h->buffer = buffer;
     143          24 :   for (cur = json->child; cur != NULL; cur = cur->next) {
     144          18 :     if (strcmp(cur->key, "alg") == 0) {
     145             :       /* We only support RSA-1.5 signatures for now.
     146             :          Beware of this if we add HMAC support:
     147             :          https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
     148             :        */
     149          12 :       if (cur->type != GRPC_JSON_STRING || strncmp(cur->value, "RS", 2) ||
     150           6 :           evp_md_from_alg(cur->value) == NULL) {
     151           0 :         gpr_log(GPR_ERROR, "Invalid alg field [%s]", cur->value);
     152           0 :         goto error;
     153             :       }
     154           6 :       h->alg = cur->value;
     155          12 :     } else if (strcmp(cur->key, "typ") == 0) {
     156           6 :       h->typ = validate_string_field(cur, "typ");
     157           6 :       if (h->typ == NULL) goto error;
     158           6 :     } else if (strcmp(cur->key, "kid") == 0) {
     159           6 :       h->kid = validate_string_field(cur, "kid");
     160           6 :       if (h->kid == NULL) goto error;
     161             :     }
     162             :   }
     163           6 :   if (h->alg == NULL) {
     164           0 :     gpr_log(GPR_ERROR, "Missing alg field.");
     165           0 :     goto error;
     166             :   }
     167           6 :   grpc_json_destroy(json);
     168           6 :   h->buffer = buffer;
     169           6 :   return h;
     170             : 
     171             : error:
     172           0 :   grpc_json_destroy(json);
     173           0 :   jose_header_destroy(h);
     174           0 :   return NULL;
     175             : }
     176             : 
     177             : /* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */
     178             : 
     179             : struct grpc_jwt_claims {
     180             :   /* Well known properties already parsed. */
     181             :   const char *sub;
     182             :   const char *iss;
     183             :   const char *aud;
     184             :   const char *jti;
     185             :   gpr_timespec iat;
     186             :   gpr_timespec exp;
     187             :   gpr_timespec nbf;
     188             : 
     189             :   grpc_json *json;
     190             :   gpr_slice buffer;
     191             : };
     192             : 
     193          10 : void grpc_jwt_claims_destroy(grpc_jwt_claims *claims) {
     194          10 :   grpc_json_destroy(claims->json);
     195          10 :   gpr_slice_unref(claims->buffer);
     196          10 :   gpr_free(claims);
     197          10 : }
     198             : 
     199           2 : const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims) {
     200           2 :   if (claims == NULL) return NULL;
     201           2 :   return claims->json;
     202             : }
     203             : 
     204           2 : const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims) {
     205           2 :   if (claims == NULL) return NULL;
     206           2 :   return claims->sub;
     207             : }
     208             : 
     209           2 : const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims) {
     210           2 :   if (claims == NULL) return NULL;
     211           2 :   return claims->iss;
     212             : }
     213             : 
     214           2 : const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims) {
     215           2 :   if (claims == NULL) return NULL;
     216           2 :   return claims->jti;
     217             : }
     218             : 
     219           5 : const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims) {
     220           5 :   if (claims == NULL) return NULL;
     221           5 :   return claims->aud;
     222             : }
     223             : 
     224           1 : gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims) {
     225           1 :   if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME);
     226           1 :   return claims->iat;
     227             : }
     228             : 
     229           1 : gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims) {
     230           1 :   if (claims == NULL) return gpr_inf_future(GPR_CLOCK_REALTIME);
     231           1 :   return claims->exp;
     232             : }
     233             : 
     234           1 : gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims) {
     235           1 :   if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME);
     236           1 :   return claims->nbf;
     237             : }
     238             : 
     239             : /* Takes ownership of json and buffer even in case of failure. */
     240          10 : grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_json *json, gpr_slice buffer) {
     241             :   grpc_json *cur;
     242          10 :   grpc_jwt_claims *claims = gpr_malloc(sizeof(grpc_jwt_claims));
     243          10 :   memset(claims, 0, sizeof(grpc_jwt_claims));
     244          10 :   claims->json = json;
     245          10 :   claims->buffer = buffer;
     246          10 :   claims->iat = gpr_inf_past(GPR_CLOCK_REALTIME);
     247          10 :   claims->nbf = gpr_inf_past(GPR_CLOCK_REALTIME);
     248          10 :   claims->exp = gpr_inf_future(GPR_CLOCK_REALTIME);
     249             : 
     250             :   /* Per the spec, all fields are optional. */
     251          59 :   for (cur = json->child; cur != NULL; cur = cur->next) {
     252          50 :     if (strcmp(cur->key, "sub") == 0) {
     253           9 :       claims->sub = validate_string_field(cur, "sub");
     254           9 :       if (claims->sub == NULL) goto error;
     255          41 :     } else if (strcmp(cur->key, "iss") == 0) {
     256          10 :       claims->iss = validate_string_field(cur, "iss");
     257          10 :       if (claims->iss == NULL) goto error;
     258          31 :     } else if (strcmp(cur->key, "aud") == 0) {
     259          10 :       claims->aud = validate_string_field(cur, "aud");
     260          10 :       if (claims->aud == NULL) goto error;
     261          21 :     } else if (strcmp(cur->key, "jti") == 0) {
     262           3 :       claims->jti = validate_string_field(cur, "jti");
     263           3 :       if (claims->jti == NULL) goto error;
     264          18 :     } else if (strcmp(cur->key, "iat") == 0) {
     265           7 :       claims->iat = validate_time_field(cur, "iat");
     266           7 :       if (gpr_time_cmp(claims->iat, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
     267           0 :         goto error;
     268          11 :     } else if (strcmp(cur->key, "exp") == 0) {
     269           7 :       claims->exp = validate_time_field(cur, "exp");
     270           7 :       if (gpr_time_cmp(claims->exp, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
     271           0 :         goto error;
     272           4 :     } else if (strcmp(cur->key, "nbf") == 0) {
     273           1 :       claims->nbf = validate_time_field(cur, "nbf");
     274           1 :       if (gpr_time_cmp(claims->nbf, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
     275           0 :         goto error;
     276             :     }
     277             :   }
     278           9 :   return claims;
     279             : 
     280             : error:
     281           1 :   grpc_jwt_claims_destroy(claims);
     282           1 :   return NULL;
     283             : }
     284             : 
     285           6 : 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           6 :   GPR_ASSERT(claims != NULL);
     291             : 
     292           6 :   skewed_now =
     293           6 :       gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
     294           6 :   if (gpr_time_cmp(skewed_now, claims->nbf) < 0) {
     295           0 :     gpr_log(GPR_ERROR, "JWT is not valid yet.");
     296           0 :     return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
     297             :   }
     298           6 :   skewed_now =
     299           6 :       gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
     300           6 :   if (gpr_time_cmp(skewed_now, claims->exp) > 0) {
     301           1 :     gpr_log(GPR_ERROR, "JWT is expired.");
     302           1 :     return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
     303             :   }
     304             : 
     305           5 :   if (audience == NULL) {
     306           0 :     audience_ok = claims->aud == NULL;
     307             :   } else {
     308           5 :     audience_ok = claims->aud != NULL && strcmp(audience, claims->aud) == 0;
     309             :   }
     310           5 :   if (!audience_ok) {
     311           1 :     gpr_log(GPR_ERROR, "Audience mismatch: expected %s and found %s.",
     312             :             audience == NULL ? "NULL" : audience,
     313           1 :             claims->aud == NULL ? "NULL" : claims->aud);
     314           1 :     return GRPC_JWT_VERIFIER_BAD_AUDIENCE;
     315             :   }
     316           4 :   return GRPC_JWT_VERIFIER_OK;
     317             : }
     318             : 
     319             : /* --- verifier_cb_ctx object. --- */
     320             : 
     321             : typedef struct {
     322             :   grpc_jwt_verifier *verifier;
     323             :   grpc_pollset *pollset;
     324             :   jose_header *header;
     325             :   grpc_jwt_claims *claims;
     326             :   char *audience;
     327             :   gpr_slice signature;
     328             :   gpr_slice signed_data;
     329             :   void *user_data;
     330             :   grpc_jwt_verification_done_cb user_cb;
     331             : } verifier_cb_ctx;
     332             : 
     333             : /* Takes ownership of the header, claims and signature. */
     334           6 : static verifier_cb_ctx *verifier_cb_ctx_create(
     335             :     grpc_jwt_verifier *verifier, grpc_pollset *pollset, jose_header *header,
     336             :     grpc_jwt_claims *claims, const char *audience, gpr_slice signature,
     337             :     const char *signed_jwt, size_t signed_jwt_len, void *user_data,
     338             :     grpc_jwt_verification_done_cb cb) {
     339           6 :   verifier_cb_ctx *ctx = gpr_malloc(sizeof(verifier_cb_ctx));
     340           6 :   memset(ctx, 0, sizeof(verifier_cb_ctx));
     341           6 :   ctx->verifier = verifier;
     342           6 :   ctx->pollset = pollset;
     343           6 :   ctx->header = header;
     344           6 :   ctx->audience = gpr_strdup(audience);
     345           6 :   ctx->claims = claims;
     346           6 :   ctx->signature = signature;
     347           6 :   ctx->signed_data = gpr_slice_from_copied_buffer(signed_jwt, signed_jwt_len);
     348           6 :   ctx->user_data = user_data;
     349           6 :   ctx->user_cb = cb;
     350           6 :   return ctx;
     351             : }
     352             : 
     353           6 : void verifier_cb_ctx_destroy(verifier_cb_ctx *ctx) {
     354           6 :   if (ctx->audience != NULL) gpr_free(ctx->audience);
     355           6 :   if (ctx->claims != NULL) grpc_jwt_claims_destroy(ctx->claims);
     356           6 :   gpr_slice_unref(ctx->signature);
     357           6 :   gpr_slice_unref(ctx->signed_data);
     358           6 :   jose_header_destroy(ctx->header);
     359             :   /* TODO: see what to do with claims... */
     360           6 :   gpr_free(ctx);
     361           6 : }
     362             : 
     363             : /* --- grpc_jwt_verifier object. --- */
     364             : 
     365             : /* Clock skew defaults to one minute. */
     366             : gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN};
     367             : 
     368             : /* Max delay defaults to one minute. */
     369             : gpr_timespec grpc_jwt_verifier_max_delay = {60, 0, GPR_TIMESPAN};
     370             : 
     371             : typedef struct {
     372             :   char *email_domain;
     373             :   char *key_url_prefix;
     374             : } email_key_mapping;
     375             : 
     376             : struct grpc_jwt_verifier {
     377             :   email_key_mapping *mappings;
     378             :   size_t num_mappings; /* Should be very few, linear search ok. */
     379             :   size_t allocated_mappings;
     380             :   grpc_httpcli_context http_ctx;
     381             : };
     382             : 
     383           8 : static grpc_json *json_from_http(const grpc_httpcli_response *response) {
     384           8 :   grpc_json *json = NULL;
     385             : 
     386           8 :   if (response == NULL) {
     387           0 :     gpr_log(GPR_ERROR, "HTTP response is NULL.");
     388           0 :     return NULL;
     389             :   }
     390           8 :   if (response->status != 200) {
     391           0 :     gpr_log(GPR_ERROR, "Call to http server failed with error %d.",
     392             :             response->status);
     393           0 :     return NULL;
     394             :   }
     395             : 
     396           8 :   json = grpc_json_parse_string_with_len(response->body, response->body_length);
     397           8 :   if (json == NULL) {
     398           0 :     gpr_log(GPR_ERROR, "Invalid JSON found in response.");
     399             :   }
     400           8 :   return json;
     401             : }
     402             : 
     403          10 : static const grpc_json *find_property_by_name(const grpc_json *json,
     404             :                                               const char *name) {
     405             :   const grpc_json *cur;
     406          24 :   for (cur = json->child; cur != NULL; cur = cur->next) {
     407          20 :     if (strcmp(cur->key, name) == 0) return cur;
     408             :   }
     409           4 :   return NULL;
     410             : }
     411             : 
     412           1 : static EVP_PKEY *extract_pkey_from_x509(const char *x509_str) {
     413           1 :   X509 *x509 = NULL;
     414           1 :   EVP_PKEY *result = NULL;
     415           1 :   BIO *bio = BIO_new(BIO_s_mem());
     416           1 :   size_t len = strlen(x509_str);
     417           1 :   GPR_ASSERT(len < INT_MAX);
     418           1 :   BIO_write(bio, x509_str, (int)len);
     419           1 :   x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
     420           1 :   if (x509 == NULL) {
     421           0 :     gpr_log(GPR_ERROR, "Unable to parse x509 cert.");
     422           0 :     goto end;
     423             :   }
     424           1 :   result = X509_get_pubkey(x509);
     425           1 :   if (result == NULL) {
     426           0 :     gpr_log(GPR_ERROR, "Cannot find public key in X509 cert.");
     427             :   }
     428             : 
     429             : end:
     430           1 :   BIO_free(bio);
     431           1 :   if (x509 != NULL) X509_free(x509);
     432           1 :   return result;
     433             : }
     434             : 
     435           6 : static BIGNUM *bignum_from_base64(const char *b64) {
     436           6 :   BIGNUM *result = NULL;
     437             :   gpr_slice bin;
     438             : 
     439           6 :   if (b64 == NULL) return NULL;
     440           6 :   bin = grpc_base64_decode(b64, 1);
     441           6 :   if (GPR_SLICE_IS_EMPTY(bin)) {
     442           0 :     gpr_log(GPR_ERROR, "Invalid base64 for big num.");
     443           0 :     return NULL;
     444             :   }
     445           6 :   result =
     446           6 :       BN_bin2bn(GPR_SLICE_START_PTR(bin), (int)GPR_SLICE_LENGTH(bin), NULL);
     447           6 :   gpr_slice_unref(bin);
     448           6 :   return result;
     449             : }
     450             : 
     451           3 : static EVP_PKEY *pkey_from_jwk(const grpc_json *json, const char *kty) {
     452             :   const grpc_json *key_prop;
     453           3 :   RSA *rsa = NULL;
     454           3 :   EVP_PKEY *result = NULL;
     455             : 
     456           3 :   GPR_ASSERT(kty != NULL && json != NULL);
     457           3 :   if (strcmp(kty, "RSA") != 0) {
     458           0 :     gpr_log(GPR_ERROR, "Unsupported key type %s.", kty);
     459           0 :     goto end;
     460             :   }
     461           3 :   rsa = RSA_new();
     462           3 :   if (rsa == NULL) {
     463           0 :     gpr_log(GPR_ERROR, "Could not create rsa key.");
     464           0 :     goto end;
     465             :   }
     466          21 :   for (key_prop = json->child; key_prop != NULL; key_prop = key_prop->next) {
     467          18 :     if (strcmp(key_prop->key, "n") == 0) {
     468           3 :       rsa->n = bignum_from_base64(validate_string_field(key_prop, "n"));
     469           3 :       if (rsa->n == NULL) goto end;
     470          15 :     } else if (strcmp(key_prop->key, "e") == 0) {
     471           3 :       rsa->e = bignum_from_base64(validate_string_field(key_prop, "e"));
     472           3 :       if (rsa->e == NULL) goto end;
     473             :     }
     474             :   }
     475           3 :   if (rsa->e == NULL || rsa->n == NULL) {
     476           0 :     gpr_log(GPR_ERROR, "Missing RSA public key field.");
     477           0 :     goto end;
     478             :   }
     479           3 :   result = EVP_PKEY_new();
     480           3 :   EVP_PKEY_set1_RSA(result, rsa); /* uprefs rsa. */
     481             : 
     482             : end:
     483           3 :   if (rsa != NULL) RSA_free(rsa);
     484           3 :   return result;
     485             : }
     486             : 
     487           5 : static EVP_PKEY *find_verification_key(const grpc_json *json,
     488             :                                        const char *header_alg,
     489             :                                        const char *header_kid) {
     490             :   const grpc_json *jkey;
     491             :   const grpc_json *jwk_keys;
     492             :   /* Try to parse the json as a JWK set:
     493             :      https://tools.ietf.org/html/rfc7517#section-5. */
     494           5 :   jwk_keys = find_property_by_name(json, "keys");
     495           5 :   if (jwk_keys == NULL) {
     496             :     /* Use the google proprietary format which is:
     497             :        { <kid1>: <x5091>, <kid2>: <x5092>, ... } */
     498           2 :     const grpc_json *cur = find_property_by_name(json, header_kid);
     499           2 :     if (cur == NULL) return NULL;
     500           1 :     return extract_pkey_from_x509(cur->value);
     501             :   }
     502             : 
     503           3 :   if (jwk_keys->type != GRPC_JSON_ARRAY) {
     504           0 :     gpr_log(GPR_ERROR,
     505             :             "Unexpected value type of keys property in jwks key set.");
     506           0 :     return NULL;
     507             :   }
     508             :   /* Key format is specified in:
     509             :      https://tools.ietf.org/html/rfc7518#section-6. */
     510           3 :   for (jkey = jwk_keys->child; jkey != NULL; jkey = jkey->next) {
     511             :     grpc_json *key_prop;
     512           3 :     const char *alg = NULL;
     513           3 :     const char *kid = NULL;
     514           3 :     const char *kty = NULL;
     515             : 
     516           3 :     if (jkey->type != GRPC_JSON_OBJECT) continue;
     517          21 :     for (key_prop = jkey->child; key_prop != NULL; key_prop = key_prop->next) {
     518          21 :       if (strcmp(key_prop->key, "alg") == 0 &&
     519           3 :           key_prop->type == GRPC_JSON_STRING) {
     520           3 :         alg = key_prop->value;
     521          18 :       } else if (strcmp(key_prop->key, "kid") == 0 &&
     522           3 :                  key_prop->type == GRPC_JSON_STRING) {
     523           3 :         kid = key_prop->value;
     524          15 :       } else if (strcmp(key_prop->key, "kty") == 0 &&
     525           3 :                  key_prop->type == GRPC_JSON_STRING) {
     526           3 :         kty = key_prop->value;
     527             :       }
     528             :     }
     529           6 :     if (alg != NULL && kid != NULL && kty != NULL &&
     530           6 :         strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) {
     531           3 :       return pkey_from_jwk(jkey, kty);
     532             :     }
     533             :   }
     534           0 :   gpr_log(GPR_ERROR,
     535             :           "Could not find matching key in key set for kid=%s and alg=%s",
     536             :           header_kid, header_alg);
     537           0 :   return NULL;
     538             : }
     539             : 
     540           4 : static int verify_jwt_signature(EVP_PKEY *key, const char *alg,
     541             :                                 gpr_slice signature, gpr_slice signed_data) {
     542           4 :   EVP_MD_CTX *md_ctx = EVP_MD_CTX_create();
     543           4 :   const EVP_MD *md = evp_md_from_alg(alg);
     544           4 :   int result = 0;
     545             : 
     546           4 :   GPR_ASSERT(md != NULL); /* Checked before. */
     547           4 :   if (md_ctx == NULL) {
     548           0 :     gpr_log(GPR_ERROR, "Could not create EVP_MD_CTX.");
     549           0 :     goto end;
     550             :   }
     551           4 :   if (EVP_DigestVerifyInit(md_ctx, NULL, md, NULL, key) != 1) {
     552           0 :     gpr_log(GPR_ERROR, "EVP_DigestVerifyInit failed.");
     553           0 :     goto end;
     554             :   }
     555           4 :   if (EVP_DigestVerifyUpdate(md_ctx, GPR_SLICE_START_PTR(signed_data),
     556             :                              GPR_SLICE_LENGTH(signed_data)) != 1) {
     557           0 :     gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed.");
     558           0 :     goto end;
     559             :   }
     560           4 :   if (EVP_DigestVerifyFinal(md_ctx, GPR_SLICE_START_PTR(signature),
     561           4 :                             GPR_SLICE_LENGTH(signature)) != 1) {
     562           1 :     gpr_log(GPR_ERROR, "JWT signature verification failed.");
     563           1 :     goto end;
     564             :   }
     565           3 :   result = 1;
     566             : 
     567             : end:
     568           4 :   if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
     569           4 :   return result;
     570             : }
     571             : 
     572           5 : static void on_keys_retrieved(grpc_exec_ctx *exec_ctx, void *user_data,
     573             :                               const grpc_httpcli_response *response) {
     574           5 :   grpc_json *json = json_from_http(response);
     575           5 :   verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
     576           5 :   EVP_PKEY *verification_key = NULL;
     577           5 :   grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR;
     578           5 :   grpc_jwt_claims *claims = NULL;
     579             : 
     580           5 :   if (json == NULL) {
     581           0 :     status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
     582           0 :     goto end;
     583             :   }
     584           5 :   verification_key =
     585           5 :       find_verification_key(json, ctx->header->alg, ctx->header->kid);
     586           5 :   if (verification_key == NULL) {
     587           1 :     gpr_log(GPR_ERROR, "Could not find verification key with kid %s.",
     588           1 :             ctx->header->kid);
     589           1 :     status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
     590           1 :     goto end;
     591             :   }
     592             : 
     593           4 :   if (!verify_jwt_signature(verification_key, ctx->header->alg, ctx->signature,
     594             :                             ctx->signed_data)) {
     595           1 :     status = GRPC_JWT_VERIFIER_BAD_SIGNATURE;
     596           1 :     goto end;
     597             :   }
     598             : 
     599           3 :   status = grpc_jwt_claims_check(ctx->claims, ctx->audience);
     600           3 :   if (status == GRPC_JWT_VERIFIER_OK) {
     601             :     /* Pass ownership. */
     602           3 :     claims = ctx->claims;
     603           3 :     ctx->claims = NULL;
     604             :   }
     605             : 
     606             : end:
     607           5 :   if (json != NULL) grpc_json_destroy(json);
     608           5 :   if (verification_key != NULL) EVP_PKEY_free(verification_key);
     609           5 :   ctx->user_cb(ctx->user_data, status, claims);
     610           5 :   verifier_cb_ctx_destroy(ctx);
     611           5 : }
     612             : 
     613           3 : static void on_openid_config_retrieved(grpc_exec_ctx *exec_ctx, void *user_data,
     614             :                                        const grpc_httpcli_response *response) {
     615             :   const grpc_json *cur;
     616           3 :   grpc_json *json = json_from_http(response);
     617           3 :   verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
     618             :   grpc_httpcli_request req;
     619             :   const char *jwks_uri;
     620             : 
     621             :   /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */
     622           3 :   if (json == NULL) goto error;
     623           3 :   cur = find_property_by_name(json, "jwks_uri");
     624           3 :   if (cur == NULL) {
     625           1 :     gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config.");
     626           1 :     goto error;
     627             :   }
     628           2 :   jwks_uri = validate_string_field(cur, "jwks_uri");
     629           2 :   if (jwks_uri == NULL) goto error;
     630           2 :   if (strstr(jwks_uri, "https://") != jwks_uri) {
     631           0 :     gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri);
     632           0 :     goto error;
     633             :   }
     634           2 :   jwks_uri += 8;
     635           2 :   req.handshaker = &grpc_httpcli_ssl;
     636           2 :   req.host = gpr_strdup(jwks_uri);
     637           2 :   req.path = strchr(jwks_uri, '/');
     638           2 :   if (req.path == NULL) {
     639           0 :     req.path = "";
     640             :   } else {
     641           2 :     *(req.host + (req.path - jwks_uri)) = '\0';
     642             :   }
     643           4 :   grpc_httpcli_get(
     644           2 :       exec_ctx, &ctx->verifier->http_ctx, ctx->pollset, &req,
     645             :       gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay),
     646             :       on_keys_retrieved, ctx);
     647           2 :   grpc_json_destroy(json);
     648           2 :   gpr_free(req.host);
     649           5 :   return;
     650             : 
     651             : error:
     652           1 :   if (json != NULL) grpc_json_destroy(json);
     653           1 :   ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL);
     654           1 :   verifier_cb_ctx_destroy(ctx);
     655             : }
     656             : 
     657          11 : static email_key_mapping *verifier_get_mapping(grpc_jwt_verifier *v,
     658             :                                                const char *email_domain) {
     659             :   size_t i;
     660          11 :   if (v->mappings == NULL) return NULL;
     661          13 :   for (i = 0; i < v->num_mappings; i++) {
     662           5 :     if (strcmp(email_domain, v->mappings[i].email_domain) == 0) {
     663           3 :       return &v->mappings[i];
     664             :     }
     665             :   }
     666           8 :   return NULL;
     667             : }
     668             : 
     669           8 : static void verifier_put_mapping(grpc_jwt_verifier *v, const char *email_domain,
     670             :                                  const char *key_url_prefix) {
     671           8 :   email_key_mapping *mapping = verifier_get_mapping(v, email_domain);
     672           8 :   GPR_ASSERT(v->num_mappings < v->allocated_mappings);
     673           8 :   if (mapping != NULL) {
     674           0 :     gpr_free(mapping->key_url_prefix);
     675           0 :     mapping->key_url_prefix = gpr_strdup(key_url_prefix);
     676           8 :     return;
     677             :   }
     678           8 :   v->mappings[v->num_mappings].email_domain = gpr_strdup(email_domain);
     679           8 :   v->mappings[v->num_mappings].key_url_prefix = gpr_strdup(key_url_prefix);
     680           8 :   v->num_mappings++;
     681           8 :   GPR_ASSERT(v->num_mappings <= v->allocated_mappings);
     682             : }
     683             : 
     684             : /* Takes ownership of ctx. */
     685           6 : static void retrieve_key_and_verify(grpc_exec_ctx *exec_ctx,
     686             :                                     verifier_cb_ctx *ctx) {
     687             :   const char *at_sign;
     688             :   grpc_httpcli_response_cb http_cb;
     689           6 :   char *path_prefix = NULL;
     690             :   const char *iss;
     691             :   grpc_httpcli_request req;
     692           6 :   memset(&req, 0, sizeof(grpc_httpcli_request));
     693           6 :   req.handshaker = &grpc_httpcli_ssl;
     694             : 
     695           6 :   GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL);
     696           6 :   iss = ctx->claims->iss;
     697           6 :   if (ctx->header->kid == NULL) {
     698           0 :     gpr_log(GPR_ERROR, "Missing kid in jose header.");
     699           0 :     goto error;
     700             :   }
     701           6 :   if (iss == NULL) {
     702           0 :     gpr_log(GPR_ERROR, "Missing iss in claims.");
     703           0 :     goto error;
     704             :   }
     705             : 
     706             :   /* This code relies on:
     707             :      https://openid.net/specs/openid-connect-discovery-1_0.html
     708             :      Nobody seems to implement the account/email/webfinger part 2. of the spec
     709             :      so we will rely instead on email/url mappings if we detect such an issuer.
     710             :      Part 4, on the other hand is implemented by both google and salesforce. */
     711             : 
     712             :   /* Very non-sophisticated way to detect an email address. Should be good
     713             :      enough for now... */
     714           6 :   at_sign = strchr(iss, '@');
     715           6 :   if (at_sign != NULL) {
     716             :     email_key_mapping *mapping;
     717           3 :     const char *email_domain = at_sign + 1;
     718           3 :     GPR_ASSERT(ctx->verifier != NULL);
     719           3 :     mapping = verifier_get_mapping(ctx->verifier, email_domain);
     720           3 :     if (mapping == NULL) {
     721           0 :       gpr_log(GPR_ERROR, "Missing mapping for issuer email.");
     722           0 :       goto error;
     723             :     }
     724           3 :     req.host = gpr_strdup(mapping->key_url_prefix);
     725           3 :     path_prefix = strchr(req.host, '/');
     726           3 :     if (path_prefix == NULL) {
     727           0 :       gpr_asprintf(&req.path, "/%s", iss);
     728             :     } else {
     729           3 :       *(path_prefix++) = '\0';
     730           3 :       gpr_asprintf(&req.path, "/%s/%s", path_prefix, iss);
     731             :     }
     732           3 :     http_cb = on_keys_retrieved;
     733             :   } else {
     734           3 :     req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss);
     735           3 :     path_prefix = strchr(req.host, '/');
     736           3 :     if (path_prefix == NULL) {
     737           3 :       req.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX);
     738             :     } else {
     739           0 :       *(path_prefix++) = 0;
     740           0 :       gpr_asprintf(&req.path, "/%s%s", path_prefix,
     741             :                    GRPC_OPENID_CONFIG_URL_SUFFIX);
     742             :     }
     743           3 :     http_cb = on_openid_config_retrieved;
     744             :   }
     745             : 
     746          12 :   grpc_httpcli_get(
     747           6 :       exec_ctx, &ctx->verifier->http_ctx, ctx->pollset, &req,
     748             :       gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay),
     749             :       http_cb, ctx);
     750           6 :   gpr_free(req.host);
     751           6 :   gpr_free(req.path);
     752          12 :   return;
     753             : 
     754             : error:
     755           0 :   ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL);
     756           0 :   verifier_cb_ctx_destroy(ctx);
     757             : }
     758             : 
     759           7 : void grpc_jwt_verifier_verify(grpc_exec_ctx *exec_ctx,
     760             :                               grpc_jwt_verifier *verifier,
     761             :                               grpc_pollset *pollset, const char *jwt,
     762             :                               const char *audience,
     763             :                               grpc_jwt_verification_done_cb cb,
     764             :                               void *user_data) {
     765           7 :   const char *dot = NULL;
     766             :   grpc_json *json;
     767           7 :   jose_header *header = NULL;
     768           7 :   grpc_jwt_claims *claims = NULL;
     769             :   gpr_slice header_buffer;
     770             :   gpr_slice claims_buffer;
     771             :   gpr_slice signature;
     772             :   size_t signed_jwt_len;
     773           7 :   const char *cur = jwt;
     774             : 
     775           7 :   GPR_ASSERT(verifier != NULL && jwt != NULL && audience != NULL && cb != NULL);
     776           7 :   dot = strchr(cur, '.');
     777           7 :   if (dot == NULL) goto error;
     778           6 :   json = parse_json_part_from_jwt(cur, (size_t)(dot - cur), &header_buffer);
     779           6 :   if (json == NULL) goto error;
     780           6 :   header = jose_header_from_json(json, header_buffer);
     781           6 :   if (header == NULL) goto error;
     782             : 
     783           6 :   cur = dot + 1;
     784           6 :   dot = strchr(cur, '.');
     785           6 :   if (dot == NULL) goto error;
     786           6 :   json = parse_json_part_from_jwt(cur, (size_t)(dot - cur), &claims_buffer);
     787           6 :   if (json == NULL) goto error;
     788           6 :   claims = grpc_jwt_claims_from_json(json, claims_buffer);
     789           6 :   if (claims == NULL) goto error;
     790             : 
     791           6 :   signed_jwt_len = (size_t)(dot - jwt);
     792           6 :   cur = dot + 1;
     793           6 :   signature = grpc_base64_decode(cur, 1);
     794           6 :   if (GPR_SLICE_IS_EMPTY(signature)) goto error;
     795           6 :   retrieve_key_and_verify(
     796             :       exec_ctx,
     797             :       verifier_cb_ctx_create(verifier, pollset, header, claims, audience,
     798             :                              signature, jwt, signed_jwt_len, user_data, cb));
     799          13 :   return;
     800             : 
     801             : error:
     802           1 :   if (header != NULL) jose_header_destroy(header);
     803           1 :   if (claims != NULL) grpc_jwt_claims_destroy(claims);
     804           1 :   cb(user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, NULL);
     805             : }
     806             : 
     807           7 : grpc_jwt_verifier *grpc_jwt_verifier_create(
     808             :     const grpc_jwt_verifier_email_domain_key_url_mapping *mappings,
     809             :     size_t num_mappings) {
     810           7 :   grpc_jwt_verifier *v = gpr_malloc(sizeof(grpc_jwt_verifier));
     811           7 :   memset(v, 0, sizeof(grpc_jwt_verifier));
     812           7 :   grpc_httpcli_context_init(&v->http_ctx);
     813             : 
     814             :   /* We know at least of one mapping. */
     815           7 :   v->allocated_mappings = 1 + num_mappings;
     816           7 :   v->mappings = gpr_malloc(v->allocated_mappings * sizeof(email_key_mapping));
     817           7 :   verifier_put_mapping(v, GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN,
     818             :                        GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX);
     819             :   /* User-Provided mappings. */
     820           7 :   if (mappings != NULL) {
     821             :     size_t i;
     822           2 :     for (i = 0; i < num_mappings; i++) {
     823           1 :       verifier_put_mapping(v, mappings[i].email_domain,
     824           1 :                            mappings[i].key_url_prefix);
     825             :     }
     826             :   }
     827           7 :   return v;
     828             : }
     829             : 
     830           7 : void grpc_jwt_verifier_destroy(grpc_jwt_verifier *v) {
     831             :   size_t i;
     832          14 :   if (v == NULL) return;
     833           7 :   grpc_httpcli_context_destroy(&v->http_ctx);
     834           7 :   if (v->mappings != NULL) {
     835          15 :     for (i = 0; i < v->num_mappings; i++) {
     836           8 :       gpr_free(v->mappings[i].email_domain);
     837           8 :       gpr_free(v->mappings[i].key_url_prefix);
     838             :     }
     839           7 :     gpr_free(v->mappings);
     840             :   }
     841           7 :   gpr_free(v);
     842             : }

Generated by: LCOV version 1.10