Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 1 | /* |
| 2 | * |
Craig Tiller | 6169d5f | 2016-03-31 07:46:18 -0700 | [diff] [blame] | 3 | * Copyright 2015, Google Inc. |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 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 |
Craig Tiller | 897e4fe | 2015-12-22 15:03:40 -0800 | [diff] [blame] | 11 | * notice, this list of conditions and the following disclaimer. |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 12 | * * Redistributions in binary form must reproduce the above |
Craig Tiller | 897e4fe | 2015-12-22 15:03:40 -0800 | [diff] [blame] | 13 | * copyright notice, this list of conditions and the following disclaimer |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 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 | |
Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 34 | #include "src/core/lib/security/credentials/jwt/jwt_verifier.h" |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 35 | |
Craig Tiller | f96dfc3 | 2015-09-10 14:43:18 -0700 | [diff] [blame] | 36 | #include <limits.h> |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 37 | #include <string.h> |
| 38 | |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 39 | #include <grpc/support/alloc.h> |
| 40 | #include <grpc/support/log.h> |
| 41 | #include <grpc/support/string_util.h> |
| 42 | #include <grpc/support/sync.h> |
Craig Tiller | 65279fe | 2016-05-12 16:01:58 -0700 | [diff] [blame] | 43 | #include <grpc/support/useful.h> |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 44 | #include <openssl/pem.h> |
| 45 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 46 | #include "src/core/lib/http/httpcli.h" |
| 47 | #include "src/core/lib/iomgr/polling_entity.h" |
| 48 | #include "src/core/lib/security/util/b64.h" |
| 49 | #include "src/core/lib/slice/slice_internal.h" |
Craig Tiller | 36d3746 | 2017-01-03 09:23:09 -0800 | [diff] [blame] | 50 | #include "src/core/lib/support/string.h" |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 51 | #include "src/core/lib/tsi/ssl_types.h" |
| 52 | |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 53 | /* --- Utils. --- */ |
| 54 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 55 | const char *grpc_jwt_verifier_status_to_string( |
| 56 | grpc_jwt_verifier_status status) { |
| 57 | switch (status) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 58 | case GRPC_JWT_VERIFIER_OK: |
| 59 | return "OK"; |
| 60 | case GRPC_JWT_VERIFIER_BAD_SIGNATURE: |
| 61 | return "BAD_SIGNATURE"; |
| 62 | case GRPC_JWT_VERIFIER_BAD_FORMAT: |
| 63 | return "BAD_FORMAT"; |
| 64 | case GRPC_JWT_VERIFIER_BAD_AUDIENCE: |
| 65 | return "BAD_AUDIENCE"; |
| 66 | case GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR: |
| 67 | return "KEY_RETRIEVAL_ERROR"; |
| 68 | case GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE: |
| 69 | return "TIME_CONSTRAINT_FAILURE"; |
| 70 | case GRPC_JWT_VERIFIER_GENERIC_ERROR: |
| 71 | return "GENERIC_ERROR"; |
| 72 | default: |
| 73 | return "UNKNOWN"; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 74 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 75 | } |
| 76 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 77 | static const EVP_MD *evp_md_from_alg(const char *alg) { |
| 78 | if (strcmp(alg, "RS256") == 0) { |
| 79 | return EVP_sha256(); |
| 80 | } else if (strcmp(alg, "RS384") == 0) { |
| 81 | return EVP_sha384(); |
| 82 | } else if (strcmp(alg, "RS512") == 0) { |
| 83 | return EVP_sha512(); |
| 84 | } else { |
| 85 | return NULL; |
| 86 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 87 | } |
| 88 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 89 | static grpc_json *parse_json_part_from_jwt(grpc_exec_ctx *exec_ctx, |
| 90 | const char *str, size_t len, |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 91 | grpc_slice *buffer) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 92 | grpc_json *json; |
| 93 | |
Craig Tiller | 87a7e1f | 2016-11-09 09:42:19 -0800 | [diff] [blame] | 94 | *buffer = grpc_base64_decode_with_len(exec_ctx, str, len, 1); |
Craig Tiller | 618e67d | 2016-10-26 21:08:10 -0700 | [diff] [blame] | 95 | if (GRPC_SLICE_IS_EMPTY(*buffer)) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 96 | gpr_log(GPR_ERROR, "Invalid base64."); |
| 97 | return NULL; |
| 98 | } |
Craig Tiller | 618e67d | 2016-10-26 21:08:10 -0700 | [diff] [blame] | 99 | json = grpc_json_parse_string_with_len((char *)GRPC_SLICE_START_PTR(*buffer), |
| 100 | GRPC_SLICE_LENGTH(*buffer)); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 101 | if (json == NULL) { |
Craig Tiller | a59c16c | 2016-10-31 07:25:01 -0700 | [diff] [blame] | 102 | grpc_slice_unref_internal(exec_ctx, *buffer); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 103 | gpr_log(GPR_ERROR, "JSON parsing error."); |
| 104 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 105 | return json; |
| 106 | } |
| 107 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 108 | static const char *validate_string_field(const grpc_json *json, |
| 109 | const char *key) { |
| 110 | if (json->type != GRPC_JSON_STRING) { |
| 111 | gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value); |
| 112 | return NULL; |
| 113 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 114 | return json->value; |
| 115 | } |
| 116 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 117 | static gpr_timespec validate_time_field(const grpc_json *json, |
| 118 | const char *key) { |
| 119 | gpr_timespec result = gpr_time_0(GPR_CLOCK_REALTIME); |
| 120 | if (json->type != GRPC_JSON_NUMBER) { |
| 121 | gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value); |
| 122 | return result; |
| 123 | } |
| 124 | result.tv_sec = strtol(json->value, NULL, 10); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 125 | return result; |
| 126 | } |
| 127 | |
| 128 | /* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */ |
| 129 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 130 | typedef struct { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 131 | const char *alg; |
| 132 | const char *kid; |
| 133 | const char *typ; |
| 134 | /* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */ |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 135 | grpc_slice buffer; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 136 | } jose_header; |
| 137 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 138 | static void jose_header_destroy(grpc_exec_ctx *exec_ctx, jose_header *h) { |
Craig Tiller | a59c16c | 2016-10-31 07:25:01 -0700 | [diff] [blame] | 139 | grpc_slice_unref_internal(exec_ctx, h->buffer); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 140 | gpr_free(h); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | /* Takes ownership of json and buffer. */ |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 144 | static jose_header *jose_header_from_json(grpc_exec_ctx *exec_ctx, |
| 145 | grpc_json *json, grpc_slice buffer) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 146 | grpc_json *cur; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 147 | jose_header *h = gpr_malloc(sizeof(jose_header)); |
| 148 | memset(h, 0, sizeof(jose_header)); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 149 | h->buffer = buffer; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 150 | for (cur = json->child; cur != NULL; cur = cur->next) { |
| 151 | if (strcmp(cur->key, "alg") == 0) { |
| 152 | /* We only support RSA-1.5 signatures for now. |
| 153 | Beware of this if we add HMAC support: |
| 154 | https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ |
| 155 | */ |
| 156 | if (cur->type != GRPC_JSON_STRING || strncmp(cur->value, "RS", 2) || |
| 157 | evp_md_from_alg(cur->value) == NULL) { |
| 158 | gpr_log(GPR_ERROR, "Invalid alg field [%s]", cur->value); |
| 159 | goto error; |
| 160 | } |
| 161 | h->alg = cur->value; |
| 162 | } else if (strcmp(cur->key, "typ") == 0) { |
| 163 | h->typ = validate_string_field(cur, "typ"); |
| 164 | if (h->typ == NULL) goto error; |
| 165 | } else if (strcmp(cur->key, "kid") == 0) { |
| 166 | h->kid = validate_string_field(cur, "kid"); |
| 167 | if (h->kid == NULL) goto error; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 168 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 169 | } |
| 170 | if (h->alg == NULL) { |
| 171 | gpr_log(GPR_ERROR, "Missing alg field."); |
| 172 | goto error; |
| 173 | } |
| 174 | grpc_json_destroy(json); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 175 | h->buffer = buffer; |
| 176 | return h; |
| 177 | |
| 178 | error: |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 179 | grpc_json_destroy(json); |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 180 | jose_header_destroy(exec_ctx, h); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 181 | return NULL; |
| 182 | } |
| 183 | |
| 184 | /* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */ |
| 185 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 186 | struct grpc_jwt_claims { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 187 | /* Well known properties already parsed. */ |
| 188 | const char *sub; |
| 189 | const char *iss; |
| 190 | const char *aud; |
| 191 | const char *jti; |
| 192 | gpr_timespec iat; |
| 193 | gpr_timespec exp; |
| 194 | gpr_timespec nbf; |
| 195 | |
| 196 | grpc_json *json; |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 197 | grpc_slice buffer; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 198 | }; |
| 199 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 200 | void grpc_jwt_claims_destroy(grpc_exec_ctx *exec_ctx, grpc_jwt_claims *claims) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 201 | grpc_json_destroy(claims->json); |
Craig Tiller | a59c16c | 2016-10-31 07:25:01 -0700 | [diff] [blame] | 202 | grpc_slice_unref_internal(exec_ctx, claims->buffer); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 203 | gpr_free(claims); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 204 | } |
| 205 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 206 | const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims) { |
| 207 | if (claims == NULL) return NULL; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 208 | return claims->json; |
| 209 | } |
| 210 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 211 | const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims) { |
| 212 | if (claims == NULL) return NULL; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 213 | return claims->sub; |
| 214 | } |
| 215 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 216 | const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims) { |
| 217 | if (claims == NULL) return NULL; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 218 | return claims->iss; |
| 219 | } |
| 220 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 221 | const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims) { |
| 222 | if (claims == NULL) return NULL; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 223 | return claims->jti; |
| 224 | } |
| 225 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 226 | const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims) { |
| 227 | if (claims == NULL) return NULL; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 228 | return claims->aud; |
| 229 | } |
| 230 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 231 | gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims) { |
| 232 | if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 233 | return claims->iat; |
| 234 | } |
| 235 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 236 | gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims) { |
| 237 | if (claims == NULL) return gpr_inf_future(GPR_CLOCK_REALTIME); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 238 | return claims->exp; |
| 239 | } |
| 240 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 241 | gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims) { |
| 242 | if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 243 | return claims->nbf; |
| 244 | } |
| 245 | |
| 246 | /* Takes ownership of json and buffer even in case of failure. */ |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 247 | grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_exec_ctx *exec_ctx, |
| 248 | grpc_json *json, grpc_slice buffer) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 249 | grpc_json *cur; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 250 | grpc_jwt_claims *claims = gpr_malloc(sizeof(grpc_jwt_claims)); |
| 251 | memset(claims, 0, sizeof(grpc_jwt_claims)); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 252 | claims->json = json; |
| 253 | claims->buffer = buffer; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 254 | claims->iat = gpr_inf_past(GPR_CLOCK_REALTIME); |
| 255 | claims->nbf = gpr_inf_past(GPR_CLOCK_REALTIME); |
| 256 | claims->exp = gpr_inf_future(GPR_CLOCK_REALTIME); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 257 | |
| 258 | /* Per the spec, all fields are optional. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 259 | for (cur = json->child; cur != NULL; cur = cur->next) { |
| 260 | if (strcmp(cur->key, "sub") == 0) { |
| 261 | claims->sub = validate_string_field(cur, "sub"); |
| 262 | if (claims->sub == NULL) goto error; |
| 263 | } else if (strcmp(cur->key, "iss") == 0) { |
| 264 | claims->iss = validate_string_field(cur, "iss"); |
| 265 | if (claims->iss == NULL) goto error; |
| 266 | } else if (strcmp(cur->key, "aud") == 0) { |
| 267 | claims->aud = validate_string_field(cur, "aud"); |
| 268 | if (claims->aud == NULL) goto error; |
| 269 | } else if (strcmp(cur->key, "jti") == 0) { |
| 270 | claims->jti = validate_string_field(cur, "jti"); |
| 271 | if (claims->jti == NULL) goto error; |
| 272 | } else if (strcmp(cur->key, "iat") == 0) { |
| 273 | claims->iat = validate_time_field(cur, "iat"); |
| 274 | if (gpr_time_cmp(claims->iat, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) |
| 275 | goto error; |
| 276 | } else if (strcmp(cur->key, "exp") == 0) { |
| 277 | claims->exp = validate_time_field(cur, "exp"); |
| 278 | if (gpr_time_cmp(claims->exp, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) |
| 279 | goto error; |
| 280 | } else if (strcmp(cur->key, "nbf") == 0) { |
| 281 | claims->nbf = validate_time_field(cur, "nbf"); |
| 282 | if (gpr_time_cmp(claims->nbf, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) |
| 283 | goto error; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 284 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 285 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 286 | return claims; |
| 287 | |
| 288 | error: |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 289 | grpc_jwt_claims_destroy(exec_ctx, claims); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 290 | return NULL; |
| 291 | } |
| 292 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 293 | grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims, |
| 294 | const char *audience) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 295 | gpr_timespec skewed_now; |
| 296 | int audience_ok; |
| 297 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 298 | GPR_ASSERT(claims != NULL); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 299 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 300 | skewed_now = |
| 301 | gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew); |
| 302 | if (gpr_time_cmp(skewed_now, claims->nbf) < 0) { |
| 303 | gpr_log(GPR_ERROR, "JWT is not valid yet."); |
| 304 | return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE; |
| 305 | } |
| 306 | skewed_now = |
| 307 | gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew); |
| 308 | if (gpr_time_cmp(skewed_now, claims->exp) > 0) { |
| 309 | gpr_log(GPR_ERROR, "JWT is expired."); |
| 310 | return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE; |
| 311 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 312 | |
Julien Boeuf | 964d7bb | 2016-11-17 16:59:48 -0800 | [diff] [blame] | 313 | /* This should be probably up to the upper layer to decide but let's harcode |
| 314 | the 99% use case here for email issuers, where the JWT must be self |
| 315 | issued. */ |
| 316 | if (grpc_jwt_issuer_email_domain(claims->iss) != NULL && |
| 317 | claims->sub != NULL && strcmp(claims->iss, claims->sub) != 0) { |
| 318 | gpr_log(GPR_ERROR, |
| 319 | "Email issuer (%s) cannot assert another subject (%s) than itself.", |
| 320 | claims->iss, claims->sub); |
| 321 | return GRPC_JWT_VERIFIER_BAD_SUBJECT; |
| 322 | } |
| 323 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 324 | if (audience == NULL) { |
| 325 | audience_ok = claims->aud == NULL; |
| 326 | } else { |
| 327 | audience_ok = claims->aud != NULL && strcmp(audience, claims->aud) == 0; |
| 328 | } |
| 329 | if (!audience_ok) { |
| 330 | gpr_log(GPR_ERROR, "Audience mismatch: expected %s and found %s.", |
| 331 | audience == NULL ? "NULL" : audience, |
| 332 | claims->aud == NULL ? "NULL" : claims->aud); |
| 333 | return GRPC_JWT_VERIFIER_BAD_AUDIENCE; |
| 334 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 335 | return GRPC_JWT_VERIFIER_OK; |
| 336 | } |
| 337 | |
| 338 | /* --- verifier_cb_ctx object. --- */ |
| 339 | |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 340 | typedef enum { |
| 341 | HTTP_RESPONSE_OPENID = 0, |
| 342 | HTTP_RESPONSE_KEYS, |
| 343 | HTTP_RESPONSE_COUNT /* must be last */ |
| 344 | } http_response_index; |
| 345 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 346 | typedef struct { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 347 | grpc_jwt_verifier *verifier; |
David Garcia Quintas | 2a50dfe | 2016-05-31 15:09:12 -0700 | [diff] [blame] | 348 | grpc_polling_entity pollent; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 349 | jose_header *header; |
| 350 | grpc_jwt_claims *claims; |
| 351 | char *audience; |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 352 | grpc_slice signature; |
| 353 | grpc_slice signed_data; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 354 | void *user_data; |
| 355 | grpc_jwt_verification_done_cb user_cb; |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 356 | grpc_http_response responses[HTTP_RESPONSE_COUNT]; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 357 | } verifier_cb_ctx; |
| 358 | |
| 359 | /* Takes ownership of the header, claims and signature. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 360 | static verifier_cb_ctx *verifier_cb_ctx_create( |
| 361 | grpc_jwt_verifier *verifier, grpc_pollset *pollset, jose_header *header, |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 362 | grpc_jwt_claims *claims, const char *audience, grpc_slice signature, |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 363 | const char *signed_jwt, size_t signed_jwt_len, void *user_data, |
| 364 | grpc_jwt_verification_done_cb cb) { |
David Garcia Quintas | 4afce7e | 2016-04-18 16:25:17 -0700 | [diff] [blame] | 365 | grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 366 | verifier_cb_ctx *ctx = gpr_malloc(sizeof(verifier_cb_ctx)); |
| 367 | memset(ctx, 0, sizeof(verifier_cb_ctx)); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 368 | ctx->verifier = verifier; |
David Garcia Quintas | c4d5112 | 2016-06-06 14:56:02 -0700 | [diff] [blame] | 369 | ctx->pollent = grpc_polling_entity_create_from_pollset(pollset); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 370 | ctx->header = header; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 371 | ctx->audience = gpr_strdup(audience); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 372 | ctx->claims = claims; |
| 373 | ctx->signature = signature; |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 374 | ctx->signed_data = grpc_slice_from_copied_buffer(signed_jwt, signed_jwt_len); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 375 | ctx->user_data = user_data; |
| 376 | ctx->user_cb = cb; |
David Garcia Quintas | 4afce7e | 2016-04-18 16:25:17 -0700 | [diff] [blame] | 377 | grpc_exec_ctx_finish(&exec_ctx); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 378 | return ctx; |
| 379 | } |
| 380 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 381 | void verifier_cb_ctx_destroy(grpc_exec_ctx *exec_ctx, verifier_cb_ctx *ctx) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 382 | if (ctx->audience != NULL) gpr_free(ctx->audience); |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 383 | if (ctx->claims != NULL) grpc_jwt_claims_destroy(exec_ctx, ctx->claims); |
Craig Tiller | a59c16c | 2016-10-31 07:25:01 -0700 | [diff] [blame] | 384 | grpc_slice_unref_internal(exec_ctx, ctx->signature); |
| 385 | grpc_slice_unref_internal(exec_ctx, ctx->signed_data); |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 386 | jose_header_destroy(exec_ctx, ctx->header); |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 387 | for (size_t i = 0; i < HTTP_RESPONSE_COUNT; i++) { |
Craig Tiller | 65279fe | 2016-05-12 16:01:58 -0700 | [diff] [blame] | 388 | grpc_http_response_destroy(&ctx->responses[i]); |
| 389 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 390 | /* TODO: see what to do with claims... */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 391 | gpr_free(ctx); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 392 | } |
| 393 | |
| 394 | /* --- grpc_jwt_verifier object. --- */ |
| 395 | |
| 396 | /* Clock skew defaults to one minute. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 397 | gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN}; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 398 | |
| 399 | /* Max delay defaults to one minute. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 400 | gpr_timespec grpc_jwt_verifier_max_delay = {60, 0, GPR_TIMESPAN}; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 401 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 402 | typedef struct { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 403 | char *email_domain; |
| 404 | char *key_url_prefix; |
| 405 | } email_key_mapping; |
| 406 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 407 | struct grpc_jwt_verifier { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 408 | email_key_mapping *mappings; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 409 | size_t num_mappings; /* Should be very few, linear search ok. */ |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 410 | size_t allocated_mappings; |
| 411 | grpc_httpcli_context http_ctx; |
| 412 | }; |
| 413 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 414 | static grpc_json *json_from_http(const grpc_httpcli_response *response) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 415 | grpc_json *json = NULL; |
| 416 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 417 | if (response == NULL) { |
| 418 | gpr_log(GPR_ERROR, "HTTP response is NULL."); |
| 419 | return NULL; |
| 420 | } |
| 421 | if (response->status != 200) { |
| 422 | gpr_log(GPR_ERROR, "Call to http server failed with error %d.", |
| 423 | response->status); |
| 424 | return NULL; |
| 425 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 426 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 427 | json = grpc_json_parse_string_with_len(response->body, response->body_length); |
| 428 | if (json == NULL) { |
| 429 | gpr_log(GPR_ERROR, "Invalid JSON found in response."); |
| 430 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 431 | return json; |
| 432 | } |
| 433 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 434 | static const grpc_json *find_property_by_name(const grpc_json *json, |
| 435 | const char *name) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 436 | const grpc_json *cur; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 437 | for (cur = json->child; cur != NULL; cur = cur->next) { |
| 438 | if (strcmp(cur->key, name) == 0) return cur; |
| 439 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 440 | return NULL; |
| 441 | } |
| 442 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 443 | static EVP_PKEY *extract_pkey_from_x509(const char *x509_str) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 444 | X509 *x509 = NULL; |
| 445 | EVP_PKEY *result = NULL; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 446 | BIO *bio = BIO_new(BIO_s_mem()); |
| 447 | size_t len = strlen(x509_str); |
| 448 | GPR_ASSERT(len < INT_MAX); |
| 449 | BIO_write(bio, x509_str, (int)len); |
| 450 | x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| 451 | if (x509 == NULL) { |
| 452 | gpr_log(GPR_ERROR, "Unable to parse x509 cert."); |
| 453 | goto end; |
| 454 | } |
| 455 | result = X509_get_pubkey(x509); |
| 456 | if (result == NULL) { |
| 457 | gpr_log(GPR_ERROR, "Cannot find public key in X509 cert."); |
| 458 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 459 | |
| 460 | end: |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 461 | BIO_free(bio); |
| 462 | if (x509 != NULL) X509_free(x509); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 463 | return result; |
| 464 | } |
| 465 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 466 | static BIGNUM *bignum_from_base64(grpc_exec_ctx *exec_ctx, const char *b64) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 467 | BIGNUM *result = NULL; |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 468 | grpc_slice bin; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 469 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 470 | if (b64 == NULL) return NULL; |
Craig Tiller | 87a7e1f | 2016-11-09 09:42:19 -0800 | [diff] [blame] | 471 | bin = grpc_base64_decode(exec_ctx, b64, 1); |
Craig Tiller | 618e67d | 2016-10-26 21:08:10 -0700 | [diff] [blame] | 472 | if (GRPC_SLICE_IS_EMPTY(bin)) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 473 | gpr_log(GPR_ERROR, "Invalid base64 for big num."); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 474 | return NULL; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 475 | } |
Craig Tiller | 618e67d | 2016-10-26 21:08:10 -0700 | [diff] [blame] | 476 | result = BN_bin2bn(GRPC_SLICE_START_PTR(bin), |
| 477 | TSI_SIZE_AS_SIZE(GRPC_SLICE_LENGTH(bin)), NULL); |
Craig Tiller | a59c16c | 2016-10-31 07:25:01 -0700 | [diff] [blame] | 478 | grpc_slice_unref_internal(exec_ctx, bin); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 479 | return result; |
| 480 | } |
| 481 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 482 | static EVP_PKEY *pkey_from_jwk(grpc_exec_ctx *exec_ctx, const grpc_json *json, |
| 483 | const char *kty) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 484 | const grpc_json *key_prop; |
| 485 | RSA *rsa = NULL; |
| 486 | EVP_PKEY *result = NULL; |
| 487 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 488 | GPR_ASSERT(kty != NULL && json != NULL); |
| 489 | if (strcmp(kty, "RSA") != 0) { |
| 490 | gpr_log(GPR_ERROR, "Unsupported key type %s.", kty); |
| 491 | goto end; |
| 492 | } |
| 493 | rsa = RSA_new(); |
| 494 | if (rsa == NULL) { |
| 495 | gpr_log(GPR_ERROR, "Could not create rsa key."); |
| 496 | goto end; |
| 497 | } |
| 498 | for (key_prop = json->child; key_prop != NULL; key_prop = key_prop->next) { |
| 499 | if (strcmp(key_prop->key, "n") == 0) { |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 500 | rsa->n = |
| 501 | bignum_from_base64(exec_ctx, validate_string_field(key_prop, "n")); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 502 | if (rsa->n == NULL) goto end; |
| 503 | } else if (strcmp(key_prop->key, "e") == 0) { |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 504 | rsa->e = |
| 505 | bignum_from_base64(exec_ctx, validate_string_field(key_prop, "e")); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 506 | if (rsa->e == NULL) goto end; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 507 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 508 | } |
| 509 | if (rsa->e == NULL || rsa->n == NULL) { |
| 510 | gpr_log(GPR_ERROR, "Missing RSA public key field."); |
| 511 | goto end; |
| 512 | } |
| 513 | result = EVP_PKEY_new(); |
| 514 | EVP_PKEY_set1_RSA(result, rsa); /* uprefs rsa. */ |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 515 | |
| 516 | end: |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 517 | if (rsa != NULL) RSA_free(rsa); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 518 | return result; |
| 519 | } |
| 520 | |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 521 | static EVP_PKEY *find_verification_key(grpc_exec_ctx *exec_ctx, |
| 522 | const grpc_json *json, |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 523 | const char *header_alg, |
| 524 | const char *header_kid) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 525 | const grpc_json *jkey; |
| 526 | const grpc_json *jwk_keys; |
| 527 | /* Try to parse the json as a JWK set: |
| 528 | https://tools.ietf.org/html/rfc7517#section-5. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 529 | jwk_keys = find_property_by_name(json, "keys"); |
| 530 | if (jwk_keys == NULL) { |
| 531 | /* Use the google proprietary format which is: |
| 532 | { <kid1>: <x5091>, <kid2>: <x5092>, ... } */ |
| 533 | const grpc_json *cur = find_property_by_name(json, header_kid); |
| 534 | if (cur == NULL) return NULL; |
| 535 | return extract_pkey_from_x509(cur->value); |
| 536 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 537 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 538 | if (jwk_keys->type != GRPC_JSON_ARRAY) { |
| 539 | gpr_log(GPR_ERROR, |
| 540 | "Unexpected value type of keys property in jwks key set."); |
| 541 | return NULL; |
| 542 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 543 | /* Key format is specified in: |
| 544 | https://tools.ietf.org/html/rfc7518#section-6. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 545 | for (jkey = jwk_keys->child; jkey != NULL; jkey = jkey->next) { |
| 546 | grpc_json *key_prop; |
| 547 | const char *alg = NULL; |
| 548 | const char *kid = NULL; |
| 549 | const char *kty = NULL; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 550 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 551 | if (jkey->type != GRPC_JSON_OBJECT) continue; |
| 552 | for (key_prop = jkey->child; key_prop != NULL; key_prop = key_prop->next) { |
| 553 | if (strcmp(key_prop->key, "alg") == 0 && |
| 554 | key_prop->type == GRPC_JSON_STRING) { |
| 555 | alg = key_prop->value; |
| 556 | } else if (strcmp(key_prop->key, "kid") == 0 && |
| 557 | key_prop->type == GRPC_JSON_STRING) { |
| 558 | kid = key_prop->value; |
| 559 | } else if (strcmp(key_prop->key, "kty") == 0 && |
| 560 | key_prop->type == GRPC_JSON_STRING) { |
| 561 | kty = key_prop->value; |
| 562 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 563 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 564 | if (alg != NULL && kid != NULL && kty != NULL && |
| 565 | strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) { |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 566 | return pkey_from_jwk(exec_ctx, jkey, kty); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 567 | } |
| 568 | } |
| 569 | gpr_log(GPR_ERROR, |
| 570 | "Could not find matching key in key set for kid=%s and alg=%s", |
| 571 | header_kid, header_alg); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 572 | return NULL; |
| 573 | } |
| 574 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 575 | static int verify_jwt_signature(EVP_PKEY *key, const char *alg, |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 576 | grpc_slice signature, grpc_slice signed_data) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 577 | EVP_MD_CTX *md_ctx = EVP_MD_CTX_create(); |
| 578 | const EVP_MD *md = evp_md_from_alg(alg); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 579 | int result = 0; |
| 580 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 581 | GPR_ASSERT(md != NULL); /* Checked before. */ |
| 582 | if (md_ctx == NULL) { |
| 583 | gpr_log(GPR_ERROR, "Could not create EVP_MD_CTX."); |
| 584 | goto end; |
| 585 | } |
| 586 | if (EVP_DigestVerifyInit(md_ctx, NULL, md, NULL, key) != 1) { |
| 587 | gpr_log(GPR_ERROR, "EVP_DigestVerifyInit failed."); |
| 588 | goto end; |
| 589 | } |
Craig Tiller | 618e67d | 2016-10-26 21:08:10 -0700 | [diff] [blame] | 590 | if (EVP_DigestVerifyUpdate(md_ctx, GRPC_SLICE_START_PTR(signed_data), |
| 591 | GRPC_SLICE_LENGTH(signed_data)) != 1) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 592 | gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed."); |
| 593 | goto end; |
| 594 | } |
Craig Tiller | 618e67d | 2016-10-26 21:08:10 -0700 | [diff] [blame] | 595 | if (EVP_DigestVerifyFinal(md_ctx, GRPC_SLICE_START_PTR(signature), |
| 596 | GRPC_SLICE_LENGTH(signature)) != 1) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 597 | gpr_log(GPR_ERROR, "JWT signature verification failed."); |
| 598 | goto end; |
| 599 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 600 | result = 1; |
| 601 | |
| 602 | end: |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 603 | if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 604 | return result; |
| 605 | } |
| 606 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 607 | static void on_keys_retrieved(grpc_exec_ctx *exec_ctx, void *user_data, |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame] | 608 | grpc_error *error) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 609 | verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data; |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 610 | grpc_json *json = json_from_http(&ctx->responses[HTTP_RESPONSE_KEYS]); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 611 | EVP_PKEY *verification_key = NULL; |
| 612 | grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR; |
| 613 | grpc_jwt_claims *claims = NULL; |
| 614 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 615 | if (json == NULL) { |
| 616 | status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR; |
| 617 | goto end; |
| 618 | } |
| 619 | verification_key = |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 620 | find_verification_key(exec_ctx, json, ctx->header->alg, ctx->header->kid); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 621 | if (verification_key == NULL) { |
| 622 | gpr_log(GPR_ERROR, "Could not find verification key with kid %s.", |
| 623 | ctx->header->kid); |
| 624 | status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR; |
| 625 | goto end; |
| 626 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 627 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 628 | if (!verify_jwt_signature(verification_key, ctx->header->alg, ctx->signature, |
| 629 | ctx->signed_data)) { |
| 630 | status = GRPC_JWT_VERIFIER_BAD_SIGNATURE; |
| 631 | goto end; |
| 632 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 633 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 634 | status = grpc_jwt_claims_check(ctx->claims, ctx->audience); |
| 635 | if (status == GRPC_JWT_VERIFIER_OK) { |
| 636 | /* Pass ownership. */ |
| 637 | claims = ctx->claims; |
| 638 | ctx->claims = NULL; |
| 639 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 640 | |
| 641 | end: |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 642 | if (json != NULL) grpc_json_destroy(json); |
| 643 | if (verification_key != NULL) EVP_PKEY_free(verification_key); |
Craig Tiller | 3cf7922 | 2016-11-14 08:02:45 -0800 | [diff] [blame] | 644 | ctx->user_cb(exec_ctx, ctx->user_data, status, claims); |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 645 | verifier_cb_ctx_destroy(exec_ctx, ctx); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 646 | } |
| 647 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 648 | static void on_openid_config_retrieved(grpc_exec_ctx *exec_ctx, void *user_data, |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame] | 649 | grpc_error *error) { |
Craig Tiller | 5cc175f | 2015-07-09 08:48:35 -0700 | [diff] [blame] | 650 | const grpc_json *cur; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 651 | verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data; |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 652 | const grpc_http_response *response = &ctx->responses[HTTP_RESPONSE_OPENID]; |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame] | 653 | grpc_json *json = json_from_http(response); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 654 | grpc_httpcli_request req; |
| 655 | const char *jwks_uri; |
| 656 | |
Craig Tiller | 45724b3 | 2015-09-22 10:42:19 -0700 | [diff] [blame] | 657 | /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 658 | if (json == NULL) goto error; |
| 659 | cur = find_property_by_name(json, "jwks_uri"); |
| 660 | if (cur == NULL) { |
| 661 | gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config."); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 662 | goto error; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 663 | } |
| 664 | jwks_uri = validate_string_field(cur, "jwks_uri"); |
| 665 | if (jwks_uri == NULL) goto error; |
| 666 | if (strstr(jwks_uri, "https://") != jwks_uri) { |
| 667 | gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 668 | goto error; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 669 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 670 | jwks_uri += 8; |
Craig Tiller | f53d9c8 | 2015-08-04 14:19:43 -0700 | [diff] [blame] | 671 | req.handshaker = &grpc_httpcli_ssl; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 672 | req.host = gpr_strdup(jwks_uri); |
Matthew Iselin | 1824f05 | 2016-02-10 12:16:06 +1100 | [diff] [blame] | 673 | req.http.path = strchr(jwks_uri, '/'); |
| 674 | if (req.http.path == NULL) { |
| 675 | req.http.path = ""; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 676 | } else { |
Matthew Iselin | 1824f05 | 2016-02-10 12:16:06 +1100 | [diff] [blame] | 677 | *(req.host + (req.http.path - jwks_uri)) = '\0'; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 678 | } |
Craig Tiller | 65279fe | 2016-05-12 16:01:58 -0700 | [diff] [blame] | 679 | |
Craig Tiller | 20afa3d | 2016-10-17 14:52:14 -0700 | [diff] [blame] | 680 | /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host |
Craig Tiller | 4588186 | 2016-09-23 10:48:53 -0700 | [diff] [blame] | 681 | channel. This would allow us to cancel an authentication query when under |
| 682 | extreme memory pressure. */ |
Craig Tiller | afcc875 | 2016-10-18 16:10:06 -0700 | [diff] [blame] | 683 | grpc_resource_quota *resource_quota = |
| 684 | grpc_resource_quota_create("jwt_verifier"); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 685 | grpc_httpcli_get( |
Craig Tiller | 20afa3d | 2016-10-17 14:52:14 -0700 | [diff] [blame] | 686 | exec_ctx, &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req, |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 687 | gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), |
Craig Tiller | 91031da | 2016-12-28 15:44:25 -0800 | [diff] [blame] | 688 | grpc_closure_create(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx), |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 689 | &ctx->responses[HTTP_RESPONSE_KEYS]); |
Craig Tiller | a59c16c | 2016-10-31 07:25:01 -0700 | [diff] [blame] | 690 | grpc_resource_quota_unref_internal(exec_ctx, resource_quota); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 691 | grpc_json_destroy(json); |
| 692 | gpr_free(req.host); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 693 | return; |
| 694 | |
| 695 | error: |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 696 | if (json != NULL) grpc_json_destroy(json); |
Craig Tiller | 3cf7922 | 2016-11-14 08:02:45 -0800 | [diff] [blame] | 697 | ctx->user_cb(exec_ctx, ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, |
| 698 | NULL); |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 699 | verifier_cb_ctx_destroy(exec_ctx, ctx); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 700 | } |
| 701 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 702 | static email_key_mapping *verifier_get_mapping(grpc_jwt_verifier *v, |
| 703 | const char *email_domain) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 704 | size_t i; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 705 | if (v->mappings == NULL) return NULL; |
| 706 | for (i = 0; i < v->num_mappings; i++) { |
| 707 | if (strcmp(email_domain, v->mappings[i].email_domain) == 0) { |
| 708 | return &v->mappings[i]; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 709 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 710 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 711 | return NULL; |
| 712 | } |
| 713 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 714 | static void verifier_put_mapping(grpc_jwt_verifier *v, const char *email_domain, |
| 715 | const char *key_url_prefix) { |
| 716 | email_key_mapping *mapping = verifier_get_mapping(v, email_domain); |
| 717 | GPR_ASSERT(v->num_mappings < v->allocated_mappings); |
| 718 | if (mapping != NULL) { |
| 719 | gpr_free(mapping->key_url_prefix); |
| 720 | mapping->key_url_prefix = gpr_strdup(key_url_prefix); |
| 721 | return; |
| 722 | } |
| 723 | v->mappings[v->num_mappings].email_domain = gpr_strdup(email_domain); |
| 724 | v->mappings[v->num_mappings].key_url_prefix = gpr_strdup(key_url_prefix); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 725 | v->num_mappings++; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 726 | GPR_ASSERT(v->num_mappings <= v->allocated_mappings); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 727 | } |
| 728 | |
Julien Boeuf | 964d7bb | 2016-11-17 16:59:48 -0800 | [diff] [blame] | 729 | /* Very non-sophisticated way to detect an email address. Should be good |
| 730 | enough for now... */ |
| 731 | const char *grpc_jwt_issuer_email_domain(const char *issuer) { |
| 732 | const char *at_sign = strchr(issuer, '@'); |
| 733 | if (at_sign == NULL) return NULL; |
| 734 | const char *email_domain = at_sign + 1; |
| 735 | if (*email_domain == '\0') return NULL; |
| 736 | const char *dot = strrchr(email_domain, '.'); |
| 737 | if (dot == NULL || dot == email_domain) return email_domain; |
| 738 | GPR_ASSERT(dot > email_domain); |
| 739 | /* There may be a subdomain, we just want the domain. */ |
| 740 | dot = gpr_memrchr(email_domain, '.', (size_t)(dot - email_domain)); |
| 741 | if (dot == NULL) return email_domain; |
| 742 | return dot + 1; |
| 743 | } |
| 744 | |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 745 | /* Takes ownership of ctx. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 746 | static void retrieve_key_and_verify(grpc_exec_ctx *exec_ctx, |
| 747 | verifier_cb_ctx *ctx) { |
Julien Boeuf | 964d7bb | 2016-11-17 16:59:48 -0800 | [diff] [blame] | 748 | const char *email_domain; |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame] | 749 | grpc_closure *http_cb; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 750 | char *path_prefix = NULL; |
| 751 | const char *iss; |
| 752 | grpc_httpcli_request req; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 753 | memset(&req, 0, sizeof(grpc_httpcli_request)); |
Craig Tiller | f53d9c8 | 2015-08-04 14:19:43 -0700 | [diff] [blame] | 754 | req.handshaker = &grpc_httpcli_ssl; |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 755 | http_response_index rsp_idx; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 756 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 757 | GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 758 | iss = ctx->claims->iss; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 759 | if (ctx->header->kid == NULL) { |
| 760 | gpr_log(GPR_ERROR, "Missing kid in jose header."); |
| 761 | goto error; |
| 762 | } |
| 763 | if (iss == NULL) { |
| 764 | gpr_log(GPR_ERROR, "Missing iss in claims."); |
| 765 | goto error; |
| 766 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 767 | |
| 768 | /* This code relies on: |
| 769 | https://openid.net/specs/openid-connect-discovery-1_0.html |
| 770 | Nobody seems to implement the account/email/webfinger part 2. of the spec |
| 771 | so we will rely instead on email/url mappings if we detect such an issuer. |
| 772 | Part 4, on the other hand is implemented by both google and salesforce. */ |
Julien Boeuf | 964d7bb | 2016-11-17 16:59:48 -0800 | [diff] [blame] | 773 | email_domain = grpc_jwt_issuer_email_domain(iss); |
| 774 | if (email_domain != NULL) { |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 775 | email_key_mapping *mapping; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 776 | GPR_ASSERT(ctx->verifier != NULL); |
| 777 | mapping = verifier_get_mapping(ctx->verifier, email_domain); |
| 778 | if (mapping == NULL) { |
| 779 | gpr_log(GPR_ERROR, "Missing mapping for issuer email."); |
| 780 | goto error; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 781 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 782 | req.host = gpr_strdup(mapping->key_url_prefix); |
| 783 | path_prefix = strchr(req.host, '/'); |
| 784 | if (path_prefix == NULL) { |
Matthew Iselin | 1824f05 | 2016-02-10 12:16:06 +1100 | [diff] [blame] | 785 | gpr_asprintf(&req.http.path, "/%s", iss); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 786 | } else { |
| 787 | *(path_prefix++) = '\0'; |
Matthew Iselin | 1824f05 | 2016-02-10 12:16:06 +1100 | [diff] [blame] | 788 | gpr_asprintf(&req.http.path, "/%s/%s", path_prefix, iss); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 789 | } |
Craig Tiller | 91031da | 2016-12-28 15:44:25 -0800 | [diff] [blame] | 790 | http_cb = |
| 791 | grpc_closure_create(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx); |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 792 | rsp_idx = HTTP_RESPONSE_KEYS; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 793 | } else { |
| 794 | req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss); |
| 795 | path_prefix = strchr(req.host, '/'); |
| 796 | if (path_prefix == NULL) { |
Matthew Iselin | 1824f05 | 2016-02-10 12:16:06 +1100 | [diff] [blame] | 797 | req.http.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 798 | } else { |
| 799 | *(path_prefix++) = 0; |
Matthew Iselin | 1824f05 | 2016-02-10 12:16:06 +1100 | [diff] [blame] | 800 | gpr_asprintf(&req.http.path, "/%s%s", path_prefix, |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 801 | GRPC_OPENID_CONFIG_URL_SUFFIX); |
| 802 | } |
Craig Tiller | 91031da | 2016-12-28 15:44:25 -0800 | [diff] [blame] | 803 | http_cb = grpc_closure_create(on_openid_config_retrieved, ctx, |
| 804 | grpc_schedule_on_exec_ctx); |
Craig Tiller | 4e80494 | 2016-06-02 09:33:18 -0700 | [diff] [blame] | 805 | rsp_idx = HTTP_RESPONSE_OPENID; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 806 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 807 | |
Craig Tiller | 20afa3d | 2016-10-17 14:52:14 -0700 | [diff] [blame] | 808 | /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host |
Craig Tiller | 4588186 | 2016-09-23 10:48:53 -0700 | [diff] [blame] | 809 | channel. This would allow us to cancel an authentication query when under |
| 810 | extreme memory pressure. */ |
Craig Tiller | afcc875 | 2016-10-18 16:10:06 -0700 | [diff] [blame] | 811 | grpc_resource_quota *resource_quota = |
| 812 | grpc_resource_quota_create("jwt_verifier"); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 813 | grpc_httpcli_get( |
Craig Tiller | 20afa3d | 2016-10-17 14:52:14 -0700 | [diff] [blame] | 814 | exec_ctx, &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req, |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 815 | gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), |
Craig Tiller | 65279fe | 2016-05-12 16:01:58 -0700 | [diff] [blame] | 816 | http_cb, &ctx->responses[rsp_idx]); |
Craig Tiller | a59c16c | 2016-10-31 07:25:01 -0700 | [diff] [blame] | 817 | grpc_resource_quota_unref_internal(exec_ctx, resource_quota); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 818 | gpr_free(req.host); |
Matthew Iselin | 1824f05 | 2016-02-10 12:16:06 +1100 | [diff] [blame] | 819 | gpr_free(req.http.path); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 820 | return; |
| 821 | |
| 822 | error: |
Craig Tiller | 3cf7922 | 2016-11-14 08:02:45 -0800 | [diff] [blame] | 823 | ctx->user_cb(exec_ctx, ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, |
| 824 | NULL); |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 825 | verifier_cb_ctx_destroy(exec_ctx, ctx); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 826 | } |
| 827 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 828 | void grpc_jwt_verifier_verify(grpc_exec_ctx *exec_ctx, |
| 829 | grpc_jwt_verifier *verifier, |
| 830 | grpc_pollset *pollset, const char *jwt, |
| 831 | const char *audience, |
| 832 | grpc_jwt_verification_done_cb cb, |
| 833 | void *user_data) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 834 | const char *dot = NULL; |
| 835 | grpc_json *json; |
| 836 | jose_header *header = NULL; |
| 837 | grpc_jwt_claims *claims = NULL; |
Craig Tiller | d41a4a7 | 2016-10-26 16:16:06 -0700 | [diff] [blame] | 838 | grpc_slice header_buffer; |
| 839 | grpc_slice claims_buffer; |
| 840 | grpc_slice signature; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 841 | size_t signed_jwt_len; |
| 842 | const char *cur = jwt; |
| 843 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 844 | GPR_ASSERT(verifier != NULL && jwt != NULL && audience != NULL && cb != NULL); |
| 845 | dot = strchr(cur, '.'); |
| 846 | if (dot == NULL) goto error; |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 847 | json = parse_json_part_from_jwt(exec_ctx, cur, (size_t)(dot - cur), |
| 848 | &header_buffer); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 849 | if (json == NULL) goto error; |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 850 | header = jose_header_from_json(exec_ctx, json, header_buffer); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 851 | if (header == NULL) goto error; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 852 | |
| 853 | cur = dot + 1; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 854 | dot = strchr(cur, '.'); |
| 855 | if (dot == NULL) goto error; |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 856 | json = parse_json_part_from_jwt(exec_ctx, cur, (size_t)(dot - cur), |
| 857 | &claims_buffer); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 858 | if (json == NULL) goto error; |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 859 | claims = grpc_jwt_claims_from_json(exec_ctx, json, claims_buffer); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 860 | if (claims == NULL) goto error; |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 861 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 862 | signed_jwt_len = (size_t)(dot - jwt); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 863 | cur = dot + 1; |
Craig Tiller | 87a7e1f | 2016-11-09 09:42:19 -0800 | [diff] [blame] | 864 | signature = grpc_base64_decode(exec_ctx, cur, 1); |
Craig Tiller | 618e67d | 2016-10-26 21:08:10 -0700 | [diff] [blame] | 865 | if (GRPC_SLICE_IS_EMPTY(signature)) goto error; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 866 | retrieve_key_and_verify( |
| 867 | exec_ctx, |
| 868 | verifier_cb_ctx_create(verifier, pollset, header, claims, audience, |
| 869 | signature, jwt, signed_jwt_len, user_data, cb)); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 870 | return; |
| 871 | |
| 872 | error: |
Craig Tiller | bd1795c | 2016-10-31 15:30:00 -0700 | [diff] [blame] | 873 | if (header != NULL) jose_header_destroy(exec_ctx, header); |
| 874 | if (claims != NULL) grpc_jwt_claims_destroy(exec_ctx, claims); |
Craig Tiller | 3cf7922 | 2016-11-14 08:02:45 -0800 | [diff] [blame] | 875 | cb(exec_ctx, user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, NULL); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 876 | } |
| 877 | |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 878 | grpc_jwt_verifier *grpc_jwt_verifier_create( |
| 879 | const grpc_jwt_verifier_email_domain_key_url_mapping *mappings, |
| 880 | size_t num_mappings) { |
| 881 | grpc_jwt_verifier *v = gpr_malloc(sizeof(grpc_jwt_verifier)); |
| 882 | memset(v, 0, sizeof(grpc_jwt_verifier)); |
| 883 | grpc_httpcli_context_init(&v->http_ctx); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 884 | |
| 885 | /* We know at least of one mapping. */ |
| 886 | v->allocated_mappings = 1 + num_mappings; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 887 | v->mappings = gpr_malloc(v->allocated_mappings * sizeof(email_key_mapping)); |
| 888 | verifier_put_mapping(v, GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN, |
| 889 | GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 890 | /* User-Provided mappings. */ |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 891 | if (mappings != NULL) { |
| 892 | size_t i; |
| 893 | for (i = 0; i < num_mappings; i++) { |
| 894 | verifier_put_mapping(v, mappings[i].email_domain, |
| 895 | mappings[i].key_url_prefix); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 896 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 897 | } |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 898 | return v; |
| 899 | } |
| 900 | |
Craig Tiller | 9e5ac1b | 2017-02-14 22:25:50 -0800 | [diff] [blame] | 901 | void grpc_jwt_verifier_destroy(grpc_exec_ctx *exec_ctx, grpc_jwt_verifier *v) { |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 902 | size_t i; |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 903 | if (v == NULL) return; |
Craig Tiller | 9e5ac1b | 2017-02-14 22:25:50 -0800 | [diff] [blame] | 904 | grpc_httpcli_context_destroy(exec_ctx, &v->http_ctx); |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 905 | if (v->mappings != NULL) { |
| 906 | for (i = 0; i < v->num_mappings; i++) { |
| 907 | gpr_free(v->mappings[i].email_domain); |
| 908 | gpr_free(v->mappings[i].key_url_prefix); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 909 | } |
Craig Tiller | a82950e | 2015-09-22 12:33:20 -0700 | [diff] [blame] | 910 | gpr_free(v->mappings); |
| 911 | } |
| 912 | gpr_free(v); |
Julien Boeuf | feca1bf | 2015-06-22 16:46:20 +0200 | [diff] [blame] | 913 | } |