OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * |
| 3 * Copyright 2015-2016, 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 disclaimer. |
| 12 * * Redistributions in binary form must reproduce the above |
| 13 * copyright notice, this list of conditions and the following disclaimer |
| 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/b64.h" |
| 41 #include "src/core/tsi/ssl_types.h" |
| 42 |
| 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 |
| 51 const char *grpc_jwt_verifier_status_to_string( |
| 52 grpc_jwt_verifier_status status) { |
| 53 switch (status) { |
| 54 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"; |
| 70 } |
| 71 } |
| 72 |
| 73 static 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 } |
| 83 } |
| 84 |
| 85 static grpc_json *parse_json_part_from_jwt(const char *str, size_t len, |
| 86 gpr_slice *buffer) { |
| 87 grpc_json *json; |
| 88 |
| 89 *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 } |
| 100 return json; |
| 101 } |
| 102 |
| 103 static 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 } |
| 109 return json->value; |
| 110 } |
| 111 |
| 112 static 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); |
| 120 return result; |
| 121 } |
| 122 |
| 123 /* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */ |
| 124 |
| 125 typedef struct { |
| 126 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 |
| 133 static void jose_header_destroy(jose_header *h) { |
| 134 gpr_slice_unref(h->buffer); |
| 135 gpr_free(h); |
| 136 } |
| 137 |
| 138 /* Takes ownership of json and buffer. */ |
| 139 static jose_header *jose_header_from_json(grpc_json *json, gpr_slice buffer) { |
| 140 grpc_json *cur; |
| 141 jose_header *h = gpr_malloc(sizeof(jose_header)); |
| 142 memset(h, 0, sizeof(jose_header)); |
| 143 h->buffer = buffer; |
| 144 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; |
| 162 } |
| 163 } |
| 164 if (h->alg == NULL) { |
| 165 gpr_log(GPR_ERROR, "Missing alg field."); |
| 166 goto error; |
| 167 } |
| 168 grpc_json_destroy(json); |
| 169 h->buffer = buffer; |
| 170 return h; |
| 171 |
| 172 error: |
| 173 grpc_json_destroy(json); |
| 174 jose_header_destroy(h); |
| 175 return NULL; |
| 176 } |
| 177 |
| 178 /* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */ |
| 179 |
| 180 struct grpc_jwt_claims { |
| 181 /* 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 |
| 194 void grpc_jwt_claims_destroy(grpc_jwt_claims *claims) { |
| 195 grpc_json_destroy(claims->json); |
| 196 gpr_slice_unref(claims->buffer); |
| 197 gpr_free(claims); |
| 198 } |
| 199 |
| 200 const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims) { |
| 201 if (claims == NULL) return NULL; |
| 202 return claims->json; |
| 203 } |
| 204 |
| 205 const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims) { |
| 206 if (claims == NULL) return NULL; |
| 207 return claims->sub; |
| 208 } |
| 209 |
| 210 const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims) { |
| 211 if (claims == NULL) return NULL; |
| 212 return claims->iss; |
| 213 } |
| 214 |
| 215 const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims) { |
| 216 if (claims == NULL) return NULL; |
| 217 return claims->jti; |
| 218 } |
| 219 |
| 220 const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims) { |
| 221 if (claims == NULL) return NULL; |
| 222 return claims->aud; |
| 223 } |
| 224 |
| 225 gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims) { |
| 226 if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME); |
| 227 return claims->iat; |
| 228 } |
| 229 |
| 230 gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims) { |
| 231 if (claims == NULL) return gpr_inf_future(GPR_CLOCK_REALTIME); |
| 232 return claims->exp; |
| 233 } |
| 234 |
| 235 gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims) { |
| 236 if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME); |
| 237 return claims->nbf; |
| 238 } |
| 239 |
| 240 /* Takes ownership of json and buffer even in case of failure. */ |
| 241 grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_json *json, gpr_slice buffer) { |
| 242 grpc_json *cur; |
| 243 grpc_jwt_claims *claims = gpr_malloc(sizeof(grpc_jwt_claims)); |
| 244 memset(claims, 0, sizeof(grpc_jwt_claims)); |
| 245 claims->json = json; |
| 246 claims->buffer = buffer; |
| 247 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); |
| 250 |
| 251 /* Per the spec, all fields are optional. */ |
| 252 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; |
| 277 } |
| 278 } |
| 279 return claims; |
| 280 |
| 281 error: |
| 282 grpc_jwt_claims_destroy(claims); |
| 283 return NULL; |
| 284 } |
| 285 |
| 286 grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims, |
| 287 const char *audience) { |
| 288 gpr_timespec skewed_now; |
| 289 int audience_ok; |
| 290 |
| 291 GPR_ASSERT(claims != NULL); |
| 292 |
| 293 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 } |
| 305 |
| 306 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 } |
| 317 return GRPC_JWT_VERIFIER_OK; |
| 318 } |
| 319 |
| 320 /* --- verifier_cb_ctx object. --- */ |
| 321 |
| 322 typedef struct { |
| 323 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. */ |
| 335 static 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)); |
| 342 ctx->verifier = verifier; |
| 343 ctx->pollset = pollset; |
| 344 ctx->header = header; |
| 345 ctx->audience = gpr_strdup(audience); |
| 346 ctx->claims = claims; |
| 347 ctx->signature = signature; |
| 348 ctx->signed_data = gpr_slice_from_copied_buffer(signed_jwt, signed_jwt_len); |
| 349 ctx->user_data = user_data; |
| 350 ctx->user_cb = cb; |
| 351 return ctx; |
| 352 } |
| 353 |
| 354 void 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); |
| 360 /* TODO: see what to do with claims... */ |
| 361 gpr_free(ctx); |
| 362 } |
| 363 |
| 364 /* --- grpc_jwt_verifier object. --- */ |
| 365 |
| 366 /* Clock skew defaults to one minute. */ |
| 367 gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN}; |
| 368 |
| 369 /* Max delay defaults to one minute. */ |
| 370 gpr_timespec grpc_jwt_verifier_max_delay = {60, 0, GPR_TIMESPAN}; |
| 371 |
| 372 typedef struct { |
| 373 char *email_domain; |
| 374 char *key_url_prefix; |
| 375 } email_key_mapping; |
| 376 |
| 377 struct grpc_jwt_verifier { |
| 378 email_key_mapping *mappings; |
| 379 size_t num_mappings; /* Should be very few, linear search ok. */ |
| 380 size_t allocated_mappings; |
| 381 grpc_httpcli_context http_ctx; |
| 382 }; |
| 383 |
| 384 static grpc_json *json_from_http(const grpc_httpcli_response *response) { |
| 385 grpc_json *json = NULL; |
| 386 |
| 387 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 } |
| 396 |
| 397 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 } |
| 401 return json; |
| 402 } |
| 403 |
| 404 static const grpc_json *find_property_by_name(const grpc_json *json, |
| 405 const char *name) { |
| 406 const grpc_json *cur; |
| 407 for (cur = json->child; cur != NULL; cur = cur->next) { |
| 408 if (strcmp(cur->key, name) == 0) return cur; |
| 409 } |
| 410 return NULL; |
| 411 } |
| 412 |
| 413 static EVP_PKEY *extract_pkey_from_x509(const char *x509_str) { |
| 414 X509 *x509 = NULL; |
| 415 EVP_PKEY *result = NULL; |
| 416 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 } |
| 429 |
| 430 end: |
| 431 BIO_free(bio); |
| 432 if (x509 != NULL) X509_free(x509); |
| 433 return result; |
| 434 } |
| 435 |
| 436 static BIGNUM *bignum_from_base64(const char *b64) { |
| 437 BIGNUM *result = NULL; |
| 438 gpr_slice bin; |
| 439 |
| 440 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."); |
| 444 return NULL; |
| 445 } |
| 446 result = BN_bin2bn(GPR_SLICE_START_PTR(bin), |
| 447 TSI_SIZE_AS_SIZE(GPR_SLICE_LENGTH(bin)), NULL); |
| 448 gpr_slice_unref(bin); |
| 449 return result; |
| 450 } |
| 451 |
| 452 static EVP_PKEY *pkey_from_jwk(const grpc_json *json, const char *kty) { |
| 453 const grpc_json *key_prop; |
| 454 RSA *rsa = NULL; |
| 455 EVP_PKEY *result = NULL; |
| 456 |
| 457 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; |
| 474 } |
| 475 } |
| 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. */ |
| 482 |
| 483 end: |
| 484 if (rsa != NULL) RSA_free(rsa); |
| 485 return result; |
| 486 } |
| 487 |
| 488 static EVP_PKEY *find_verification_key(const grpc_json *json, |
| 489 const char *header_alg, |
| 490 const char *header_kid) { |
| 491 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. */ |
| 495 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 } |
| 503 |
| 504 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 } |
| 509 /* Key format is specified in: |
| 510 https://tools.ietf.org/html/rfc7518#section-6. */ |
| 511 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; |
| 516 |
| 517 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 } |
| 529 } |
| 530 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); |
| 538 return NULL; |
| 539 } |
| 540 |
| 541 static 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); |
| 545 int result = 0; |
| 546 |
| 547 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 } |
| 566 result = 1; |
| 567 |
| 568 end: |
| 569 if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx); |
| 570 return result; |
| 571 } |
| 572 |
| 573 static 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; |
| 577 EVP_PKEY *verification_key = NULL; |
| 578 grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR; |
| 579 grpc_jwt_claims *claims = NULL; |
| 580 |
| 581 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 } |
| 593 |
| 594 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 } |
| 599 |
| 600 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 } |
| 606 |
| 607 end: |
| 608 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); |
| 612 } |
| 613 |
| 614 static void on_openid_config_retrieved(grpc_exec_ctx *exec_ctx, void *user_data, |
| 615 const grpc_httpcli_response *response) { |
| 616 const grpc_json *cur; |
| 617 grpc_json *json = json_from_http(response); |
| 618 verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data; |
| 619 grpc_httpcli_request req; |
| 620 const char *jwks_uri; |
| 621 |
| 622 /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */ |
| 623 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."); |
| 627 goto error; |
| 628 } |
| 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); |
| 633 goto error; |
| 634 } |
| 635 jwks_uri += 8; |
| 636 req.handshaker = &grpc_httpcli_ssl; |
| 637 req.host = gpr_strdup(jwks_uri); |
| 638 req.path = strchr(jwks_uri, '/'); |
| 639 if (req.path == NULL) { |
| 640 req.path = ""; |
| 641 } else { |
| 642 *(req.host + (req.path - jwks_uri)) = '\0'; |
| 643 } |
| 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); |
| 650 return; |
| 651 |
| 652 error: |
| 653 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); |
| 656 } |
| 657 |
| 658 static email_key_mapping *verifier_get_mapping(grpc_jwt_verifier *v, |
| 659 const char *email_domain) { |
| 660 size_t i; |
| 661 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]; |
| 665 } |
| 666 } |
| 667 return NULL; |
| 668 } |
| 669 |
| 670 static 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); |
| 681 v->num_mappings++; |
| 682 GPR_ASSERT(v->num_mappings <= v->allocated_mappings); |
| 683 } |
| 684 |
| 685 /* Takes ownership of ctx. */ |
| 686 static void retrieve_key_and_verify(grpc_exec_ctx *exec_ctx, |
| 687 verifier_cb_ctx *ctx) { |
| 688 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; |
| 693 memset(&req, 0, sizeof(grpc_httpcli_request)); |
| 694 req.handshaker = &grpc_httpcli_ssl; |
| 695 |
| 696 GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL); |
| 697 iss = ctx->claims->iss; |
| 698 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 } |
| 706 |
| 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... */ |
| 715 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; |
| 724 } |
| 725 req.host = gpr_strdup(mapping->key_url_prefix); |
| 726 path_prefix = strchr(req.host, '/'); |
| 727 if (path_prefix == NULL) { |
| 728 gpr_asprintf(&req.path, "/%s", iss); |
| 729 } else { |
| 730 *(path_prefix++) = '\0'; |
| 731 gpr_asprintf(&req.path, "/%s/%s", path_prefix, iss); |
| 732 } |
| 733 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) { |
| 738 req.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX); |
| 739 } else { |
| 740 *(path_prefix++) = 0; |
| 741 gpr_asprintf(&req.path, "/%s%s", path_prefix, |
| 742 GRPC_OPENID_CONFIG_URL_SUFFIX); |
| 743 } |
| 744 http_cb = on_openid_config_retrieved; |
| 745 } |
| 746 |
| 747 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); |
| 752 gpr_free(req.path); |
| 753 return; |
| 754 |
| 755 error: |
| 756 ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL); |
| 757 verifier_cb_ctx_destroy(ctx); |
| 758 } |
| 759 |
| 760 void 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) { |
| 766 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 |
| 776 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; |
| 783 |
| 784 cur = dot + 1; |
| 785 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; |
| 791 |
| 792 signed_jwt_len = (size_t)(dot - jwt); |
| 793 cur = dot + 1; |
| 794 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)); |
| 800 return; |
| 801 |
| 802 error: |
| 803 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); |
| 806 } |
| 807 |
| 808 grpc_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); |
| 814 |
| 815 /* We know at least of one mapping. */ |
| 816 v->allocated_mappings = 1 + num_mappings; |
| 817 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); |
| 820 /* User-Provided mappings. */ |
| 821 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); |
| 826 } |
| 827 } |
| 828 return v; |
| 829 } |
| 830 |
| 831 void grpc_jwt_verifier_destroy(grpc_jwt_verifier *v) { |
| 832 size_t i; |
| 833 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); |
| 839 } |
| 840 gpr_free(v->mappings); |
| 841 } |
| 842 gpr_free(v); |
| 843 } |
OLD | NEW |