static void on_openid_config_retrieved(grpc_exec_ctx *exec_ctx, void *user_data, grpc_error *error) { const grpc_json *cur; verifier_cb_ctx *ctx = (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; /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */ if (json == NULL) goto error; cur = find_property_by_name(json, "jwks_uri"); if (cur == NULL) { 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 == NULL) 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 = strchr(jwks_uri, '/'); if (req.http.path == NULL) { req.http.path = ""; } 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. */ grpc_resource_quota *resource_quota = grpc_resource_quota_create("jwt_verifier"); grpc_httpcli_get( exec_ctx, &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req, gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), grpc_closure_create(on_keys_retrieved, ctx), &ctx->responses[HTTP_RESPONSE_KEYS]); grpc_resource_quota_internal_unref(exec_ctx, resource_quota); grpc_json_destroy(json); gpr_free(req.host); return; error: if (json != NULL) grpc_json_destroy(json); ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL); verifier_cb_ctx_destroy(ctx); }
static void on_openid_config_retrieved(void *user_data, const grpc_httpcli_response *response) { const grpc_json *cur; grpc_json *json = json_from_http(response); verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data; grpc_httpcli_request req; const char *jwks_uri; /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time.*/ if (json == NULL) goto error; cur = find_property_by_name(json, "jwks_uri"); if (cur == NULL) { 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 == NULL) 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.use_ssl = 1; req.host = gpr_strdup(jwks_uri); req.path = strchr(jwks_uri, '/'); if (req.path == NULL) { req.path = ""; } else { *(req.host + (req.path - jwks_uri)) = '\0'; } grpc_httpcli_get( &ctx->verifier->http_ctx, ctx->pollset, &req, gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), on_keys_retrieved, ctx); grpc_json_destroy(json); gpr_free(req.host); return; error: if (json != NULL) grpc_json_destroy(json); ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL); verifier_cb_ctx_destroy(ctx); }
static void on_keys_retrieved(grpc_exec_ctx *exec_ctx, void *user_data, grpc_error *error) { verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data; grpc_json *json = json_from_http(&ctx->responses[HTTP_RESPONSE_KEYS]); EVP_PKEY *verification_key = NULL; grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR; grpc_jwt_claims *claims = NULL; if (json == NULL) { status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR; goto end; } verification_key = find_verification_key(json, ctx->header->alg, ctx->header->kid); if (verification_key == NULL) { 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 = NULL; } end: if (json != NULL) grpc_json_destroy(json); if (verification_key != NULL) EVP_PKEY_free(verification_key); ctx->user_cb(ctx->user_data, status, claims); verifier_cb_ctx_destroy(ctx); }
/* Takes ownership of ctx. */ static void retrieve_key_and_verify(grpc_exec_ctx *exec_ctx, verifier_cb_ctx *ctx) { const char *at_sign; grpc_closure *http_cb; char *path_prefix = NULL; const char *iss; grpc_httpcli_request req; memset(&req, 0, sizeof(grpc_httpcli_request)); req.handshaker = &grpc_httpcli_ssl; http_response_index rsp_idx; GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL); iss = ctx->claims->iss; if (ctx->header->kid == NULL) { gpr_log(GPR_ERROR, "Missing kid in jose header."); goto error; } if (iss == NULL) { 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. */ /* Very non-sophisticated way to detect an email address. Should be good enough for now... */ at_sign = strchr(iss, '@'); if (at_sign != NULL) { email_key_mapping *mapping; const char *email_domain = at_sign + 1; GPR_ASSERT(ctx->verifier != NULL); mapping = verifier_get_mapping(ctx->verifier, email_domain); if (mapping == NULL) { 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 == NULL) { 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); 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 == NULL) { 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); 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. */ grpc_resource_quota *resource_quota = grpc_resource_quota_create("jwt_verifier"); grpc_httpcli_get( exec_ctx, &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req, gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), http_cb, &ctx->responses[rsp_idx]); grpc_resource_quota_internal_unref(exec_ctx, 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, NULL); verifier_cb_ctx_destroy(ctx); }
/* Takes ownership of ctx. */ static void retrieve_key_and_verify(verifier_cb_ctx *ctx) { const char *at_sign; grpc_httpcli_response_cb http_cb; char *path_prefix = NULL; const char *iss; grpc_httpcli_request req; memset(&req, 0, sizeof(grpc_httpcli_request)); req.use_ssl = 1; GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL); iss = ctx->claims->iss; if (ctx->header->kid == NULL) { gpr_log(GPR_ERROR, "Missing kid in jose header."); goto error; } if (iss == NULL) { 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. */ /* Very non-sophisticated way to detect an email address. Should be good enough for now... */ at_sign = strchr(iss, '@'); if (at_sign != NULL) { email_key_mapping *mapping; const char *email_domain = at_sign + 1; GPR_ASSERT(ctx->verifier != NULL); mapping = verifier_get_mapping(ctx->verifier, email_domain); if (mapping == NULL) { 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 == NULL) { gpr_asprintf(&req.path, "/%s", iss); } else { *(path_prefix++) = '\0'; gpr_asprintf(&req.path, "/%s/%s", path_prefix, iss); } http_cb = on_keys_retrieved; } else { req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss); path_prefix = strchr(req.host, '/'); if (path_prefix == NULL) { req.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX); } else { *(path_prefix++) = 0; gpr_asprintf(&req.path, "/%s%s", path_prefix, GRPC_OPENID_CONFIG_URL_SUFFIX); } http_cb = on_openid_config_retrieved; } grpc_httpcli_get( &ctx->verifier->http_ctx, ctx->pollset, &req, gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), http_cb, ctx); gpr_free(req.host); gpr_free(req.path); return; error: ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL); verifier_cb_ctx_destroy(ctx); }