Implementing JWT credentials (a.k.a JWT ID Tokens).

- Not tested end to end yet
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index a21c27b..42d1a90 100644
--- a/src/core/security/credentials.c
+++ b/src/core/security/credentials.c
@@ -33,6 +33,10 @@
 
 #include "src/core/security/credentials.h"
 
+#include <string.h>
+#include <stdio.h>
+
+#include "src/core/json/json.h"
 #include "src/core/httpcli/httpcli.h"
 #include "src/core/iomgr/iomgr.h"
 #include "src/core/security/json_token.h"
@@ -42,14 +46,9 @@
 #include <grpc/support/sync.h>
 #include <grpc/support/time.h>
 
-#include "src/core/json/json.h"
-
-#include <string.h>
-#include <stdio.h>
-
 /* -- Constants. -- */
 
-#define GRPC_OAUTH2_TOKEN_REFRESH_THRESHOLD_SECS 60
+#define GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS 60
 
 #define GRPC_COMPUTE_ENGINE_METADATA_HOST "metadata"
 #define GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH \
@@ -113,6 +112,7 @@
 }
 
 void grpc_credentials_get_request_metadata(grpc_credentials *creds,
+                                           const char *service_url,
                                            grpc_credentials_metadata_cb cb,
                                            void *user_data) {
   if (creds == NULL || !grpc_credentials_has_request_metadata(creds) ||
@@ -122,7 +122,7 @@
     }
     return;
   }
-  creds->vtable->get_request_metadata(creds, cb, user_data);
+  creds->vtable->get_request_metadata(creds, service_url, cb, user_data);
 }
 
 void grpc_server_credentials_release(grpc_server_credentials *creds) {
@@ -288,6 +288,128 @@
   return &c->base;
 }
 
+/* -- Jwt credentials -- */
+
+typedef struct {
+  grpc_credentials base;
+  grpc_mdctx *md_ctx;
+
+  /* Have a simple cache for now with just 1 entry. We could have a map based on
+     the service_url for a more sophisticated one. */
+  gpr_mu cache_mu;
+  struct {
+    grpc_mdelem *jwt_md;
+    char *service_url;
+    gpr_timespec jwt_expiration;
+  } cached;
+
+  grpc_auth_json_key key;
+  gpr_timespec jwt_lifetime;
+} grpc_jwt_credentials;
+
+static void jwt_reset_cache(grpc_jwt_credentials *c) {
+  if (c->cached.jwt_md != NULL) {
+    grpc_mdelem_unref(c->cached.jwt_md);
+    c->cached.jwt_md = NULL;
+  }
+  if (c->cached.service_url != NULL) {
+    gpr_free(c->cached.service_url);
+    c->cached.service_url = NULL;
+  }
+  c->cached.jwt_expiration = gpr_inf_past;
+}
+
+static void jwt_destroy(grpc_credentials *creds) {
+  grpc_jwt_credentials *c = (grpc_jwt_credentials *)creds;
+  grpc_auth_json_key_destruct(&c->key);
+  jwt_reset_cache(c);
+  gpr_mu_destroy(&c->cache_mu);
+  grpc_mdctx_unref(c->md_ctx);
+  gpr_free(c);
+}
+
+static int jwt_has_request_metadata(const grpc_credentials *creds) { return 1; }
+
+static int jwt_has_request_metadata_only(const grpc_credentials *creds) {
+  return 1;
+}
+
+
+static void jwt_get_request_metadata(grpc_credentials *creds,
+                                     const char *service_url,
+                                     grpc_credentials_metadata_cb cb,
+                                     void *user_data) {
+  grpc_jwt_credentials *c = (grpc_jwt_credentials *)creds;
+  gpr_timespec refresh_threshold = {GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
+                                    0};
+
+  /* See if we can return a cached jwt. */
+  grpc_mdelem *jwt_md = NULL;
+  {
+    gpr_mu_lock(&c->cache_mu);
+    if (c->cached.service_url != NULL &&
+        !strcmp(c->cached.service_url, service_url) &&
+        c->cached.jwt_md != NULL &&
+        (gpr_time_cmp(gpr_time_sub(c->cached.jwt_expiration, gpr_now()),
+                      refresh_threshold) > 0)) {
+      jwt_md = grpc_mdelem_ref(c->cached.jwt_md);
+    }
+    gpr_mu_unlock(&c->cache_mu);
+  }
+
+  if (jwt_md == NULL) {
+    char *jwt = NULL;
+    /* Generate a new jwt. */
+    gpr_mu_lock(&c->cache_mu);
+    jwt_reset_cache(c);
+    jwt = grpc_jwt_encode_and_sign(&c->key, service_url, c->jwt_lifetime, NULL);
+    if (jwt != NULL) {
+      char *md_value;
+      gpr_asprintf(&md_value, "Bearer %s", jwt);
+      gpr_free(jwt);
+      c->cached.jwt_expiration = gpr_time_add(gpr_now(), c->jwt_lifetime);
+      c->cached.service_url = gpr_strdup(service_url);
+      c->cached.jwt_md = grpc_mdelem_from_strings(
+          c->md_ctx, GRPC_AUTHORIZATION_METADATA_KEY, md_value);
+      gpr_free(md_value);
+      jwt_md = grpc_mdelem_ref(c->cached.jwt_md);
+    }
+    gpr_mu_unlock(&c->cache_mu);
+  }
+
+  if (jwt_md != NULL) {
+    cb(user_data, &jwt_md, 1, GRPC_CREDENTIALS_OK);
+    grpc_mdelem_unref(jwt_md);
+  } else {
+    cb(user_data, NULL, 0, GRPC_CREDENTIALS_ERROR);
+  }
+}
+
+static grpc_credentials_vtable jwt_vtable = {
+    jwt_destroy, jwt_has_request_metadata, jwt_has_request_metadata_only,
+    jwt_get_request_metadata};
+
+grpc_credentials *grpc_jwt_credentials_create(const char *json_key,
+                                              gpr_timespec token_lifetime) {
+  grpc_jwt_credentials *c;
+  grpc_auth_json_key key = grpc_auth_json_key_create_from_string(json_key);
+  if (!grpc_auth_json_key_is_valid(&key)) {
+    gpr_log(GPR_ERROR, "Invalid input for jwt credentials creation");
+    return NULL;
+  }
+  c = gpr_malloc(sizeof(grpc_jwt_credentials));
+  memset(c, 0, sizeof(grpc_jwt_credentials));
+  c->base.type = GRPC_CREDENTIALS_TYPE_JWT;
+  gpr_ref_init(&c->base.refcount, 1);
+  c->base.vtable = &jwt_vtable;
+  c->md_ctx = grpc_mdctx_create();
+  c->key = key;
+  c->jwt_lifetime = token_lifetime;
+  gpr_mu_init(&c->cache_mu);
+  jwt_reset_cache(c);
+  return &c->base;
+}
+
 /* -- Oauth2TokenFetcher credentials -- */
 
 /* This object is a base for credentials that need to acquire an oauth2 token
@@ -439,10 +561,11 @@
 }
 
 static void oauth2_token_fetcher_get_request_metadata(
-    grpc_credentials *creds, grpc_credentials_metadata_cb cb, void *user_data) {
+    grpc_credentials *creds, const char *service_url,
+    grpc_credentials_metadata_cb cb, void *user_data) {
   grpc_oauth2_token_fetcher_credentials *c =
       (grpc_oauth2_token_fetcher_credentials *)creds;
-  gpr_timespec refresh_threshold = {GRPC_OAUTH2_TOKEN_REFRESH_THRESHOLD_SECS,
+  gpr_timespec refresh_threshold = {GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
                                     0};
   grpc_mdelem *cached_access_token_md = NULL;
   {
@@ -535,7 +658,8 @@
                                 "application/x-www-form-urlencoded"};
   grpc_httpcli_request request;
   char *body = NULL;
-  char *jwt = grpc_jwt_encode_and_sign(&c->key, c->scope, c->token_lifetime);
+  char *jwt = grpc_jwt_encode_and_sign(&c->key, GRPC_JWT_OAUTH2_AUDIENCE,
+                                       c->token_lifetime, c->scope);
   if (jwt == NULL) {
     grpc_httpcli_response response;
     memset(&response, 0, sizeof(grpc_httpcli_response));
@@ -616,6 +740,7 @@
 }
 
 static void fake_oauth2_get_request_metadata(grpc_credentials *creds,
+                                             const char *service_url,
                                              grpc_credentials_metadata_cb cb,
                                              void *user_data) {
   grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)creds;
@@ -709,6 +834,7 @@
   size_t creds_index;
   grpc_mdelem **md_elems;
   size_t num_md;
+  char *service_url;
   void *user_data;
   grpc_credentials_metadata_cb cb;
 } grpc_composite_credentials_metadata_context;
@@ -754,6 +880,7 @@
     grpc_mdelem_unref(ctx->md_elems[i]);
   }
   gpr_free(ctx->md_elems);
+  if (ctx->service_url != NULL) gpr_free(ctx->service_url);
   gpr_free(ctx);
 }
 
@@ -783,8 +910,8 @@
     grpc_credentials *inner_creds =
         ctx->composite_creds->inner.creds_array[ctx->creds_index++];
     if (grpc_credentials_has_request_metadata(inner_creds)) {
-      grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb,
-                                            ctx);
+      grpc_credentials_get_request_metadata(inner_creds, ctx->service_url,
+                                            composite_metadata_cb, ctx);
       return;
     }
   }
@@ -795,6 +922,7 @@
 }
 
 static void composite_get_request_metadata(grpc_credentials *creds,
+                                           const char *service_url,
                                            grpc_credentials_metadata_cb cb,
                                            void *user_data) {
   grpc_composite_credentials *c = (grpc_composite_credentials *)creds;
@@ -805,14 +933,15 @@
   }
   ctx = gpr_malloc(sizeof(grpc_composite_credentials_metadata_context));
   memset(ctx, 0, sizeof(grpc_composite_credentials_metadata_context));
+  ctx->service_url = gpr_strdup(service_url);
   ctx->user_data = user_data;
   ctx->cb = cb;
   ctx->composite_creds = c;
   while (ctx->creds_index < c->inner.num_creds) {
     grpc_credentials *inner_creds = c->inner.creds_array[ctx->creds_index++];
     if (grpc_credentials_has_request_metadata(inner_creds)) {
-      grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb,
-                                            ctx);
+      grpc_credentials_get_request_metadata(inner_creds, service_url,
+                                            composite_metadata_cb, ctx);
       return;
     }
   }
@@ -916,6 +1045,7 @@
 }
 
 static void iam_get_request_metadata(grpc_credentials *creds,
+                                     const char *service_url,
                                      grpc_credentials_metadata_cb cb,
                                      void *user_data) {
   grpc_iam_credentials *c = (grpc_iam_credentials *)creds;