text: /*
   *
   * Copyright 2015 gRPC authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  
  #include <grpc/support/port_platform.h>
  
  #include "src/core/lib/security/credentials/jwt/jwt_verifier.h"
  
  #include <limits.h>
  #include <string.h>
  
  #include <grpc/support/alloc.h>
  #include <grpc/support/log.h>
  #include <grpc/support/string_util.h>
  #include <grpc/support/sync.h>
  
  extern "C" {
  #include <openssl/pem.h>
  }
  
  #include "src/core/lib/gpr/string.h"
  #include "src/core/lib/http/httpcli.h"
  #include "src/core/lib/iomgr/polling_entity.h"
  #include "src/core/lib/slice/b64.h"
  #include "src/core/lib/slice/slice_internal.h"
  #include "src/core/tsi/ssl_types.h"
  
  /* --- Utils. --- */
  
  const char* grpc_jwt_verifier_status_to_string(
      grpc_jwt_verifier_status status) {
    switch (status) {
      case GRPC_JWT_VERIFIER_OK:
        return "OK";
      case GRPC_JWT_VERIFIER_BAD_SIGNATURE:
        return "BAD_SIGNATURE";
      case GRPC_JWT_VERIFIER_BAD_FORMAT:
        return "BAD_FORMAT";
      case GRPC_JWT_VERIFIER_BAD_AUDIENCE:
        return "BAD_AUDIENCE";
      case GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR:
        return "KEY_RETRIEVAL_ERROR";
      case GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE:
        return "TIME_CONSTRAINT_FAILURE";
      case GRPC_JWT_VERIFIER_GENERIC_ERROR:
        return "GENERIC_ERROR";
      default:
        return "UNKNOWN";
    }
  }
  
  static const EVP_MD* evp_md_from_alg(const char* alg) {
    if (strcmp(alg, "RS256") == 0) {
      return EVP_sha256();
    } else if (strcmp(alg, "RS384") == 0) {
      return EVP_sha384();
    } else if (strcmp(alg, "RS512") == 0) {
      return EVP_sha512();
    } else {
      return nullptr;
    }
  }
  
  static grpc_json* parse_json_part_from_jwt(const char* str, size_t len,
                                             grpc_slice* buffer) {
    grpc_json* json;
  
    *buffer = grpc_base64_decode_with_len(str, len, 1);
    if (GRPC_SLICE_IS_EMPTY(*buffer)) {
      gpr_log(GPR_ERROR, "Invalid base64.");
      return nullptr;
    }
    json = grpc_json_parse_string_with_len(
        reinterpret_cast<char*> GRPC_SLICE_START_PTR(*buffer),
        GRPC_SLICE_LENGTH(*buffer));
    if (json == nullptr) {
      grpc_slice_unref_internal(*buffer);
      gpr_log(GPR_ERROR, "JSON parsing error.");
    }
    return json;
  }
  
  static const char* validate_string_field(const grpc_json* json,
                                           const char* key) {
    if (json->type != GRPC_JSON_STRING) {
      gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
      return nullptr;
    }
    return json->value;
  }
  
  static gpr_timespec validate_time_field(const grpc_json* json,
                                          const char* key) {
    gpr_timespec result = gpr_time_0(GPR_CLOCK_REALTIME);
    if (json->type != GRPC_JSON_NUMBER) {
      gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
      return result;
    }
    result.tv_sec = strtol(json->value, nullptr, 10);
    return result;
  }
  
  /* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */
  
  typedef struct {
    const char* alg;
    const char* kid;
    const char* typ;
    /* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */
    grpc_slice buffer;
  } jose_header;
  
  static void jose_header_destroy(jose_header* h) {
    grpc_slice_unref_internal(h->buffer);
    gpr_free(h);
  }
  
  /* Takes ownership of json and buffer. */
  static jose_header* jose_header_from_json(grpc_json* json, grpc_slice buffer) {
    grpc_json* cur;
    jose_header* h = static_cast<jose_header*>(gpr_zalloc(sizeof(jose_header)));
    h->buffer = buffer;
    for (cur = json->child; cur != nullptr; cur = cur->next) {
      if (strcmp(cur->key, "alg") == 0) {
        /* We only support RSA-1.5 signatures for now.
           Beware of this if we add HMAC support:
           https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
         */
        if (cur->type != GRPC_JSON_STRING || strncmp(cur->value, "RS", 2) ||
            evp_md_from_alg(cur->value) == nullptr) {
          gpr_log(GPR_ERROR, "Invalid alg field [%s]", cur->value);
          goto error;
        }
        h->alg = cur->value;
      } else if (strcmp(cur->key, "typ") == 0) {
        h->typ = validate_string_field(cur, "typ");
        if (h->typ == nullptr) goto error;
      } else if (strcmp(cur->key, "kid") == 0) {
        h->kid = validate_string_field(cur, "kid");
        if (h->kid == nullptr) goto error;
      }
    }
    if (h->alg == nullptr) {
      gpr_log(GPR_ERROR, "Missing alg field.");
      goto error;
    }
    grpc_json_destroy(json);
    h->buffer = buffer;
    return h;
  
  error:
    grpc_json_destroy(json);
    jose_header_destroy(h);
    return nullptr;
  }
  
  /* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */
  
  struct grpc_jwt_claims {
    /* Well known properties already parsed. */
    const char* sub;
    const char* iss;
    const char* aud;
    const char* jti;
    gpr_timespec iat;
    gpr_timespec exp;
    gpr_timespec nbf;
  
    grpc_json* json;
    grpc_slice buffer;
  };
  
  void grpc_jwt_claims_destroy(grpc_jwt_claims* claims) {
    grpc_json_destroy(claims->json);
    grpc_slice_unref_internal(claims->buffer);
    gpr_free(claims);
  }
  
  const grpc_json* grpc_jwt_claims_json(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return nullptr;
    return claims->json;
  }
  
  const char* grpc_jwt_claims_subject(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return nullptr;
    return claims->sub;
  }
  
  const char* grpc_jwt_claims_issuer(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return nullptr;
    return claims->iss;
  }
  
  const char* grpc_jwt_claims_id(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return nullptr;
    return claims->jti;
  }
  
  const char* grpc_jwt_claims_audience(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return nullptr;
    return claims->aud;
  }
  
  gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return gpr_inf_past(GPR_CLOCK_REALTIME);
    return claims->iat;
  }
  
  gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return gpr_inf_future(GPR_CLOCK_REALTIME);
    return claims->exp;
  }
  
  gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims* claims) {
    if (claims == nullptr) return gpr_inf_past(GPR_CLOCK_REALTIME);
    return claims->nbf;
  }
  
  /* Takes ownership of json and buffer even in case of failure. */
  grpc_jwt_claims* grpc_jwt_claims_from_json(grpc_json* json, grpc_slice buffer) {
    grpc_json* cur;
    grpc_jwt_claims* claims =
        static_cast<grpc_jwt_claims*>(gpr_malloc(sizeof(grpc_jwt_claims)));
    memset(claims, 0, sizeof(grpc_jwt_claims));
    claims->json = json;
    claims->buffer = buffer;
    claims->iat = gpr_inf_past(GPR_CLOCK_REALTIME);
    claims->nbf = gpr_inf_past(GPR_CLOCK_REALTIME);
    claims->exp = gpr_inf_future(GPR_CLOCK_REALTIME);
  
    /* Per the spec, all fields are optional. */
    for (cur = json->child; cur != nullptr; cur = cur->next) {
      if (strcmp(cur->key, "sub") == 0) {
        claims->sub = validate_string_field(cur, "sub");
        if (claims->sub == nullptr) goto error;
      } else if (strcmp(cur->key, "iss") == 0) {
        claims->iss = validate_string_field(cur, "iss");
        if (claims->iss == nullptr) goto error;
      } else if (strcmp(cur->key, "aud") == 0) {
        claims->aud = validate_string_field(cur, "aud");
        if (claims->aud == nullptr) goto error;
      } else if (strcmp(cur->key, "jti") == 0) {
        claims->jti = validate_string_field(cur, "jti");
        if (claims->jti == nullptr) goto error;
      } else if (strcmp(cur->key, "iat") == 0) {
        claims->iat = validate_time_field(cur, "iat");
        if (gpr_time_cmp(claims->iat, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
          goto error;
      } else if (strcmp(cur->key, "exp") == 0) {
        claims->exp = validate_time_field(cur, "exp");
        if (gpr_time_cmp(claims->exp, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
          goto error;
      } else if (strcmp(cur->key, "nbf") == 0) {
        claims->nbf = validate_time_field(cur, "nbf");
        if (gpr_time_cmp(claims->nbf, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
          goto error;
      }
    }
    return claims;
  
  error:
    grpc_jwt_claims_destroy(claims);
    return nullptr;
  }
  
  grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims* claims,
                                                 const char* audience) {
    gpr_timespec skewed_now;
    int audience_ok;
  
    GPR_ASSERT(claims != nullptr);
  
    skewed_now =
        gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
    if (gpr_time_cmp(skewed_now, claims->nbf) < 0) {
      gpr_log(GPR_ERROR, "JWT is not valid yet.");
      return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
    }
    skewed_now =
        gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
    if (gpr_time_cmp(skewed_now, claims->exp) > 0) {
      gpr_log(GPR_ERROR, "JWT is expired.");
      return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
    }
  
    /* This should be probably up to the upper layer to decide but let's harcode
       the 99% use case here for email issuers, where the JWT must be self
       issued. */
    if (grpc_jwt_issuer_email_domain(claims->iss) != nullptr &&
        claims->sub != nullptr && strcmp(claims->iss, claims->sub) != 0) {
      gpr_log(GPR_ERROR,
              "Email issuer (%s) cannot assert another subject (%s) than itself.",
              claims->iss, claims->sub);
      return GRPC_JWT_VERIFIER_BAD_SUBJECT;
    }
  
    if (audience == nullptr) {
      audience_ok = claims->aud == nullptr;
    } else {
      audience_ok = claims->aud != nullptr && strcmp(audience, claims->aud) == 0;
    }
    if (!audience_ok) {
      gpr_log(GPR_ERROR, "Audience mismatch: expected %s and found %s.",
              audience == nullptr ? "NULL" : audience,
              claims->aud == nullptr ? "NULL" : claims->aud);
      return GRPC_JWT_VERIFIER_BAD_AUDIENCE;
    }
    return GRPC_JWT_VERIFIER_OK;
  }
  
  /* --- verifier_cb_ctx object. --- */
  
  typedef enum {
    HTTP_RESPONSE_OPENID = 0,
    HTTP_RESPONSE_KEYS,
    HTTP_RESPONSE_COUNT /* must be last */
  } http_response_index;
  
  typedef struct {
    grpc_jwt_verifier* verifier;
    grpc_polling_entity pollent;
    jose_header* header;
    grpc_jwt_claims* claims;
    char* audience;
    grpc_slice signature;
    grpc_slice signed_data;
    void* user_data;
    grpc_jwt_verification_done_cb user_cb;
    grpc_http_response responses[HTTP_RESPONSE_COUNT];
  } verifier_cb_ctx;
  
  /* Takes ownership of the header, claims and signature. */
  static verifier_cb_ctx* 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) {
    grpc_core::ExecCtx exec_ctx;
    verifier_cb_ctx* ctx =
        static_cast<verifier_cb_ctx*>(gpr_zalloc(sizeof(verifier_cb_ctx)));
    ctx->verifier = verifier;
    ctx->pollent = grpc_polling_entity_create_from_pollset(pollset);
    ctx->header = header;
    ctx->audience = gpr_strdup(audience);
    ctx->claims = claims;
    ctx->signature = signature;
    ctx->signed_data = grpc_slice_from_copied_buffer(signed_jwt, signed_jwt_len);
    ctx->user_data = user_data;
    ctx->user_cb = cb;
  
    return ctx;
  }
  
  void verifier_cb_ctx_destroy(verifier_cb_ctx* ctx) {
    if (ctx->audience != nullptr) gpr_free(ctx->audience);
    if (ctx->claims != nullptr) grpc_jwt_claims_destroy(ctx->claims);
    grpc_slice_unref_internal(ctx->signature);
    grpc_slice_unref_internal(ctx->signed_data);
    jose_header_destroy(ctx->header);
    for (size_t i = 0; i < HTTP_RESPONSE_COUNT; i++) {
      grpc_http_response_destroy(&ctx->responses[i]);
    }
    /* TODO: see what to do with claims... */
    gpr_free(ctx);
  }
  
  /* --- grpc_jwt_verifier object. --- */
  
  /* Clock skew defaults to one minute. */
  gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN};
  
  /* Max delay defaults to one minute. */
  grpc_millis grpc_jwt_verifier_max_delay = 60 * GPR_MS_PER_SEC;
  
  typedef struct {
    char* email_domain;
    char* key_url_prefix;
  } email_key_mapping;
  
  struct grpc_jwt_verifier {
    email_key_mapping* mappings;
    size_t num_mappings; /* Should be very few, linear search ok. */
    size_t allocated_mappings;
    grpc_httpcli_context http_ctx;
  };
  
  static grpc_json* json_from_http(const grpc_httpcli_response* response) {
    grpc_json* json = nullptr;
  
    if (response == nullptr) {
      gpr_log(GPR_ERROR, "HTTP response is NULL.");
      return nullptr;
    }
    if (response->status != 200) {
      gpr_log(GPR_ERROR, "Call to http server failed with error %d.",
              response->status);
      return nullptr;
    }
  
    json = grpc_json_parse_string_with_len(response->body, response->body_length);
    if (json == nullptr) {
      gpr_log(GPR_ERROR, "Invalid JSON found in response.");
    }
    return json;
  }
  
  static const grpc_json* find_property_by_name(const grpc_json* json,
                                                const char* name) {
    const grpc_json* cur;
    for (cur = json->child; cur != nullptr; cur = cur->next) {
      if (strcmp(cur->key, name) == 0) return cur;
    }
    return nullptr;
  }
  
  static EVP_PKEY* extract_pkey_from_x509(const char* x509_str) {
    X509* x509 = nullptr;
    EVP_PKEY* result = nullptr;
    BIO* bio = BIO_new(BIO_s_mem());
    size_t len = strlen(x509_str);
    GPR_ASSERT(len < INT_MAX);
    BIO_write(bio, x509_str, static_cast<int>(len));
    x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
    if (x509 == nullptr) {
      gpr_log(GPR_ERROR, "Unable to parse x509 cert.");
      goto end;
    }
    result = X509_get_pubkey(x509);
    if (result == nullptr) {
      gpr_log(GPR_ERROR, "Cannot find public key in X509 cert.");
    }
  
  end:
    BIO_free(bio);
    X509_free(x509);
    return result;
  }
  
  static BIGNUM* bignum_from_base64(const char* b64) {
    BIGNUM* result = nullptr;
    grpc_slice bin;
  
    if (b64 == nullptr) return nullptr;
    bin = grpc_base64_decode(b64, 1);
    if (GRPC_SLICE_IS_EMPTY(bin)) {
      gpr_log(GPR_ERROR, "Invalid base64 for big num.");
      return nullptr;
    }
    result = BN_bin2bn(GRPC_SLICE_START_PTR(bin),
                       TSI_SIZE_AS_SIZE(GRPC_SLICE_LENGTH(bin)), nullptr);
    grpc_slice_unref_internal(bin);
    return result;
  }
  
  #if OPENSSL_VERSION_NUMBER < 0x10100000L
  
  // Provide compatibility across OpenSSL 1.02 and 1.1.
  static int RSA_set0_key(RSA* r, BIGNUM* n, BIGNUM* e, BIGNUM* d) {
    /* If the fields n and e in r are NULL, the corresponding input
     * parameters MUST be non-NULL for n and e.  d may be
     * left NULL (in case only the public key is used).
     */
    if ((r->n == nullptr && n == nullptr) || (r->e == nullptr && e == nullptr)) {
      return 0;
    }
  
    if (n != nullptr) {
      BN_free(r->n);
      r->n = n;
    }
    if (e != nullptr) {
      BN_free(r->e);
      r->e = e;
    }
    if (d != nullptr) {
      BN_free(r->d);
      r->d = d;
    }
  
    return 1;
  }
  #endif  // OPENSSL_VERSION_NUMBER < 0x10100000L
  
  static EVP_PKEY* pkey_from_jwk(const grpc_json* json, const char* kty) {
    const grpc_json* key_prop;
    RSA* rsa = nullptr;
    EVP_PKEY* result = nullptr;
    BIGNUM* tmp_n = nullptr;
    BIGNUM* tmp_e = nullptr;
  
    GPR_ASSERT(kty != nullptr && json != nullptr);
    if (strcmp(kty, "RSA") != 0) {
      gpr_log(GPR_ERROR, "Unsupported key type %s.", kty);
      goto end;
    }
    rsa = RSA_new();
    if (rsa == nullptr) {
      gpr_log(GPR_ERROR, "Could not create rsa key.");
      goto end;
    }
    for (key_prop = json->child; key_prop != nullptr; key_prop = key_prop->next) {
      if (strcmp(key_prop->key, "n") == 0) {
        tmp_n = bignum_from_base64(validate_string_field(key_prop, "n"));
        if (tmp_n == nullptr) goto end;
      } else if (strcmp(key_prop->key, "e") == 0) {
        tmp_e = bignum_from_base64(validate_string_field(key_prop, "e"));
        if (tmp_e == nullptr) goto end;
      }
    }
    if (tmp_e == nullptr || tmp_n == nullptr) {
      gpr_log(GPR_ERROR, "Missing RSA public key field.");
      goto end;
    }
    if (!RSA_set0_key(rsa, tmp_n, tmp_e, nullptr)) {
      gpr_log(GPR_ERROR, "Cannot set RSA key from inputs.");
      goto end;
    }
    /* RSA_set0_key takes ownership on success. */
    tmp_n = nullptr;
    tmp_e = nullptr;
    result = EVP_PKEY_new();
    EVP_PKEY_set1_RSA(result, rsa); /* uprefs rsa. */
  
  end:
    RSA_free(rsa);
    BN_free(tmp_n);
    BN_free(tmp_e);
    return result;
  }
  
  static EVP_PKEY* find_verification_key(const grpc_json* json,
                                         const char* header_alg,
                                         const char* header_kid) {
    const grpc_json* jkey;
    const grpc_json* jwk_keys;
    /* Try to parse the json as a JWK set:
       https://tools.ietf.org/html/rfc7517#section-5. */
    jwk_keys = find_property_by_name(json, "keys");
    if (jwk_keys == nullptr) {
      /* Use the google proprietary format which is:
         { <kid1>: <x5091>, <kid2>: <x5092>, ... } */
      const grpc_json* cur = find_property_by_name(json, header_kid);
      if (cur == nullptr) return nullptr;
      return extract_pkey_from_x509(cur->value);
    }
  
    if (jwk_keys->type != GRPC_JSON_ARRAY) {
      gpr_log(GPR_ERROR,
              "Unexpected value type of keys property in jwks key set.");
      return nullptr;
    }
    /* Key format is specified in:
       https://tools.ietf.org/html/rfc7518#section-6. */
    for (jkey = jwk_keys->child; jkey != nullptr; jkey = jkey->next) {
      grpc_json* key_prop;
      const char* alg = nullptr;
      const char* kid = nullptr;
      const char* kty = nullptr;
  
      if (jkey->type != GRPC_JSON_OBJECT) continue;
      for (key_prop = jkey->child; key_prop != nullptr;
           key_prop = key_prop->next) {
        if (strcmp(key_prop->key, "alg") == 0 &&
            key_prop->type == GRPC_JSON_STRING) {
          alg = key_prop->value;
        } else if (strcmp(key_prop->key, "kid") == 0 &&
                   key_prop->type == GRPC_JSON_STRING) {
          kid = key_prop->value;
        } else if (strcmp(key_prop->key, "kty") == 0 &&
                   key_prop->type == GRPC_JSON_STRING) {
          kty = key_prop->value;
        }
      }
      if (alg != nullptr && kid != nullptr && kty != nullptr &&
          strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) {
        return pkey_from_jwk(jkey, kty);
      }
    }
    gpr_log(GPR_ERROR,
            "Could not find matching key in key set for kid=%s and alg=%s",
            header_kid, header_alg);
    return nullptr;
  }
  
  static int verify_jwt_signature(EVP_PKEY* key, const char* alg,
                                  grpc_slice signature, grpc_slice signed_data) {
    EVP_MD_CTX* md_ctx = EVP_MD_CTX_create();
    const EVP_MD* md = evp_md_from_alg(alg);
    int result = 0;
  
    GPR_ASSERT(md != nullptr); /* Checked before. */
    if (md_ctx == nullptr) {
      gpr_log(GPR_ERROR, "Could not create EVP_MD_CTX.");
      goto end;
    }
    if (EVP_DigestVerifyInit(md_ctx, nullptr, md, nullptr, key) != 1) {
      gpr_log(GPR_ERROR, "EVP_DigestVerifyInit failed.");
      goto end;
    }
    if (EVP_DigestVerifyUpdate(md_ctx, GRPC_SLICE_START_PTR(signed_data),
                               GRPC_SLICE_LENGTH(signed_data)) != 1) {
      gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed.");
      goto end;
    }
    if (EVP_DigestVerifyFinal(md_ctx, GRPC_SLICE_START_PTR(signature),
                              GRPC_SLICE_LENGTH(signature)) != 1) {
      gpr_log(GPR_ERROR, "JWT signature verification failed.");
      goto end;
    }
    result = 1;
  
  end:
    EVP_MD_CTX_destroy(md_ctx);
    return result;
  }
  
  static void on_keys_retrieved(void* user_data, grpc_error* error) {
    verifier_cb_ctx* ctx = static_cast<verifier_cb_ctx*>(user_data);
    grpc_json* json = json_from_http(&ctx->responses[HTTP_RESPONSE_KEYS]);
    EVP_PKEY* verification_key = nullptr;
    grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR;
    grpc_jwt_claims* claims = nullptr;
  
    if (json == nullptr) {
      status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
      goto end;
    }
    verification_key =
        find_verification_key(json, ctx->header->alg, ctx->header->kid);
    if (verification_key == nullptr) {
      gpr_log(GPR_ERROR, "Could not find verification key with kid %s.",
              ctx->header->kid);
      status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
      goto end;
    }
  
    if (!verify_jwt_signature(verification_key, ctx->header->alg, ctx->signature,
                              ctx->signed_data)) {
      status = GRPC_JWT_VERIFIER_BAD_SIGNATURE;
      goto end;
    }
  
    status = grpc_jwt_claims_check(ctx->claims, ctx->audience);
    if (status == GRPC_JWT_VERIFIER_OK) {
      /* Pass ownership. */
      claims = ctx->claims;
      ctx->claims = nullptr;
    }
  
  end:
    if (json != nullptr) grpc_json_destroy(json);
    EVP_PKEY_free(verification_key);
    ctx->user_cb(ctx->user_data, status, claims);
    verifier_cb_ctx_destroy(ctx);
  }
  
  static void on_openid_config_retrieved(void* user_data, grpc_error* error) {
    const grpc_json* cur;
    verifier_cb_ctx* ctx = static_cast<verifier_cb_ctx*>(user_data);
    const grpc_http_response* response = &ctx->responses[HTTP_RESPONSE_OPENID];
    grpc_json* json = json_from_http(response);
    grpc_httpcli_request req;
    const char* jwks_uri;
    grpc_resource_quota* resource_quota = nullptr;
  
    /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */
    if (json == nullptr) goto error;
    cur = find_property_by_name(json, "jwks_uri");
    if (cur == nullptr) {
      gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config.");
      goto error;
    }
    jwks_uri = validate_string_field(cur, "jwks_uri");
    if (jwks_uri == nullptr) goto error;
    if (strstr(jwks_uri, "https://") != jwks_uri) {
      gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri);
      goto error;
    }
    jwks_uri += 8;
    req.handshaker = &grpc_httpcli_ssl;
    req.host = gpr_strdup(jwks_uri);
    req.http.path = const_cast<char*>(strchr(jwks_uri, '/'));
    if (req.http.path == nullptr) {
      req.http.path = (char*)"";
    } else {
      *(req.host + (req.http.path - jwks_uri)) = '\0';
    }
  
    /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
       channel. This would allow us to cancel an authentication query when under
       extreme memory pressure. */
    resource_quota = grpc_resource_quota_create("jwt_verifier");
    grpc_httpcli_get(
        &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req,
        grpc_core::ExecCtx::Get()->Now() + grpc_jwt_verifier_max_delay,
        GRPC_CLOSURE_CREATE(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx),
        &ctx->responses[HTTP_RESPONSE_KEYS]);
    grpc_resource_quota_unref_internal(resource_quota);
    grpc_json_destroy(json);
    gpr_free(req.host);
    return;
  
  error:
    if (json != nullptr) grpc_json_destroy(json);
    ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, nullptr);
    verifier_cb_ctx_destroy(ctx);
  }
  
  static email_key_mapping* verifier_get_mapping(grpc_jwt_verifier* v,
                                                 const char* email_domain) {
    size_t i;
    if (v->mappings == nullptr) return nullptr;
    for (i = 0; i < v->num_mappings; i++) {
      if (strcmp(email_domain, v->mappings[i].email_domain) == 0) {
        return &v->mappings[i];
      }
    }
    return nullptr;
  }
  
  static void verifier_put_mapping(grpc_jwt_verifier* v, const char* email_domain,
                                   const char* key_url_prefix) {
    email_key_mapping* mapping = verifier_get_mapping(v, email_domain);
    GPR_ASSERT(v->num_mappings < v->allocated_mappings);
    if (mapping != nullptr) {
      gpr_free(mapping->key_url_prefix);
      mapping->key_url_prefix = gpr_strdup(key_url_prefix);
      return;
    }
    v->mappings[v->num_mappings].email_domain = gpr_strdup(email_domain);
    v->mappings[v->num_mappings].key_url_prefix = gpr_strdup(key_url_prefix);
    v->num_mappings++;
    GPR_ASSERT(v->num_mappings <= v->allocated_mappings);
  }
  
  /* Very non-sophisticated way to detect an email address. Should be good
     enough for now... */
  const char* grpc_jwt_issuer_email_domain(const char* issuer) {
    const char* at_sign = strchr(issuer, '@');
    if (at_sign == nullptr) return nullptr;
    const char* email_domain = at_sign + 1;
    if (*email_domain == '\0') return nullptr;
    const char* dot = strrchr(email_domain, '.');
    if (dot == nullptr || dot == email_domain) return email_domain;
    GPR_ASSERT(dot > email_domain);
    /* There may be a subdomain, we just want the domain. */
    dot = static_cast<const char*>(gpr_memrchr(
        (void*)email_domain, '.', static_cast<size_t>(dot - email_domain)));
    if (dot == nullptr) return email_domain;
    return dot + 1;
  }
  
  /* Takes ownership of ctx. */
  static void retrieve_key_and_verify(verifier_cb_ctx* ctx) {
    const char* email_domain;
    grpc_closure* http_cb;
    char* path_prefix = nullptr;
    const char* iss;
    grpc_httpcli_request req;
    grpc_resource_quota* resource_quota = nullptr;
    memset(&req, 0, sizeof(grpc_httpcli_request));
    req.handshaker = &grpc_httpcli_ssl;
    http_response_index rsp_idx;
  
    GPR_ASSERT(ctx != nullptr && ctx->header != nullptr &&
               ctx->claims != nullptr);
    iss = ctx->claims->iss;
    if (ctx->header->kid == nullptr) {
      gpr_log(GPR_ERROR, "Missing kid in jose header.");
      goto error;
    }
    if (iss == nullptr) {
      gpr_log(GPR_ERROR, "Missing iss in claims.");
      goto error;
    }
  
    /* This code relies on:
       https://openid.net/specs/openid-connect-discovery-1_0.html
       Nobody seems to implement the account/email/webfinger part 2. of the spec
       so we will rely instead on email/url mappings if we detect such an issuer.
       Part 4, on the other hand is implemented by both google and salesforce. */
    email_domain = grpc_jwt_issuer_email_domain(iss);
    if (email_domain != nullptr) {
      email_key_mapping* mapping;
      GPR_ASSERT(ctx->verifier != nullptr);
      mapping = verifier_get_mapping(ctx->verifier, email_domain);
      if (mapping == nullptr) {
        gpr_log(GPR_ERROR, "Missing mapping for issuer email.");
        goto error;
      }
      req.host = gpr_strdup(mapping->key_url_prefix);
      path_prefix = strchr(req.host, '/');
      if (path_prefix == nullptr) {
        gpr_asprintf(&req.http.path, "/%s", iss);
      } else {
        *(path_prefix++) = '\0';
        gpr_asprintf(&req.http.path, "/%s/%s", path_prefix, iss);
      }
      http_cb =
          GRPC_CLOSURE_CREATE(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx);
      rsp_idx = HTTP_RESPONSE_KEYS;
    } else {
      req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss);
      path_prefix = strchr(req.host, '/');
      if (path_prefix == nullptr) {
        req.http.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX);
      } else {
        *(path_prefix++) = 0;
        gpr_asprintf(&req.http.path, "/%s%s", path_prefix,
                     GRPC_OPENID_CONFIG_URL_SUFFIX);
      }
      http_cb = GRPC_CLOSURE_CREATE(on_openid_config_retrieved, ctx,
                                    grpc_schedule_on_exec_ctx);
      rsp_idx = HTTP_RESPONSE_OPENID;
    }
  
    /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
       channel. This would allow us to cancel an authentication query when under
       extreme memory pressure. */
    resource_quota = grpc_resource_quota_create("jwt_verifier");
    grpc_httpcli_get(
        &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req,
        grpc_core::ExecCtx::Get()->Now() + grpc_jwt_verifier_max_delay, http_cb,
        &ctx->responses[rsp_idx]);
    grpc_resource_quota_unref_internal(resource_quota);
    gpr_free(req.host);
    gpr_free(req.http.path);
    return;
  
  error:
    ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, nullptr);
    verifier_cb_ctx_destroy(ctx);
  }
  
  void 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) {
    const char* dot = nullptr;
    grpc_json* json;
    jose_header* header = nullptr;
    grpc_jwt_claims* claims = nullptr;
    grpc_slice header_buffer;
    grpc_slice claims_buffer;
    grpc_slice signature;
    size_t signed_jwt_len;
    const char* cur = jwt;
  
    GPR_ASSERT(verifier != nullptr && jwt != nullptr && audience != nullptr &&
               cb != nullptr);
    dot = strchr(cur, '.');
    if (dot == nullptr) goto error;
    json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur),
                                    &header_buffer);
    if (json == nullptr) goto error;
    header = jose_header_from_json(json, header_buffer);
    if (header == nullptr) goto error;
  
    cur = dot + 1;
    dot = strchr(cur, '.');
    if (dot == nullptr) goto error;
    json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur),
                                    &claims_buffer);
    if (json == nullptr) goto error;
    claims = grpc_jwt_claims_from_json(json, claims_buffer);
    if (claims == nullptr) goto error;
  
    signed_jwt_len = static_cast<size_t>(dot - jwt);
    cur = dot + 1;
    signature = grpc_base64_decode(cur, 1);
    if (GRPC_SLICE_IS_EMPTY(signature)) goto error;
    retrieve_key_and_verify(
        verifier_cb_ctx_create(verifier, pollset, header, claims, audience,
                               signature, jwt, signed_jwt_len, user_data, cb));
    return;
  
  error:
    if (header != nullptr) jose_header_destroy(header);
    if (claims != nullptr) grpc_jwt_claims_destroy(claims);
    cb(user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, nullptr);
  }
  
  grpc_jwt_verifier* grpc_jwt_verifier_create(
      const grpc_jwt_verifier_email_domain_key_url_mapping* mappings,
      size_t num_mappings) {
    grpc_jwt_verifier* v =
        static_cast<grpc_jwt_verifier*>(gpr_zalloc(sizeof(grpc_jwt_verifier)));
    grpc_httpcli_context_init(&v->http_ctx);
  
    /* We know at least of one mapping. */
    v->allocated_mappings = 1 + num_mappings;
    v->mappings = static_cast<email_key_mapping*>(
        gpr_malloc(v->allocated_mappings * sizeof(email_key_mapping)));
    verifier_put_mapping(v, GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN,
                         GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX);
    /* User-Provided mappings. */
    if (mappings != nullptr) {
      size_t i;
      for (i = 0; i < num_mappings; i++) {
        verifier_put_mapping(v, mappings[i].email_domain,
                             mappings[i].key_url_prefix);
      }
    }
    return v;
  }
  
  void grpc_jwt_verifier_destroy(grpc_jwt_verifier* v) {
    size_t i;
    if (v == nullptr) return;
    grpc_httpcli_context_destroy(&v->http_ctx);
    if (v->mappings != nullptr) {
      for (i = 0; i < v->num_mappings; i++) {
        gpr_free(v->mappings[i].email_domain);
        gpr_free(v->mappings[i].key_url_prefix);
      }
      gpr_free(v->mappings);
    }
    gpr_free(v);
  }
  