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 : }
|