blob: f128177e8c4abc8df6844548f241656a88cffa4e [file] [log] [blame]
Julien Boeuffeca1bf2015-06-22 16:46:20 +02001/*
2 *
Craig Tiller6169d5f2016-03-31 07:46:18 -07003 * Copyright 2015, Google Inc.
Julien Boeuffeca1bf2015-06-22 16:46:20 +02004 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
Craig Tiller897e4fe2015-12-22 15:03:40 -080011 * notice, this list of conditions and the following disclaimer.
Julien Boeuffeca1bf2015-06-22 16:46:20 +020012 * * Redistributions in binary form must reproduce the above
Craig Tiller897e4fe2015-12-22 15:03:40 -080013 * copyright notice, this list of conditions and the following disclaimer
Julien Boeuffeca1bf2015-06-22 16:46:20 +020014 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
Julien Boeuf8ca294e2016-05-02 14:56:30 -070034#include "src/core/lib/security/credentials/jwt/jwt_verifier.h"
Julien Boeuffeca1bf2015-06-22 16:46:20 +020035
Craig Tillerf96dfc32015-09-10 14:43:18 -070036#include <limits.h>
Julien Boeuffeca1bf2015-06-22 16:46:20 +020037#include <string.h>
38
Julien Boeuffeca1bf2015-06-22 16:46:20 +020039#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 Tiller65279fe2016-05-12 16:01:58 -070043#include <grpc/support/useful.h>
Julien Boeuffeca1bf2015-06-22 16:46:20 +020044#include <openssl/pem.h>
45
Craig Tillerbd1795c2016-10-31 15:30:00 -070046#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 Tiller36d37462017-01-03 09:23:09 -080050#include "src/core/lib/support/string.h"
Craig Tillerbd1795c2016-10-31 15:30:00 -070051#include "src/core/lib/tsi/ssl_types.h"
52
Julien Boeuffeca1bf2015-06-22 16:46:20 +020053/* --- Utils. --- */
54
Craig Tillera82950e2015-09-22 12:33:20 -070055const char *grpc_jwt_verifier_status_to_string(
56 grpc_jwt_verifier_status status) {
57 switch (status) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +020058 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 Tillera82950e2015-09-22 12:33:20 -070074 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +020075}
76
Craig Tillera82950e2015-09-22 12:33:20 -070077static 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 Boeuffeca1bf2015-06-22 16:46:20 +020087}
88
Craig Tillerbd1795c2016-10-31 15:30:00 -070089static grpc_json *parse_json_part_from_jwt(grpc_exec_ctx *exec_ctx,
90 const char *str, size_t len,
Craig Tillerd41a4a72016-10-26 16:16:06 -070091 grpc_slice *buffer) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +020092 grpc_json *json;
93
Craig Tiller87a7e1f2016-11-09 09:42:19 -080094 *buffer = grpc_base64_decode_with_len(exec_ctx, str, len, 1);
Craig Tiller618e67d2016-10-26 21:08:10 -070095 if (GRPC_SLICE_IS_EMPTY(*buffer)) {
Craig Tillera82950e2015-09-22 12:33:20 -070096 gpr_log(GPR_ERROR, "Invalid base64.");
97 return NULL;
98 }
Craig Tiller618e67d2016-10-26 21:08:10 -070099 json = grpc_json_parse_string_with_len((char *)GRPC_SLICE_START_PTR(*buffer),
100 GRPC_SLICE_LENGTH(*buffer));
Craig Tillera82950e2015-09-22 12:33:20 -0700101 if (json == NULL) {
Craig Tillera59c16c2016-10-31 07:25:01 -0700102 grpc_slice_unref_internal(exec_ctx, *buffer);
Craig Tillera82950e2015-09-22 12:33:20 -0700103 gpr_log(GPR_ERROR, "JSON parsing error.");
104 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200105 return json;
106}
107
Craig Tillera82950e2015-09-22 12:33:20 -0700108static 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 Boeuffeca1bf2015-06-22 16:46:20 +0200114 return json->value;
115}
116
Craig Tillera82950e2015-09-22 12:33:20 -0700117static 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 Boeuffeca1bf2015-06-22 16:46:20 +0200125 return result;
126}
127
128/* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */
129
Craig Tillera82950e2015-09-22 12:33:20 -0700130typedef struct {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200131 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 Tillerd41a4a72016-10-26 16:16:06 -0700135 grpc_slice buffer;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200136} jose_header;
137
Craig Tillerbd1795c2016-10-31 15:30:00 -0700138static void jose_header_destroy(grpc_exec_ctx *exec_ctx, jose_header *h) {
Craig Tillera59c16c2016-10-31 07:25:01 -0700139 grpc_slice_unref_internal(exec_ctx, h->buffer);
Craig Tillera82950e2015-09-22 12:33:20 -0700140 gpr_free(h);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200141}
142
143/* Takes ownership of json and buffer. */
Craig Tillerbd1795c2016-10-31 15:30:00 -0700144static jose_header *jose_header_from_json(grpc_exec_ctx *exec_ctx,
145 grpc_json *json, grpc_slice buffer) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200146 grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700147 jose_header *h = gpr_malloc(sizeof(jose_header));
148 memset(h, 0, sizeof(jose_header));
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200149 h->buffer = buffer;
Craig Tillera82950e2015-09-22 12:33:20 -0700150 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 Boeuffeca1bf2015-06-22 16:46:20 +0200168 }
Craig Tillera82950e2015-09-22 12:33:20 -0700169 }
170 if (h->alg == NULL) {
171 gpr_log(GPR_ERROR, "Missing alg field.");
172 goto error;
173 }
174 grpc_json_destroy(json);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200175 h->buffer = buffer;
176 return h;
177
178error:
Craig Tillera82950e2015-09-22 12:33:20 -0700179 grpc_json_destroy(json);
Craig Tillerbd1795c2016-10-31 15:30:00 -0700180 jose_header_destroy(exec_ctx, h);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200181 return NULL;
182}
183
184/* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */
185
Craig Tillera82950e2015-09-22 12:33:20 -0700186struct grpc_jwt_claims {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200187 /* 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 Tillerd41a4a72016-10-26 16:16:06 -0700197 grpc_slice buffer;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200198};
199
Craig Tillerbd1795c2016-10-31 15:30:00 -0700200void grpc_jwt_claims_destroy(grpc_exec_ctx *exec_ctx, grpc_jwt_claims *claims) {
Craig Tillera82950e2015-09-22 12:33:20 -0700201 grpc_json_destroy(claims->json);
Craig Tillera59c16c2016-10-31 07:25:01 -0700202 grpc_slice_unref_internal(exec_ctx, claims->buffer);
Craig Tillera82950e2015-09-22 12:33:20 -0700203 gpr_free(claims);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200204}
205
Craig Tillera82950e2015-09-22 12:33:20 -0700206const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims) {
207 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200208 return claims->json;
209}
210
Craig Tillera82950e2015-09-22 12:33:20 -0700211const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims) {
212 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200213 return claims->sub;
214}
215
Craig Tillera82950e2015-09-22 12:33:20 -0700216const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims) {
217 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200218 return claims->iss;
219}
220
Craig Tillera82950e2015-09-22 12:33:20 -0700221const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims) {
222 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200223 return claims->jti;
224}
225
Craig Tillera82950e2015-09-22 12:33:20 -0700226const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims) {
227 if (claims == NULL) return NULL;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200228 return claims->aud;
229}
230
Craig Tillera82950e2015-09-22 12:33:20 -0700231gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims) {
232 if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200233 return claims->iat;
234}
235
Craig Tillera82950e2015-09-22 12:33:20 -0700236gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims) {
237 if (claims == NULL) return gpr_inf_future(GPR_CLOCK_REALTIME);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200238 return claims->exp;
239}
240
Craig Tillera82950e2015-09-22 12:33:20 -0700241gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims) {
242 if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200243 return claims->nbf;
244}
245
246/* Takes ownership of json and buffer even in case of failure. */
Craig Tillerbd1795c2016-10-31 15:30:00 -0700247grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_exec_ctx *exec_ctx,
248 grpc_json *json, grpc_slice buffer) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200249 grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700250 grpc_jwt_claims *claims = gpr_malloc(sizeof(grpc_jwt_claims));
251 memset(claims, 0, sizeof(grpc_jwt_claims));
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200252 claims->json = json;
253 claims->buffer = buffer;
Craig Tillera82950e2015-09-22 12:33:20 -0700254 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 Boeuffeca1bf2015-06-22 16:46:20 +0200257
258 /* Per the spec, all fields are optional. */
Craig Tillera82950e2015-09-22 12:33:20 -0700259 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 Boeuffeca1bf2015-06-22 16:46:20 +0200284 }
Craig Tillera82950e2015-09-22 12:33:20 -0700285 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200286 return claims;
287
288error:
Craig Tillerbd1795c2016-10-31 15:30:00 -0700289 grpc_jwt_claims_destroy(exec_ctx, claims);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200290 return NULL;
291}
292
Craig Tillera82950e2015-09-22 12:33:20 -0700293grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims,
294 const char *audience) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200295 gpr_timespec skewed_now;
296 int audience_ok;
297
Craig Tillera82950e2015-09-22 12:33:20 -0700298 GPR_ASSERT(claims != NULL);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200299
Craig Tillera82950e2015-09-22 12:33:20 -0700300 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 Boeuffeca1bf2015-06-22 16:46:20 +0200312
Julien Boeuf964d7bb2016-11-17 16:59:48 -0800313 /* 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 Tillera82950e2015-09-22 12:33:20 -0700324 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 Boeuffeca1bf2015-06-22 16:46:20 +0200335 return GRPC_JWT_VERIFIER_OK;
336}
337
338/* --- verifier_cb_ctx object. --- */
339
Craig Tiller4e804942016-06-02 09:33:18 -0700340typedef enum {
341 HTTP_RESPONSE_OPENID = 0,
342 HTTP_RESPONSE_KEYS,
343 HTTP_RESPONSE_COUNT /* must be last */
344} http_response_index;
345
Craig Tillera82950e2015-09-22 12:33:20 -0700346typedef struct {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200347 grpc_jwt_verifier *verifier;
David Garcia Quintas2a50dfe2016-05-31 15:09:12 -0700348 grpc_polling_entity pollent;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200349 jose_header *header;
350 grpc_jwt_claims *claims;
351 char *audience;
Craig Tillerd41a4a72016-10-26 16:16:06 -0700352 grpc_slice signature;
353 grpc_slice signed_data;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200354 void *user_data;
355 grpc_jwt_verification_done_cb user_cb;
Craig Tiller4e804942016-06-02 09:33:18 -0700356 grpc_http_response responses[HTTP_RESPONSE_COUNT];
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200357} verifier_cb_ctx;
358
359/* Takes ownership of the header, claims and signature. */
Craig Tillera82950e2015-09-22 12:33:20 -0700360static verifier_cb_ctx *verifier_cb_ctx_create(
361 grpc_jwt_verifier *verifier, grpc_pollset *pollset, jose_header *header,
Craig Tillerd41a4a72016-10-26 16:16:06 -0700362 grpc_jwt_claims *claims, const char *audience, grpc_slice signature,
Craig Tillera82950e2015-09-22 12:33:20 -0700363 const char *signed_jwt, size_t signed_jwt_len, void *user_data,
364 grpc_jwt_verification_done_cb cb) {
David Garcia Quintas4afce7e2016-04-18 16:25:17 -0700365 grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
Craig Tillera82950e2015-09-22 12:33:20 -0700366 verifier_cb_ctx *ctx = gpr_malloc(sizeof(verifier_cb_ctx));
367 memset(ctx, 0, sizeof(verifier_cb_ctx));
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200368 ctx->verifier = verifier;
David Garcia Quintasc4d51122016-06-06 14:56:02 -0700369 ctx->pollent = grpc_polling_entity_create_from_pollset(pollset);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200370 ctx->header = header;
Craig Tillera82950e2015-09-22 12:33:20 -0700371 ctx->audience = gpr_strdup(audience);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200372 ctx->claims = claims;
373 ctx->signature = signature;
Craig Tillerd41a4a72016-10-26 16:16:06 -0700374 ctx->signed_data = grpc_slice_from_copied_buffer(signed_jwt, signed_jwt_len);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200375 ctx->user_data = user_data;
376 ctx->user_cb = cb;
David Garcia Quintas4afce7e2016-04-18 16:25:17 -0700377 grpc_exec_ctx_finish(&exec_ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200378 return ctx;
379}
380
Craig Tillerbd1795c2016-10-31 15:30:00 -0700381void verifier_cb_ctx_destroy(grpc_exec_ctx *exec_ctx, verifier_cb_ctx *ctx) {
Craig Tillera82950e2015-09-22 12:33:20 -0700382 if (ctx->audience != NULL) gpr_free(ctx->audience);
Craig Tillerbd1795c2016-10-31 15:30:00 -0700383 if (ctx->claims != NULL) grpc_jwt_claims_destroy(exec_ctx, ctx->claims);
Craig Tillera59c16c2016-10-31 07:25:01 -0700384 grpc_slice_unref_internal(exec_ctx, ctx->signature);
385 grpc_slice_unref_internal(exec_ctx, ctx->signed_data);
Craig Tillerbd1795c2016-10-31 15:30:00 -0700386 jose_header_destroy(exec_ctx, ctx->header);
Craig Tiller4e804942016-06-02 09:33:18 -0700387 for (size_t i = 0; i < HTTP_RESPONSE_COUNT; i++) {
Craig Tiller65279fe2016-05-12 16:01:58 -0700388 grpc_http_response_destroy(&ctx->responses[i]);
389 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200390 /* TODO: see what to do with claims... */
Craig Tillera82950e2015-09-22 12:33:20 -0700391 gpr_free(ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200392}
393
394/* --- grpc_jwt_verifier object. --- */
395
396/* Clock skew defaults to one minute. */
Craig Tillera82950e2015-09-22 12:33:20 -0700397gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN};
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200398
399/* Max delay defaults to one minute. */
Craig Tillera82950e2015-09-22 12:33:20 -0700400gpr_timespec grpc_jwt_verifier_max_delay = {60, 0, GPR_TIMESPAN};
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200401
Craig Tillera82950e2015-09-22 12:33:20 -0700402typedef struct {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200403 char *email_domain;
404 char *key_url_prefix;
405} email_key_mapping;
406
Craig Tillera82950e2015-09-22 12:33:20 -0700407struct grpc_jwt_verifier {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200408 email_key_mapping *mappings;
Craig Tillera82950e2015-09-22 12:33:20 -0700409 size_t num_mappings; /* Should be very few, linear search ok. */
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200410 size_t allocated_mappings;
411 grpc_httpcli_context http_ctx;
412};
413
Craig Tillera82950e2015-09-22 12:33:20 -0700414static grpc_json *json_from_http(const grpc_httpcli_response *response) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200415 grpc_json *json = NULL;
416
Craig Tillera82950e2015-09-22 12:33:20 -0700417 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 Boeuffeca1bf2015-06-22 16:46:20 +0200426
Craig Tillera82950e2015-09-22 12:33:20 -0700427 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 Boeuffeca1bf2015-06-22 16:46:20 +0200431 return json;
432}
433
Craig Tillera82950e2015-09-22 12:33:20 -0700434static const grpc_json *find_property_by_name(const grpc_json *json,
435 const char *name) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200436 const grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700437 for (cur = json->child; cur != NULL; cur = cur->next) {
438 if (strcmp(cur->key, name) == 0) return cur;
439 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200440 return NULL;
441}
442
Craig Tillera82950e2015-09-22 12:33:20 -0700443static EVP_PKEY *extract_pkey_from_x509(const char *x509_str) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200444 X509 *x509 = NULL;
445 EVP_PKEY *result = NULL;
Craig Tillera82950e2015-09-22 12:33:20 -0700446 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 Boeuffeca1bf2015-06-22 16:46:20 +0200459
460end:
Craig Tillera82950e2015-09-22 12:33:20 -0700461 BIO_free(bio);
462 if (x509 != NULL) X509_free(x509);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200463 return result;
464}
465
Craig Tillerbd1795c2016-10-31 15:30:00 -0700466static BIGNUM *bignum_from_base64(grpc_exec_ctx *exec_ctx, const char *b64) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200467 BIGNUM *result = NULL;
Craig Tillerd41a4a72016-10-26 16:16:06 -0700468 grpc_slice bin;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200469
Craig Tillera82950e2015-09-22 12:33:20 -0700470 if (b64 == NULL) return NULL;
Craig Tiller87a7e1f2016-11-09 09:42:19 -0800471 bin = grpc_base64_decode(exec_ctx, b64, 1);
Craig Tiller618e67d2016-10-26 21:08:10 -0700472 if (GRPC_SLICE_IS_EMPTY(bin)) {
Craig Tillera82950e2015-09-22 12:33:20 -0700473 gpr_log(GPR_ERROR, "Invalid base64 for big num.");
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200474 return NULL;
Craig Tillera82950e2015-09-22 12:33:20 -0700475 }
Craig Tiller618e67d2016-10-26 21:08:10 -0700476 result = BN_bin2bn(GRPC_SLICE_START_PTR(bin),
477 TSI_SIZE_AS_SIZE(GRPC_SLICE_LENGTH(bin)), NULL);
Craig Tillera59c16c2016-10-31 07:25:01 -0700478 grpc_slice_unref_internal(exec_ctx, bin);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200479 return result;
480}
481
Craig Tillerbd1795c2016-10-31 15:30:00 -0700482static EVP_PKEY *pkey_from_jwk(grpc_exec_ctx *exec_ctx, const grpc_json *json,
483 const char *kty) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200484 const grpc_json *key_prop;
485 RSA *rsa = NULL;
486 EVP_PKEY *result = NULL;
487
Craig Tillera82950e2015-09-22 12:33:20 -0700488 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 Tillerbd1795c2016-10-31 15:30:00 -0700500 rsa->n =
501 bignum_from_base64(exec_ctx, validate_string_field(key_prop, "n"));
Craig Tillera82950e2015-09-22 12:33:20 -0700502 if (rsa->n == NULL) goto end;
503 } else if (strcmp(key_prop->key, "e") == 0) {
Craig Tillerbd1795c2016-10-31 15:30:00 -0700504 rsa->e =
505 bignum_from_base64(exec_ctx, validate_string_field(key_prop, "e"));
Craig Tillera82950e2015-09-22 12:33:20 -0700506 if (rsa->e == NULL) goto end;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200507 }
Craig Tillera82950e2015-09-22 12:33:20 -0700508 }
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 Boeuffeca1bf2015-06-22 16:46:20 +0200515
516end:
Craig Tillera82950e2015-09-22 12:33:20 -0700517 if (rsa != NULL) RSA_free(rsa);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200518 return result;
519}
520
Craig Tillerbd1795c2016-10-31 15:30:00 -0700521static EVP_PKEY *find_verification_key(grpc_exec_ctx *exec_ctx,
522 const grpc_json *json,
Craig Tillera82950e2015-09-22 12:33:20 -0700523 const char *header_alg,
524 const char *header_kid) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200525 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 Tillera82950e2015-09-22 12:33:20 -0700529 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 Boeuffeca1bf2015-06-22 16:46:20 +0200537
Craig Tillera82950e2015-09-22 12:33:20 -0700538 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 Boeuffeca1bf2015-06-22 16:46:20 +0200543 /* Key format is specified in:
544 https://tools.ietf.org/html/rfc7518#section-6. */
Craig Tillera82950e2015-09-22 12:33:20 -0700545 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 Boeuffeca1bf2015-06-22 16:46:20 +0200550
Craig Tillera82950e2015-09-22 12:33:20 -0700551 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 Boeuffeca1bf2015-06-22 16:46:20 +0200563 }
Craig Tillera82950e2015-09-22 12:33:20 -0700564 if (alg != NULL && kid != NULL && kty != NULL &&
565 strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) {
Craig Tillerbd1795c2016-10-31 15:30:00 -0700566 return pkey_from_jwk(exec_ctx, jkey, kty);
Craig Tillera82950e2015-09-22 12:33:20 -0700567 }
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 Boeuffeca1bf2015-06-22 16:46:20 +0200572 return NULL;
573}
574
Craig Tillera82950e2015-09-22 12:33:20 -0700575static int verify_jwt_signature(EVP_PKEY *key, const char *alg,
Craig Tillerd41a4a72016-10-26 16:16:06 -0700576 grpc_slice signature, grpc_slice signed_data) {
Craig Tillera82950e2015-09-22 12:33:20 -0700577 EVP_MD_CTX *md_ctx = EVP_MD_CTX_create();
578 const EVP_MD *md = evp_md_from_alg(alg);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200579 int result = 0;
580
Craig Tillera82950e2015-09-22 12:33:20 -0700581 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 Tiller618e67d2016-10-26 21:08:10 -0700590 if (EVP_DigestVerifyUpdate(md_ctx, GRPC_SLICE_START_PTR(signed_data),
591 GRPC_SLICE_LENGTH(signed_data)) != 1) {
Craig Tillera82950e2015-09-22 12:33:20 -0700592 gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed.");
593 goto end;
594 }
Craig Tiller618e67d2016-10-26 21:08:10 -0700595 if (EVP_DigestVerifyFinal(md_ctx, GRPC_SLICE_START_PTR(signature),
596 GRPC_SLICE_LENGTH(signature)) != 1) {
Craig Tillera82950e2015-09-22 12:33:20 -0700597 gpr_log(GPR_ERROR, "JWT signature verification failed.");
598 goto end;
599 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200600 result = 1;
601
602end:
Craig Tillera82950e2015-09-22 12:33:20 -0700603 if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200604 return result;
605}
606
Craig Tillera82950e2015-09-22 12:33:20 -0700607static void on_keys_retrieved(grpc_exec_ctx *exec_ctx, void *user_data,
Craig Tiller804ff712016-05-05 16:25:40 -0700608 grpc_error *error) {
Craig Tillera82950e2015-09-22 12:33:20 -0700609 verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
Craig Tiller4e804942016-06-02 09:33:18 -0700610 grpc_json *json = json_from_http(&ctx->responses[HTTP_RESPONSE_KEYS]);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200611 EVP_PKEY *verification_key = NULL;
612 grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR;
613 grpc_jwt_claims *claims = NULL;
614
Craig Tillera82950e2015-09-22 12:33:20 -0700615 if (json == NULL) {
616 status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
617 goto end;
618 }
619 verification_key =
Craig Tillerbd1795c2016-10-31 15:30:00 -0700620 find_verification_key(exec_ctx, json, ctx->header->alg, ctx->header->kid);
Craig Tillera82950e2015-09-22 12:33:20 -0700621 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 Boeuffeca1bf2015-06-22 16:46:20 +0200627
Craig Tillera82950e2015-09-22 12:33:20 -0700628 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 Boeuffeca1bf2015-06-22 16:46:20 +0200633
Craig Tillera82950e2015-09-22 12:33:20 -0700634 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 Boeuffeca1bf2015-06-22 16:46:20 +0200640
641end:
Craig Tillera82950e2015-09-22 12:33:20 -0700642 if (json != NULL) grpc_json_destroy(json);
643 if (verification_key != NULL) EVP_PKEY_free(verification_key);
Craig Tiller3cf79222016-11-14 08:02:45 -0800644 ctx->user_cb(exec_ctx, ctx->user_data, status, claims);
Craig Tillerbd1795c2016-10-31 15:30:00 -0700645 verifier_cb_ctx_destroy(exec_ctx, ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200646}
647
Craig Tillera82950e2015-09-22 12:33:20 -0700648static void on_openid_config_retrieved(grpc_exec_ctx *exec_ctx, void *user_data,
Craig Tiller804ff712016-05-05 16:25:40 -0700649 grpc_error *error) {
Craig Tiller5cc175f2015-07-09 08:48:35 -0700650 const grpc_json *cur;
Craig Tillera82950e2015-09-22 12:33:20 -0700651 verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
Craig Tiller4e804942016-06-02 09:33:18 -0700652 const grpc_http_response *response = &ctx->responses[HTTP_RESPONSE_OPENID];
Craig Tiller804ff712016-05-05 16:25:40 -0700653 grpc_json *json = json_from_http(response);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200654 grpc_httpcli_request req;
655 const char *jwks_uri;
656
Craig Tiller45724b32015-09-22 10:42:19 -0700657 /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */
Craig Tillera82950e2015-09-22 12:33:20 -0700658 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 Boeuffeca1bf2015-06-22 16:46:20 +0200662 goto error;
Craig Tillera82950e2015-09-22 12:33:20 -0700663 }
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 Boeuffeca1bf2015-06-22 16:46:20 +0200668 goto error;
Craig Tillera82950e2015-09-22 12:33:20 -0700669 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200670 jwks_uri += 8;
Craig Tillerf53d9c82015-08-04 14:19:43 -0700671 req.handshaker = &grpc_httpcli_ssl;
Craig Tillera82950e2015-09-22 12:33:20 -0700672 req.host = gpr_strdup(jwks_uri);
Matthew Iselin1824f052016-02-10 12:16:06 +1100673 req.http.path = strchr(jwks_uri, '/');
674 if (req.http.path == NULL) {
675 req.http.path = "";
Craig Tillera82950e2015-09-22 12:33:20 -0700676 } else {
Matthew Iselin1824f052016-02-10 12:16:06 +1100677 *(req.host + (req.http.path - jwks_uri)) = '\0';
Craig Tillera82950e2015-09-22 12:33:20 -0700678 }
Craig Tiller65279fe2016-05-12 16:01:58 -0700679
Craig Tiller20afa3d2016-10-17 14:52:14 -0700680 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
Craig Tiller45881862016-09-23 10:48:53 -0700681 channel. This would allow us to cancel an authentication query when under
682 extreme memory pressure. */
Craig Tillerafcc8752016-10-18 16:10:06 -0700683 grpc_resource_quota *resource_quota =
684 grpc_resource_quota_create("jwt_verifier");
Craig Tillera82950e2015-09-22 12:33:20 -0700685 grpc_httpcli_get(
Craig Tiller20afa3d2016-10-17 14:52:14 -0700686 exec_ctx, &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req,
Craig Tillera82950e2015-09-22 12:33:20 -0700687 gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay),
Craig Tiller91031da2016-12-28 15:44:25 -0800688 grpc_closure_create(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx),
Craig Tiller4e804942016-06-02 09:33:18 -0700689 &ctx->responses[HTTP_RESPONSE_KEYS]);
Craig Tillera59c16c2016-10-31 07:25:01 -0700690 grpc_resource_quota_unref_internal(exec_ctx, resource_quota);
Craig Tillera82950e2015-09-22 12:33:20 -0700691 grpc_json_destroy(json);
692 gpr_free(req.host);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200693 return;
694
695error:
Craig Tillera82950e2015-09-22 12:33:20 -0700696 if (json != NULL) grpc_json_destroy(json);
Craig Tiller3cf79222016-11-14 08:02:45 -0800697 ctx->user_cb(exec_ctx, ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR,
698 NULL);
Craig Tillerbd1795c2016-10-31 15:30:00 -0700699 verifier_cb_ctx_destroy(exec_ctx, ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200700}
701
Craig Tillera82950e2015-09-22 12:33:20 -0700702static email_key_mapping *verifier_get_mapping(grpc_jwt_verifier *v,
703 const char *email_domain) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200704 size_t i;
Craig Tillera82950e2015-09-22 12:33:20 -0700705 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 Boeuffeca1bf2015-06-22 16:46:20 +0200709 }
Craig Tillera82950e2015-09-22 12:33:20 -0700710 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200711 return NULL;
712}
713
Craig Tillera82950e2015-09-22 12:33:20 -0700714static 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 Boeuffeca1bf2015-06-22 16:46:20 +0200725 v->num_mappings++;
Craig Tillera82950e2015-09-22 12:33:20 -0700726 GPR_ASSERT(v->num_mappings <= v->allocated_mappings);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200727}
728
Julien Boeuf964d7bb2016-11-17 16:59:48 -0800729/* Very non-sophisticated way to detect an email address. Should be good
730 enough for now... */
731const 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 Boeuffeca1bf2015-06-22 16:46:20 +0200745/* Takes ownership of ctx. */
Craig Tillera82950e2015-09-22 12:33:20 -0700746static void retrieve_key_and_verify(grpc_exec_ctx *exec_ctx,
747 verifier_cb_ctx *ctx) {
Julien Boeuf964d7bb2016-11-17 16:59:48 -0800748 const char *email_domain;
Craig Tiller804ff712016-05-05 16:25:40 -0700749 grpc_closure *http_cb;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200750 char *path_prefix = NULL;
751 const char *iss;
752 grpc_httpcli_request req;
Craig Tillera82950e2015-09-22 12:33:20 -0700753 memset(&req, 0, sizeof(grpc_httpcli_request));
Craig Tillerf53d9c82015-08-04 14:19:43 -0700754 req.handshaker = &grpc_httpcli_ssl;
Craig Tiller4e804942016-06-02 09:33:18 -0700755 http_response_index rsp_idx;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200756
Craig Tillera82950e2015-09-22 12:33:20 -0700757 GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200758 iss = ctx->claims->iss;
Craig Tillera82950e2015-09-22 12:33:20 -0700759 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 Boeuffeca1bf2015-06-22 16:46:20 +0200767
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 Boeuf964d7bb2016-11-17 16:59:48 -0800773 email_domain = grpc_jwt_issuer_email_domain(iss);
774 if (email_domain != NULL) {
Craig Tillera82950e2015-09-22 12:33:20 -0700775 email_key_mapping *mapping;
Craig Tillera82950e2015-09-22 12:33:20 -0700776 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 Boeuffeca1bf2015-06-22 16:46:20 +0200781 }
Craig Tillera82950e2015-09-22 12:33:20 -0700782 req.host = gpr_strdup(mapping->key_url_prefix);
783 path_prefix = strchr(req.host, '/');
784 if (path_prefix == NULL) {
Matthew Iselin1824f052016-02-10 12:16:06 +1100785 gpr_asprintf(&req.http.path, "/%s", iss);
Craig Tillera82950e2015-09-22 12:33:20 -0700786 } else {
787 *(path_prefix++) = '\0';
Matthew Iselin1824f052016-02-10 12:16:06 +1100788 gpr_asprintf(&req.http.path, "/%s/%s", path_prefix, iss);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200789 }
Craig Tiller91031da2016-12-28 15:44:25 -0800790 http_cb =
791 grpc_closure_create(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx);
Craig Tiller4e804942016-06-02 09:33:18 -0700792 rsp_idx = HTTP_RESPONSE_KEYS;
Craig Tillera82950e2015-09-22 12:33:20 -0700793 } 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 Iselin1824f052016-02-10 12:16:06 +1100797 req.http.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX);
Craig Tillera82950e2015-09-22 12:33:20 -0700798 } else {
799 *(path_prefix++) = 0;
Matthew Iselin1824f052016-02-10 12:16:06 +1100800 gpr_asprintf(&req.http.path, "/%s%s", path_prefix,
Craig Tillera82950e2015-09-22 12:33:20 -0700801 GRPC_OPENID_CONFIG_URL_SUFFIX);
802 }
Craig Tiller91031da2016-12-28 15:44:25 -0800803 http_cb = grpc_closure_create(on_openid_config_retrieved, ctx,
804 grpc_schedule_on_exec_ctx);
Craig Tiller4e804942016-06-02 09:33:18 -0700805 rsp_idx = HTTP_RESPONSE_OPENID;
Craig Tillera82950e2015-09-22 12:33:20 -0700806 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200807
Craig Tiller20afa3d2016-10-17 14:52:14 -0700808 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
Craig Tiller45881862016-09-23 10:48:53 -0700809 channel. This would allow us to cancel an authentication query when under
810 extreme memory pressure. */
Craig Tillerafcc8752016-10-18 16:10:06 -0700811 grpc_resource_quota *resource_quota =
812 grpc_resource_quota_create("jwt_verifier");
Craig Tillera82950e2015-09-22 12:33:20 -0700813 grpc_httpcli_get(
Craig Tiller20afa3d2016-10-17 14:52:14 -0700814 exec_ctx, &ctx->verifier->http_ctx, &ctx->pollent, resource_quota, &req,
Craig Tillera82950e2015-09-22 12:33:20 -0700815 gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay),
Craig Tiller65279fe2016-05-12 16:01:58 -0700816 http_cb, &ctx->responses[rsp_idx]);
Craig Tillera59c16c2016-10-31 07:25:01 -0700817 grpc_resource_quota_unref_internal(exec_ctx, resource_quota);
Craig Tillera82950e2015-09-22 12:33:20 -0700818 gpr_free(req.host);
Matthew Iselin1824f052016-02-10 12:16:06 +1100819 gpr_free(req.http.path);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200820 return;
821
822error:
Craig Tiller3cf79222016-11-14 08:02:45 -0800823 ctx->user_cb(exec_ctx, ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR,
824 NULL);
Craig Tillerbd1795c2016-10-31 15:30:00 -0700825 verifier_cb_ctx_destroy(exec_ctx, ctx);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200826}
827
Craig Tillera82950e2015-09-22 12:33:20 -0700828void 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 Boeuffeca1bf2015-06-22 16:46:20 +0200834 const char *dot = NULL;
835 grpc_json *json;
836 jose_header *header = NULL;
837 grpc_jwt_claims *claims = NULL;
Craig Tillerd41a4a72016-10-26 16:16:06 -0700838 grpc_slice header_buffer;
839 grpc_slice claims_buffer;
840 grpc_slice signature;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200841 size_t signed_jwt_len;
842 const char *cur = jwt;
843
Craig Tillera82950e2015-09-22 12:33:20 -0700844 GPR_ASSERT(verifier != NULL && jwt != NULL && audience != NULL && cb != NULL);
845 dot = strchr(cur, '.');
846 if (dot == NULL) goto error;
Craig Tillerbd1795c2016-10-31 15:30:00 -0700847 json = parse_json_part_from_jwt(exec_ctx, cur, (size_t)(dot - cur),
848 &header_buffer);
Craig Tillera82950e2015-09-22 12:33:20 -0700849 if (json == NULL) goto error;
Craig Tillerbd1795c2016-10-31 15:30:00 -0700850 header = jose_header_from_json(exec_ctx, json, header_buffer);
Craig Tillera82950e2015-09-22 12:33:20 -0700851 if (header == NULL) goto error;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200852
853 cur = dot + 1;
Craig Tillera82950e2015-09-22 12:33:20 -0700854 dot = strchr(cur, '.');
855 if (dot == NULL) goto error;
Craig Tillerbd1795c2016-10-31 15:30:00 -0700856 json = parse_json_part_from_jwt(exec_ctx, cur, (size_t)(dot - cur),
857 &claims_buffer);
Craig Tillera82950e2015-09-22 12:33:20 -0700858 if (json == NULL) goto error;
Craig Tillerbd1795c2016-10-31 15:30:00 -0700859 claims = grpc_jwt_claims_from_json(exec_ctx, json, claims_buffer);
Craig Tillera82950e2015-09-22 12:33:20 -0700860 if (claims == NULL) goto error;
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200861
Craig Tillera82950e2015-09-22 12:33:20 -0700862 signed_jwt_len = (size_t)(dot - jwt);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200863 cur = dot + 1;
Craig Tiller87a7e1f2016-11-09 09:42:19 -0800864 signature = grpc_base64_decode(exec_ctx, cur, 1);
Craig Tiller618e67d2016-10-26 21:08:10 -0700865 if (GRPC_SLICE_IS_EMPTY(signature)) goto error;
Craig Tillera82950e2015-09-22 12:33:20 -0700866 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 Boeuffeca1bf2015-06-22 16:46:20 +0200870 return;
871
872error:
Craig Tillerbd1795c2016-10-31 15:30:00 -0700873 if (header != NULL) jose_header_destroy(exec_ctx, header);
874 if (claims != NULL) grpc_jwt_claims_destroy(exec_ctx, claims);
Craig Tiller3cf79222016-11-14 08:02:45 -0800875 cb(exec_ctx, user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, NULL);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200876}
877
Craig Tillera82950e2015-09-22 12:33:20 -0700878grpc_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 Boeuffeca1bf2015-06-22 16:46:20 +0200884
885 /* We know at least of one mapping. */
886 v->allocated_mappings = 1 + num_mappings;
Craig Tillera82950e2015-09-22 12:33:20 -0700887 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 Boeuffeca1bf2015-06-22 16:46:20 +0200890 /* User-Provided mappings. */
Craig Tillera82950e2015-09-22 12:33:20 -0700891 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 Boeuffeca1bf2015-06-22 16:46:20 +0200896 }
Craig Tillera82950e2015-09-22 12:33:20 -0700897 }
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200898 return v;
899}
900
Craig Tiller9e5ac1b2017-02-14 22:25:50 -0800901void grpc_jwt_verifier_destroy(grpc_exec_ctx *exec_ctx, grpc_jwt_verifier *v) {
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200902 size_t i;
Craig Tillera82950e2015-09-22 12:33:20 -0700903 if (v == NULL) return;
Craig Tiller9e5ac1b2017-02-14 22:25:50 -0800904 grpc_httpcli_context_destroy(exec_ctx, &v->http_ctx);
Craig Tillera82950e2015-09-22 12:33:20 -0700905 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 Boeuffeca1bf2015-06-22 16:46:20 +0200909 }
Craig Tillera82950e2015-09-22 12:33:20 -0700910 gpr_free(v->mappings);
911 }
912 gpr_free(v);
Julien Boeuffeca1bf2015-06-22 16:46:20 +0200913}