blob: 0bb8e0530604af9ad463dd3e8f1c789554178273 [file] [log] [blame]
Julien Boeuffeca1bf2015-06-22 16:46:20 +02001/*
2 *
Craig Tillerde3da742016-01-04 14:41:25 -08003 * Copyright 2015-2016, Google Inc.
Julien Boeuffeca1bf2015-06-22 16:46:20 +02004 * 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
Craig Tiller897e4fe2015-12-22 15:03:40 -080011 * notice, this list of conditions and the following disclaimer.
Julien Boeuffeca1bf2015-06-22 16:46:20 +020012 * * Redistributions in binary form must reproduce the above
Craig Tiller897e4fe2015-12-22 15:03:40 -080013 * copyright notice, this list of conditions and the following disclaimer
Julien Boeuffeca1bf2015-06-22 16:46:20 +020014 * 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
Craig Tillerf96dfc32015-09-10 14:43:18 -070036#include <limits.h>
Julien Boeuffeca1bf2015-06-22 16:46:20 +020037#include <string.h>
38
Matthew Iselin1824f052016-02-10 12:16:06 +110039#include "src/core/http/httpcli.h"
Craig Tiller732a8752016-02-22 15:59:19 -080040#include "src/core/security/b64.h"
Craig Tiller0fe5ee72015-12-22 12:50:36 -080041#include "src/core/tsi/ssl_types.h"
Julien Boeuffeca1bf2015-06-22 16:46:20 +020042
43#include <grpc/support/alloc.h>
44#include <grpc/support/log.h>
45#include <grpc/support/string_util.h>
46#include <grpc/support/sync.h>
47#include <openssl/pem.h>
48
49/* --- Utils. --- */
50
Craig Tillera82950e2015-09-22 12:33:20 -070051const char *grpc_jwt_verifier_status_to_string(
52 grpc_jwt_verifier_status status) {
53 switch (status) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +020054 case GRPC_JWT_VERIFIER_OK:
55 return "OK";
56 case GRPC_JWT_VERIFIER_BAD_SIGNATURE:
57 return "BAD_SIGNATURE";
58 case GRPC_JWT_VERIFIER_BAD_FORMAT:
59 return "BAD_FORMAT";
60 case GRPC_JWT_VERIFIER_BAD_AUDIENCE:
61 return "BAD_AUDIENCE";
62 case GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR:
63 return "KEY_RETRIEVAL_ERROR";
64 case GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE:
65 return "TIME_CONSTRAINT_FAILURE";
66 case GRPC_JWT_VERIFIER_GENERIC_ERROR:
67 return "GENERIC_ERROR";
68 default:
69 return "UNKNOWN";
Craig Tillera82950e2015-09-22 12:33:20 -070070 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +020071}
72
Craig Tillera82950e2015-09-22 12:33:20 -070073static const EVP_MD *evp_md_from_alg(const char *alg) {
74 if (strcmp(alg, "RS256") == 0) {
75 return EVP_sha256();
76 } else if (strcmp(alg, "RS384") == 0) {
77 return EVP_sha384();
78 } else if (strcmp(alg, "RS512") == 0) {
79 return EVP_sha512();
80 } else {
81 return NULL;
82 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +020083}
84
Craig Tillera82950e2015-09-22 12:33:20 -070085static grpc_json *parse_json_part_from_jwt(const char *str, size_t len,
86 gpr_slice *buffer) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +020087 grpc_json *json;
88
Craig Tillera82950e2015-09-22 12:33:20 -070089 *buffer = grpc_base64_decode_with_len(str, len, 1);
90 if (GPR_SLICE_IS_EMPTY(*buffer)) {
91 gpr_log(GPR_ERROR, "Invalid base64.");
92 return NULL;
93 }
94 json = grpc_json_parse_string_with_len((char *)GPR_SLICE_START_PTR(*buffer),
95 GPR_SLICE_LENGTH(*buffer));
96 if (json == NULL) {
97 gpr_slice_unref(*buffer);
98 gpr_log(GPR_ERROR, "JSON parsing error.");
99 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200100 return json;
101}
102
Craig Tillera82950e2015-09-22 12:33:20 -0700103static const char *validate_string_field(const grpc_json *json,
104 const char *key) {
105 if (json->type != GRPC_JSON_STRING) {
106 gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
107 return NULL;
108 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200109 return json->value;
110}
111
Craig Tillera82950e2015-09-22 12:33:20 -0700112static gpr_timespec validate_time_field(const grpc_json *json,
113 const char *key) {
114 gpr_timespec result = gpr_time_0(GPR_CLOCK_REALTIME);
115 if (json->type != GRPC_JSON_NUMBER) {
116 gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
117 return result;
118 }
119 result.tv_sec = strtol(json->value, NULL, 10);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200120 return result;
121}
122
123/* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */
124
Craig Tillera82950e2015-09-22 12:33:20 -0700125typedef struct {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200126 const char *alg;
127 const char *kid;
128 const char *typ;
129 /* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */
130 gpr_slice buffer;
131} jose_header;
132
Craig Tillera82950e2015-09-22 12:33:20 -0700133static void jose_header_destroy(jose_header *h) {
134 gpr_slice_unref(h->buffer);
135 gpr_free(h);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200136}
137
138/* Takes ownership of json and buffer. */
Craig Tillera82950e2015-09-22 12:33:20 -0700139static jose_header *jose_header_from_json(grpc_json *json, gpr_slice buffer) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200140 grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700141 jose_header *h = gpr_malloc(sizeof(jose_header));
142 memset(h, 0, sizeof(jose_header));
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200143 h->buffer = buffer;
Craig Tillera82950e2015-09-22 12:33:20 -0700144 for (cur = json->child; cur != NULL; cur = cur->next) {
145 if (strcmp(cur->key, "alg") == 0) {
146 /* We only support RSA-1.5 signatures for now.
147 Beware of this if we add HMAC support:
148 https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
149 */
150 if (cur->type != GRPC_JSON_STRING || strncmp(cur->value, "RS", 2) ||
151 evp_md_from_alg(cur->value) == NULL) {
152 gpr_log(GPR_ERROR, "Invalid alg field [%s]", cur->value);
153 goto error;
154 }
155 h->alg = cur->value;
156 } else if (strcmp(cur->key, "typ") == 0) {
157 h->typ = validate_string_field(cur, "typ");
158 if (h->typ == NULL) goto error;
159 } else if (strcmp(cur->key, "kid") == 0) {
160 h->kid = validate_string_field(cur, "kid");
161 if (h->kid == NULL) goto error;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200162 }
Craig Tillera82950e2015-09-22 12:33:20 -0700163 }
164 if (h->alg == NULL) {
165 gpr_log(GPR_ERROR, "Missing alg field.");
166 goto error;
167 }
168 grpc_json_destroy(json);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200169 h->buffer = buffer;
170 return h;
171
172error:
Craig Tillera82950e2015-09-22 12:33:20 -0700173 grpc_json_destroy(json);
174 jose_header_destroy(h);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200175 return NULL;
176}
177
178/* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */
179
Craig Tillera82950e2015-09-22 12:33:20 -0700180struct grpc_jwt_claims {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200181 /* Well known properties already parsed. */
182 const char *sub;
183 const char *iss;
184 const char *aud;
185 const char *jti;
186 gpr_timespec iat;
187 gpr_timespec exp;
188 gpr_timespec nbf;
189
190 grpc_json *json;
191 gpr_slice buffer;
192};
193
Craig Tillera82950e2015-09-22 12:33:20 -0700194void grpc_jwt_claims_destroy(grpc_jwt_claims *claims) {
195 grpc_json_destroy(claims->json);
196 gpr_slice_unref(claims->buffer);
197 gpr_free(claims);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200198}
199
Craig Tillera82950e2015-09-22 12:33:20 -0700200const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims) {
201 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200202 return claims->json;
203}
204
Craig Tillera82950e2015-09-22 12:33:20 -0700205const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims) {
206 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200207 return claims->sub;
208}
209
Craig Tillera82950e2015-09-22 12:33:20 -0700210const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims) {
211 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200212 return claims->iss;
213}
214
Craig Tillera82950e2015-09-22 12:33:20 -0700215const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims) {
216 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200217 return claims->jti;
218}
219
Craig Tillera82950e2015-09-22 12:33:20 -0700220const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims) {
221 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200222 return claims->aud;
223}
224
Craig Tillera82950e2015-09-22 12:33:20 -0700225gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims) {
226 if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200227 return claims->iat;
228}
229
Craig Tillera82950e2015-09-22 12:33:20 -0700230gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims) {
231 if (claims == NULL) return gpr_inf_future(GPR_CLOCK_REALTIME);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200232 return claims->exp;
233}
234
Craig Tillera82950e2015-09-22 12:33:20 -0700235gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims) {
236 if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200237 return claims->nbf;
238}
239
240/* Takes ownership of json and buffer even in case of failure. */
Craig Tillera82950e2015-09-22 12:33:20 -0700241grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_json *json, gpr_slice buffer) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200242 grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700243 grpc_jwt_claims *claims = gpr_malloc(sizeof(grpc_jwt_claims));
244 memset(claims, 0, sizeof(grpc_jwt_claims));
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200245 claims->json = json;
246 claims->buffer = buffer;
Craig Tillera82950e2015-09-22 12:33:20 -0700247 claims->iat = gpr_inf_past(GPR_CLOCK_REALTIME);
248 claims->nbf = gpr_inf_past(GPR_CLOCK_REALTIME);
249 claims->exp = gpr_inf_future(GPR_CLOCK_REALTIME);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200250
251 /* Per the spec, all fields are optional. */
Craig Tillera82950e2015-09-22 12:33:20 -0700252 for (cur = json->child; cur != NULL; cur = cur->next) {
253 if (strcmp(cur->key, "sub") == 0) {
254 claims->sub = validate_string_field(cur, "sub");
255 if (claims->sub == NULL) goto error;
256 } else if (strcmp(cur->key, "iss") == 0) {
257 claims->iss = validate_string_field(cur, "iss");
258 if (claims->iss == NULL) goto error;
259 } else if (strcmp(cur->key, "aud") == 0) {
260 claims->aud = validate_string_field(cur, "aud");
261 if (claims->aud == NULL) goto error;
262 } else if (strcmp(cur->key, "jti") == 0) {
263 claims->jti = validate_string_field(cur, "jti");
264 if (claims->jti == NULL) goto error;
265 } else if (strcmp(cur->key, "iat") == 0) {
266 claims->iat = validate_time_field(cur, "iat");
267 if (gpr_time_cmp(claims->iat, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
268 goto error;
269 } else if (strcmp(cur->key, "exp") == 0) {
270 claims->exp = validate_time_field(cur, "exp");
271 if (gpr_time_cmp(claims->exp, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
272 goto error;
273 } else if (strcmp(cur->key, "nbf") == 0) {
274 claims->nbf = validate_time_field(cur, "nbf");
275 if (gpr_time_cmp(claims->nbf, gpr_time_0(GPR_CLOCK_REALTIME)) == 0)
276 goto error;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200277 }
Craig Tillera82950e2015-09-22 12:33:20 -0700278 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200279 return claims;
280
281error:
Craig Tillera82950e2015-09-22 12:33:20 -0700282 grpc_jwt_claims_destroy(claims);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200283 return NULL;
284}
285
Craig Tillera82950e2015-09-22 12:33:20 -0700286grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims,
287 const char *audience) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200288 gpr_timespec skewed_now;
289 int audience_ok;
290
Craig Tillera82950e2015-09-22 12:33:20 -0700291 GPR_ASSERT(claims != NULL);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200292
Craig Tillera82950e2015-09-22 12:33:20 -0700293 skewed_now =
294 gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
295 if (gpr_time_cmp(skewed_now, claims->nbf) < 0) {
296 gpr_log(GPR_ERROR, "JWT is not valid yet.");
297 return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
298 }
299 skewed_now =
300 gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew);
301 if (gpr_time_cmp(skewed_now, claims->exp) > 0) {
302 gpr_log(GPR_ERROR, "JWT is expired.");
303 return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
304 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200305
Craig Tillera82950e2015-09-22 12:33:20 -0700306 if (audience == NULL) {
307 audience_ok = claims->aud == NULL;
308 } else {
309 audience_ok = claims->aud != NULL && strcmp(audience, claims->aud) == 0;
310 }
311 if (!audience_ok) {
312 gpr_log(GPR_ERROR, "Audience mismatch: expected %s and found %s.",
313 audience == NULL ? "NULL" : audience,
314 claims->aud == NULL ? "NULL" : claims->aud);
315 return GRPC_JWT_VERIFIER_BAD_AUDIENCE;
316 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200317 return GRPC_JWT_VERIFIER_OK;
318}
319
320/* --- verifier_cb_ctx object. --- */
321
Craig Tillera82950e2015-09-22 12:33:20 -0700322typedef struct {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200323 grpc_jwt_verifier *verifier;
324 grpc_pollset *pollset;
325 jose_header *header;
326 grpc_jwt_claims *claims;
327 char *audience;
328 gpr_slice signature;
329 gpr_slice signed_data;
330 void *user_data;
331 grpc_jwt_verification_done_cb user_cb;
332} verifier_cb_ctx;
333
334/* Takes ownership of the header, claims and signature. */
Craig Tillera82950e2015-09-22 12:33:20 -0700335static verifier_cb_ctx *verifier_cb_ctx_create(
336 grpc_jwt_verifier *verifier, grpc_pollset *pollset, jose_header *header,
337 grpc_jwt_claims *claims, const char *audience, gpr_slice signature,
338 const char *signed_jwt, size_t signed_jwt_len, void *user_data,
339 grpc_jwt_verification_done_cb cb) {
340 verifier_cb_ctx *ctx = gpr_malloc(sizeof(verifier_cb_ctx));
341 memset(ctx, 0, sizeof(verifier_cb_ctx));
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200342 ctx->verifier = verifier;
343 ctx->pollset = pollset;
344 ctx->header = header;
Craig Tillera82950e2015-09-22 12:33:20 -0700345 ctx->audience = gpr_strdup(audience);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200346 ctx->claims = claims;
347 ctx->signature = signature;
Craig Tillera82950e2015-09-22 12:33:20 -0700348 ctx->signed_data = gpr_slice_from_copied_buffer(signed_jwt, signed_jwt_len);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200349 ctx->user_data = user_data;
350 ctx->user_cb = cb;
351 return ctx;
352}
353
Craig Tillera82950e2015-09-22 12:33:20 -0700354void verifier_cb_ctx_destroy(verifier_cb_ctx *ctx) {
355 if (ctx->audience != NULL) gpr_free(ctx->audience);
356 if (ctx->claims != NULL) grpc_jwt_claims_destroy(ctx->claims);
357 gpr_slice_unref(ctx->signature);
358 gpr_slice_unref(ctx->signed_data);
359 jose_header_destroy(ctx->header);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200360 /* TODO: see what to do with claims... */
Craig Tillera82950e2015-09-22 12:33:20 -0700361 gpr_free(ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200362}
363
364/* --- grpc_jwt_verifier object. --- */
365
366/* Clock skew defaults to one minute. */
Craig Tillera82950e2015-09-22 12:33:20 -0700367gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN};
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200368
369/* Max delay defaults to one minute. */
Craig Tillera82950e2015-09-22 12:33:20 -0700370gpr_timespec grpc_jwt_verifier_max_delay = {60, 0, GPR_TIMESPAN};
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200371
Craig Tillera82950e2015-09-22 12:33:20 -0700372typedef struct {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200373 char *email_domain;
374 char *key_url_prefix;
375} email_key_mapping;
376
Craig Tillera82950e2015-09-22 12:33:20 -0700377struct grpc_jwt_verifier {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200378 email_key_mapping *mappings;
Craig Tillera82950e2015-09-22 12:33:20 -0700379 size_t num_mappings; /* Should be very few, linear search ok. */
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200380 size_t allocated_mappings;
381 grpc_httpcli_context http_ctx;
382};
383
Craig Tillera82950e2015-09-22 12:33:20 -0700384static grpc_json *json_from_http(const grpc_httpcli_response *response) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200385 grpc_json *json = NULL;
386
Craig Tillera82950e2015-09-22 12:33:20 -0700387 if (response == NULL) {
388 gpr_log(GPR_ERROR, "HTTP response is NULL.");
389 return NULL;
390 }
391 if (response->status != 200) {
392 gpr_log(GPR_ERROR, "Call to http server failed with error %d.",
393 response->status);
394 return NULL;
395 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200396
Craig Tillera82950e2015-09-22 12:33:20 -0700397 json = grpc_json_parse_string_with_len(response->body, response->body_length);
398 if (json == NULL) {
399 gpr_log(GPR_ERROR, "Invalid JSON found in response.");
400 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200401 return json;
402}
403
Craig Tillera82950e2015-09-22 12:33:20 -0700404static const grpc_json *find_property_by_name(const grpc_json *json,
405 const char *name) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200406 const grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700407 for (cur = json->child; cur != NULL; cur = cur->next) {
408 if (strcmp(cur->key, name) == 0) return cur;
409 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200410 return NULL;
411}
412
Craig Tillera82950e2015-09-22 12:33:20 -0700413static EVP_PKEY *extract_pkey_from_x509(const char *x509_str) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200414 X509 *x509 = NULL;
415 EVP_PKEY *result = NULL;
Craig Tillera82950e2015-09-22 12:33:20 -0700416 BIO *bio = BIO_new(BIO_s_mem());
417 size_t len = strlen(x509_str);
418 GPR_ASSERT(len < INT_MAX);
419 BIO_write(bio, x509_str, (int)len);
420 x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
421 if (x509 == NULL) {
422 gpr_log(GPR_ERROR, "Unable to parse x509 cert.");
423 goto end;
424 }
425 result = X509_get_pubkey(x509);
426 if (result == NULL) {
427 gpr_log(GPR_ERROR, "Cannot find public key in X509 cert.");
428 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200429
430end:
Craig Tillera82950e2015-09-22 12:33:20 -0700431 BIO_free(bio);
432 if (x509 != NULL) X509_free(x509);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200433 return result;
434}
435
Craig Tillera82950e2015-09-22 12:33:20 -0700436static BIGNUM *bignum_from_base64(const char *b64) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200437 BIGNUM *result = NULL;
438 gpr_slice bin;
439
Craig Tillera82950e2015-09-22 12:33:20 -0700440 if (b64 == NULL) return NULL;
441 bin = grpc_base64_decode(b64, 1);
442 if (GPR_SLICE_IS_EMPTY(bin)) {
443 gpr_log(GPR_ERROR, "Invalid base64 for big num.");
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200444 return NULL;
Craig Tillera82950e2015-09-22 12:33:20 -0700445 }
Craig Tiller7536af02015-12-22 13:49:30 -0800446 result = BN_bin2bn(GPR_SLICE_START_PTR(bin),
447 TSI_SIZE_AS_SIZE(GPR_SLICE_LENGTH(bin)), NULL);
Craig Tillera82950e2015-09-22 12:33:20 -0700448 gpr_slice_unref(bin);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200449 return result;
450}
451
Craig Tillera82950e2015-09-22 12:33:20 -0700452static EVP_PKEY *pkey_from_jwk(const grpc_json *json, const char *kty) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200453 const grpc_json *key_prop;
454 RSA *rsa = NULL;
455 EVP_PKEY *result = NULL;
456
Craig Tillera82950e2015-09-22 12:33:20 -0700457 GPR_ASSERT(kty != NULL && json != NULL);
458 if (strcmp(kty, "RSA") != 0) {
459 gpr_log(GPR_ERROR, "Unsupported key type %s.", kty);
460 goto end;
461 }
462 rsa = RSA_new();
463 if (rsa == NULL) {
464 gpr_log(GPR_ERROR, "Could not create rsa key.");
465 goto end;
466 }
467 for (key_prop = json->child; key_prop != NULL; key_prop = key_prop->next) {
468 if (strcmp(key_prop->key, "n") == 0) {
469 rsa->n = bignum_from_base64(validate_string_field(key_prop, "n"));
470 if (rsa->n == NULL) goto end;
471 } else if (strcmp(key_prop->key, "e") == 0) {
472 rsa->e = bignum_from_base64(validate_string_field(key_prop, "e"));
473 if (rsa->e == NULL) goto end;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200474 }
Craig Tillera82950e2015-09-22 12:33:20 -0700475 }
476 if (rsa->e == NULL || rsa->n == NULL) {
477 gpr_log(GPR_ERROR, "Missing RSA public key field.");
478 goto end;
479 }
480 result = EVP_PKEY_new();
481 EVP_PKEY_set1_RSA(result, rsa); /* uprefs rsa. */
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200482
483end:
Craig Tillera82950e2015-09-22 12:33:20 -0700484 if (rsa != NULL) RSA_free(rsa);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200485 return result;
486}
487
Craig Tillera82950e2015-09-22 12:33:20 -0700488static EVP_PKEY *find_verification_key(const grpc_json *json,
489 const char *header_alg,
490 const char *header_kid) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200491 const grpc_json *jkey;
492 const grpc_json *jwk_keys;
493 /* Try to parse the json as a JWK set:
494 https://tools.ietf.org/html/rfc7517#section-5. */
Craig Tillera82950e2015-09-22 12:33:20 -0700495 jwk_keys = find_property_by_name(json, "keys");
496 if (jwk_keys == NULL) {
497 /* Use the google proprietary format which is:
498 { <kid1>: <x5091>, <kid2>: <x5092>, ... } */
499 const grpc_json *cur = find_property_by_name(json, header_kid);
500 if (cur == NULL) return NULL;
501 return extract_pkey_from_x509(cur->value);
502 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200503
Craig Tillera82950e2015-09-22 12:33:20 -0700504 if (jwk_keys->type != GRPC_JSON_ARRAY) {
505 gpr_log(GPR_ERROR,
506 "Unexpected value type of keys property in jwks key set.");
507 return NULL;
508 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200509 /* Key format is specified in:
510 https://tools.ietf.org/html/rfc7518#section-6. */
Craig Tillera82950e2015-09-22 12:33:20 -0700511 for (jkey = jwk_keys->child; jkey != NULL; jkey = jkey->next) {
512 grpc_json *key_prop;
513 const char *alg = NULL;
514 const char *kid = NULL;
515 const char *kty = NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200516
Craig Tillera82950e2015-09-22 12:33:20 -0700517 if (jkey->type != GRPC_JSON_OBJECT) continue;
518 for (key_prop = jkey->child; key_prop != NULL; key_prop = key_prop->next) {
519 if (strcmp(key_prop->key, "alg") == 0 &&
520 key_prop->type == GRPC_JSON_STRING) {
521 alg = key_prop->value;
522 } else if (strcmp(key_prop->key, "kid") == 0 &&
523 key_prop->type == GRPC_JSON_STRING) {
524 kid = key_prop->value;
525 } else if (strcmp(key_prop->key, "kty") == 0 &&
526 key_prop->type == GRPC_JSON_STRING) {
527 kty = key_prop->value;
528 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200529 }
Craig Tillera82950e2015-09-22 12:33:20 -0700530 if (alg != NULL && kid != NULL && kty != NULL &&
531 strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) {
532 return pkey_from_jwk(jkey, kty);
533 }
534 }
535 gpr_log(GPR_ERROR,
536 "Could not find matching key in key set for kid=%s and alg=%s",
537 header_kid, header_alg);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200538 return NULL;
539}
540
Craig Tillera82950e2015-09-22 12:33:20 -0700541static int verify_jwt_signature(EVP_PKEY *key, const char *alg,
542 gpr_slice signature, gpr_slice signed_data) {
543 EVP_MD_CTX *md_ctx = EVP_MD_CTX_create();
544 const EVP_MD *md = evp_md_from_alg(alg);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200545 int result = 0;
546
Craig Tillera82950e2015-09-22 12:33:20 -0700547 GPR_ASSERT(md != NULL); /* Checked before. */
548 if (md_ctx == NULL) {
549 gpr_log(GPR_ERROR, "Could not create EVP_MD_CTX.");
550 goto end;
551 }
552 if (EVP_DigestVerifyInit(md_ctx, NULL, md, NULL, key) != 1) {
553 gpr_log(GPR_ERROR, "EVP_DigestVerifyInit failed.");
554 goto end;
555 }
556 if (EVP_DigestVerifyUpdate(md_ctx, GPR_SLICE_START_PTR(signed_data),
557 GPR_SLICE_LENGTH(signed_data)) != 1) {
558 gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed.");
559 goto end;
560 }
561 if (EVP_DigestVerifyFinal(md_ctx, GPR_SLICE_START_PTR(signature),
562 GPR_SLICE_LENGTH(signature)) != 1) {
563 gpr_log(GPR_ERROR, "JWT signature verification failed.");
564 goto end;
565 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200566 result = 1;
567
568end:
Craig Tillera82950e2015-09-22 12:33:20 -0700569 if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200570 return result;
571}
572
Craig Tillera82950e2015-09-22 12:33:20 -0700573static void on_keys_retrieved(grpc_exec_ctx *exec_ctx, void *user_data,
574 const grpc_httpcli_response *response) {
575 grpc_json *json = json_from_http(response);
576 verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200577 EVP_PKEY *verification_key = NULL;
578 grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR;
579 grpc_jwt_claims *claims = NULL;
580
Craig Tillera82950e2015-09-22 12:33:20 -0700581 if (json == NULL) {
582 status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
583 goto end;
584 }
585 verification_key =
586 find_verification_key(json, ctx->header->alg, ctx->header->kid);
587 if (verification_key == NULL) {
588 gpr_log(GPR_ERROR, "Could not find verification key with kid %s.",
589 ctx->header->kid);
590 status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
591 goto end;
592 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200593
Craig Tillera82950e2015-09-22 12:33:20 -0700594 if (!verify_jwt_signature(verification_key, ctx->header->alg, ctx->signature,
595 ctx->signed_data)) {
596 status = GRPC_JWT_VERIFIER_BAD_SIGNATURE;
597 goto end;
598 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200599
Craig Tillera82950e2015-09-22 12:33:20 -0700600 status = grpc_jwt_claims_check(ctx->claims, ctx->audience);
601 if (status == GRPC_JWT_VERIFIER_OK) {
602 /* Pass ownership. */
603 claims = ctx->claims;
604 ctx->claims = NULL;
605 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200606
607end:
Craig Tillera82950e2015-09-22 12:33:20 -0700608 if (json != NULL) grpc_json_destroy(json);
609 if (verification_key != NULL) EVP_PKEY_free(verification_key);
610 ctx->user_cb(ctx->user_data, status, claims);
611 verifier_cb_ctx_destroy(ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200612}
613
Craig Tillera82950e2015-09-22 12:33:20 -0700614static void on_openid_config_retrieved(grpc_exec_ctx *exec_ctx, void *user_data,
615 const grpc_httpcli_response *response) {
Craig Tiller5cc175f2015-07-09 08:48:35 -0700616 const grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700617 grpc_json *json = json_from_http(response);
618 verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200619 grpc_httpcli_request req;
620 const char *jwks_uri;
621
Craig Tiller45724b32015-09-22 10:42:19 -0700622 /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */
Craig Tillera82950e2015-09-22 12:33:20 -0700623 if (json == NULL) goto error;
624 cur = find_property_by_name(json, "jwks_uri");
625 if (cur == NULL) {
626 gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config.");
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200627 goto error;
Craig Tillera82950e2015-09-22 12:33:20 -0700628 }
629 jwks_uri = validate_string_field(cur, "jwks_uri");
630 if (jwks_uri == NULL) goto error;
631 if (strstr(jwks_uri, "https://") != jwks_uri) {
632 gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200633 goto error;
Craig Tillera82950e2015-09-22 12:33:20 -0700634 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200635 jwks_uri += 8;
Craig Tillerf53d9c82015-08-04 14:19:43 -0700636 req.handshaker = &grpc_httpcli_ssl;
Craig Tillera82950e2015-09-22 12:33:20 -0700637 req.host = gpr_strdup(jwks_uri);
Matthew Iselin1824f052016-02-10 12:16:06 +1100638 req.http.path = strchr(jwks_uri, '/');
639 if (req.http.path == NULL) {
640 req.http.path = "";
Craig Tillera82950e2015-09-22 12:33:20 -0700641 } else {
Matthew Iselin1824f052016-02-10 12:16:06 +1100642 *(req.host + (req.http.path - jwks_uri)) = '\0';
Craig Tillera82950e2015-09-22 12:33:20 -0700643 }
644 grpc_httpcli_get(
645 exec_ctx, &ctx->verifier->http_ctx, ctx->pollset, &req,
646 gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay),
647 on_keys_retrieved, ctx);
648 grpc_json_destroy(json);
649 gpr_free(req.host);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200650 return;
651
652error:
Craig Tillera82950e2015-09-22 12:33:20 -0700653 if (json != NULL) grpc_json_destroy(json);
654 ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL);
655 verifier_cb_ctx_destroy(ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200656}
657
Craig Tillera82950e2015-09-22 12:33:20 -0700658static email_key_mapping *verifier_get_mapping(grpc_jwt_verifier *v,
659 const char *email_domain) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200660 size_t i;
Craig Tillera82950e2015-09-22 12:33:20 -0700661 if (v->mappings == NULL) return NULL;
662 for (i = 0; i < v->num_mappings; i++) {
663 if (strcmp(email_domain, v->mappings[i].email_domain) == 0) {
664 return &v->mappings[i];
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200665 }
Craig Tillera82950e2015-09-22 12:33:20 -0700666 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200667 return NULL;
668}
669
Craig Tillera82950e2015-09-22 12:33:20 -0700670static void verifier_put_mapping(grpc_jwt_verifier *v, const char *email_domain,
671 const char *key_url_prefix) {
672 email_key_mapping *mapping = verifier_get_mapping(v, email_domain);
673 GPR_ASSERT(v->num_mappings < v->allocated_mappings);
674 if (mapping != NULL) {
675 gpr_free(mapping->key_url_prefix);
676 mapping->key_url_prefix = gpr_strdup(key_url_prefix);
677 return;
678 }
679 v->mappings[v->num_mappings].email_domain = gpr_strdup(email_domain);
680 v->mappings[v->num_mappings].key_url_prefix = gpr_strdup(key_url_prefix);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200681 v->num_mappings++;
Craig Tillera82950e2015-09-22 12:33:20 -0700682 GPR_ASSERT(v->num_mappings <= v->allocated_mappings);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200683}
684
685/* Takes ownership of ctx. */
Craig Tillera82950e2015-09-22 12:33:20 -0700686static void retrieve_key_and_verify(grpc_exec_ctx *exec_ctx,
687 verifier_cb_ctx *ctx) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200688 const char *at_sign;
689 grpc_httpcli_response_cb http_cb;
690 char *path_prefix = NULL;
691 const char *iss;
692 grpc_httpcli_request req;
Craig Tillera82950e2015-09-22 12:33:20 -0700693 memset(&req, 0, sizeof(grpc_httpcli_request));
Craig Tillerf53d9c82015-08-04 14:19:43 -0700694 req.handshaker = &grpc_httpcli_ssl;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200695
Craig Tillera82950e2015-09-22 12:33:20 -0700696 GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200697 iss = ctx->claims->iss;
Craig Tillera82950e2015-09-22 12:33:20 -0700698 if (ctx->header->kid == NULL) {
699 gpr_log(GPR_ERROR, "Missing kid in jose header.");
700 goto error;
701 }
702 if (iss == NULL) {
703 gpr_log(GPR_ERROR, "Missing iss in claims.");
704 goto error;
705 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200706
707 /* This code relies on:
708 https://openid.net/specs/openid-connect-discovery-1_0.html
709 Nobody seems to implement the account/email/webfinger part 2. of the spec
710 so we will rely instead on email/url mappings if we detect such an issuer.
711 Part 4, on the other hand is implemented by both google and salesforce. */
712
713 /* Very non-sophisticated way to detect an email address. Should be good
714 enough for now... */
Craig Tillera82950e2015-09-22 12:33:20 -0700715 at_sign = strchr(iss, '@');
716 if (at_sign != NULL) {
717 email_key_mapping *mapping;
718 const char *email_domain = at_sign + 1;
719 GPR_ASSERT(ctx->verifier != NULL);
720 mapping = verifier_get_mapping(ctx->verifier, email_domain);
721 if (mapping == NULL) {
722 gpr_log(GPR_ERROR, "Missing mapping for issuer email.");
723 goto error;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200724 }
Craig Tillera82950e2015-09-22 12:33:20 -0700725 req.host = gpr_strdup(mapping->key_url_prefix);
726 path_prefix = strchr(req.host, '/');
727 if (path_prefix == NULL) {
Matthew Iselin1824f052016-02-10 12:16:06 +1100728 gpr_asprintf(&req.http.path, "/%s", iss);
Craig Tillera82950e2015-09-22 12:33:20 -0700729 } else {
730 *(path_prefix++) = '\0';
Matthew Iselin1824f052016-02-10 12:16:06 +1100731 gpr_asprintf(&req.http.path, "/%s/%s", path_prefix, iss);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200732 }
Craig Tillera82950e2015-09-22 12:33:20 -0700733 http_cb = on_keys_retrieved;
734 } else {
735 req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss);
736 path_prefix = strchr(req.host, '/');
737 if (path_prefix == NULL) {
Matthew Iselin1824f052016-02-10 12:16:06 +1100738 req.http.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX);
Craig Tillera82950e2015-09-22 12:33:20 -0700739 } else {
740 *(path_prefix++) = 0;
Matthew Iselin1824f052016-02-10 12:16:06 +1100741 gpr_asprintf(&req.http.path, "/%s%s", path_prefix,
Craig Tillera82950e2015-09-22 12:33:20 -0700742 GRPC_OPENID_CONFIG_URL_SUFFIX);
743 }
744 http_cb = on_openid_config_retrieved;
745 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200746
Craig Tillera82950e2015-09-22 12:33:20 -0700747 grpc_httpcli_get(
748 exec_ctx, &ctx->verifier->http_ctx, ctx->pollset, &req,
749 gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay),
750 http_cb, ctx);
751 gpr_free(req.host);
Matthew Iselin1824f052016-02-10 12:16:06 +1100752 gpr_free(req.http.path);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200753 return;
754
755error:
Craig Tillera82950e2015-09-22 12:33:20 -0700756 ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL);
757 verifier_cb_ctx_destroy(ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200758}
759
Craig Tillera82950e2015-09-22 12:33:20 -0700760void grpc_jwt_verifier_verify(grpc_exec_ctx *exec_ctx,
761 grpc_jwt_verifier *verifier,
762 grpc_pollset *pollset, const char *jwt,
763 const char *audience,
764 grpc_jwt_verification_done_cb cb,
765 void *user_data) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200766 const char *dot = NULL;
767 grpc_json *json;
768 jose_header *header = NULL;
769 grpc_jwt_claims *claims = NULL;
770 gpr_slice header_buffer;
771 gpr_slice claims_buffer;
772 gpr_slice signature;
773 size_t signed_jwt_len;
774 const char *cur = jwt;
775
Craig Tillera82950e2015-09-22 12:33:20 -0700776 GPR_ASSERT(verifier != NULL && jwt != NULL && audience != NULL && cb != NULL);
777 dot = strchr(cur, '.');
778 if (dot == NULL) goto error;
779 json = parse_json_part_from_jwt(cur, (size_t)(dot - cur), &header_buffer);
780 if (json == NULL) goto error;
781 header = jose_header_from_json(json, header_buffer);
782 if (header == NULL) goto error;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200783
784 cur = dot + 1;
Craig Tillera82950e2015-09-22 12:33:20 -0700785 dot = strchr(cur, '.');
786 if (dot == NULL) goto error;
787 json = parse_json_part_from_jwt(cur, (size_t)(dot - cur), &claims_buffer);
788 if (json == NULL) goto error;
789 claims = grpc_jwt_claims_from_json(json, claims_buffer);
790 if (claims == NULL) goto error;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200791
Craig Tillera82950e2015-09-22 12:33:20 -0700792 signed_jwt_len = (size_t)(dot - jwt);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200793 cur = dot + 1;
Craig Tillera82950e2015-09-22 12:33:20 -0700794 signature = grpc_base64_decode(cur, 1);
795 if (GPR_SLICE_IS_EMPTY(signature)) goto error;
796 retrieve_key_and_verify(
797 exec_ctx,
798 verifier_cb_ctx_create(verifier, pollset, header, claims, audience,
799 signature, jwt, signed_jwt_len, user_data, cb));
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200800 return;
801
802error:
Craig Tillera82950e2015-09-22 12:33:20 -0700803 if (header != NULL) jose_header_destroy(header);
804 if (claims != NULL) grpc_jwt_claims_destroy(claims);
805 cb(user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, NULL);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200806}
807
Craig Tillera82950e2015-09-22 12:33:20 -0700808grpc_jwt_verifier *grpc_jwt_verifier_create(
809 const grpc_jwt_verifier_email_domain_key_url_mapping *mappings,
810 size_t num_mappings) {
811 grpc_jwt_verifier *v = gpr_malloc(sizeof(grpc_jwt_verifier));
812 memset(v, 0, sizeof(grpc_jwt_verifier));
813 grpc_httpcli_context_init(&v->http_ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200814
815 /* We know at least of one mapping. */
816 v->allocated_mappings = 1 + num_mappings;
Craig Tillera82950e2015-09-22 12:33:20 -0700817 v->mappings = gpr_malloc(v->allocated_mappings * sizeof(email_key_mapping));
818 verifier_put_mapping(v, GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN,
819 GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200820 /* User-Provided mappings. */
Craig Tillera82950e2015-09-22 12:33:20 -0700821 if (mappings != NULL) {
822 size_t i;
823 for (i = 0; i < num_mappings; i++) {
824 verifier_put_mapping(v, mappings[i].email_domain,
825 mappings[i].key_url_prefix);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200826 }
Craig Tillera82950e2015-09-22 12:33:20 -0700827 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200828 return v;
829}
830
Craig Tillera82950e2015-09-22 12:33:20 -0700831void grpc_jwt_verifier_destroy(grpc_jwt_verifier *v) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200832 size_t i;
Craig Tillera82950e2015-09-22 12:33:20 -0700833 if (v == NULL) return;
834 grpc_httpcli_context_destroy(&v->http_ctx);
835 if (v->mappings != NULL) {
836 for (i = 0; i < v->num_mappings; i++) {
837 gpr_free(v->mappings[i].email_domain);
838 gpr_free(v->mappings[i].key_url_prefix);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200839 }
Craig Tillera82950e2015-09-22 12:33:20 -0700840 gpr_free(v->mappings);
841 }
842 gpr_free(v);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200843}