Line data Source code
1 : /*
2 : *
3 : * Copyright 2015, Google Inc.
4 : * All rights reserved.
5 : *
6 : * Redistribution and use in source and binary forms, with or without
7 : * modification, are permitted provided that the following conditions are
8 : * met:
9 : *
10 : * * Redistributions of source code must retain the above copyright
11 : * notice, this list of conditions and the following disclaimer.
12 : * * Redistributions in binary form must reproduce the above
13 : * copyright notice, this list of conditions and the following disclaimer
14 : * in the documentation and/or other materials provided with the
15 : * distribution.
16 : * * Neither the name of Google Inc. nor the names of its
17 : * contributors may be used to endorse or promote products derived from
18 : * this software without specific prior written permission.
19 : *
20 : * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 : * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 : * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 : * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 : * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 : * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 : * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 : * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 : *
32 : */
33 :
34 : #include "src/core/security/json_token.h"
35 :
36 : #include <string.h>
37 :
38 : #include <grpc/support/alloc.h>
39 : #include <grpc/support/log.h>
40 : #include <grpc/support/string_util.h>
41 :
42 : #include "src/core/security/base64.h"
43 : #include "src/core/support/string.h"
44 :
45 : #include <openssl/bio.h>
46 : #include <openssl/evp.h>
47 : #include <openssl/pem.h>
48 :
49 : /* --- Constants. --- */
50 :
51 : /* 1 hour max. */
52 : const gpr_timespec grpc_max_auth_token_lifetime = {3600, 0, GPR_TIMESPAN};
53 :
54 : #define GRPC_JWT_RSA_SHA256_ALGORITHM "RS256"
55 : #define GRPC_JWT_TYPE "JWT"
56 :
57 : /* --- Override for testing. --- */
58 :
59 : static grpc_jwt_encode_and_sign_override g_jwt_encode_and_sign_override = NULL;
60 :
61 : /* --- grpc_auth_json_key. --- */
62 :
63 102 : static const char *json_get_string_property(const grpc_json *json,
64 : const char *prop_name) {
65 : grpc_json *child;
66 307 : for (child = json->child; child != NULL; child = child->next) {
67 298 : if (strcmp(child->key, prop_name) == 0) break;
68 : }
69 102 : if (child == NULL || child->type != GRPC_JSON_STRING) {
70 9 : gpr_log(GPR_ERROR, "Invalid or missing %s property.", prop_name);
71 9 : return NULL;
72 : }
73 93 : return child->value;
74 : }
75 :
76 63 : static int set_json_key_string_property(const grpc_json *json,
77 : const char *prop_name,
78 : char **json_key_field) {
79 63 : const char *prop_value = json_get_string_property(json, prop_name);
80 63 : if (prop_value == NULL) return 0;
81 57 : *json_key_field = gpr_strdup(prop_value);
82 57 : return 1;
83 : }
84 :
85 21 : int grpc_auth_json_key_is_valid(const grpc_auth_json_key *json_key) {
86 42 : return (json_key != NULL) &&
87 21 : strcmp(json_key->type, GRPC_AUTH_JSON_TYPE_INVALID);
88 : }
89 :
90 19 : grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json *json) {
91 : grpc_auth_json_key result;
92 19 : BIO *bio = NULL;
93 : const char *prop_value;
94 19 : int success = 0;
95 :
96 19 : memset(&result, 0, sizeof(grpc_auth_json_key));
97 19 : result.type = GRPC_AUTH_JSON_TYPE_INVALID;
98 19 : if (json == NULL) {
99 1 : gpr_log(GPR_ERROR, "Invalid json.");
100 1 : goto end;
101 : }
102 :
103 18 : prop_value = json_get_string_property(json, "type");
104 35 : if (prop_value == NULL ||
105 17 : strcmp(prop_value, GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT)) {
106 : goto end;
107 : }
108 16 : result.type = GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT;
109 :
110 16 : if (!set_json_key_string_property(json, "private_key_id",
111 15 : &result.private_key_id) ||
112 29 : !set_json_key_string_property(json, "client_id", &result.client_id) ||
113 14 : !set_json_key_string_property(json, "client_email",
114 : &result.client_email)) {
115 : goto end;
116 : }
117 :
118 13 : prop_value = json_get_string_property(json, "private_key");
119 13 : if (prop_value == NULL) {
120 1 : goto end;
121 : }
122 12 : bio = BIO_new(BIO_s_mem());
123 12 : success = BIO_puts(bio, prop_value);
124 12 : if ((success < 0) || ((size_t)success != strlen(prop_value))) {
125 0 : gpr_log(GPR_ERROR, "Could not write into openssl BIO.");
126 0 : goto end;
127 : }
128 12 : result.private_key = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, "");
129 12 : if (result.private_key == NULL) {
130 0 : gpr_log(GPR_ERROR, "Could not deserialize private key.");
131 0 : goto end;
132 : }
133 12 : success = 1;
134 :
135 : end:
136 19 : if (bio != NULL) BIO_free(bio);
137 19 : if (!success) grpc_auth_json_key_destruct(&result);
138 19 : return result;
139 : }
140 :
141 17 : grpc_auth_json_key grpc_auth_json_key_create_from_string(
142 : const char *json_string) {
143 17 : char *scratchpad = gpr_strdup(json_string);
144 17 : grpc_json *json = grpc_json_parse_string(scratchpad);
145 17 : grpc_auth_json_key result = grpc_auth_json_key_create_from_json(json);
146 17 : if (json != NULL) grpc_json_destroy(json);
147 17 : gpr_free(scratchpad);
148 17 : return result;
149 : }
150 :
151 25 : void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key) {
152 50 : if (json_key == NULL) return;
153 25 : json_key->type = GRPC_AUTH_JSON_TYPE_INVALID;
154 25 : if (json_key->client_id != NULL) {
155 14 : gpr_free(json_key->client_id);
156 14 : json_key->client_id = NULL;
157 : }
158 25 : if (json_key->private_key_id != NULL) {
159 15 : gpr_free(json_key->private_key_id);
160 15 : json_key->private_key_id = NULL;
161 : }
162 25 : if (json_key->client_email != NULL) {
163 13 : gpr_free(json_key->client_email);
164 13 : json_key->client_email = NULL;
165 : }
166 25 : if (json_key->private_key != NULL) {
167 12 : RSA_free(json_key->private_key);
168 12 : json_key->private_key = NULL;
169 : }
170 : }
171 :
172 : /* --- jwt encoding and signature. --- */
173 :
174 64 : static grpc_json *create_child(grpc_json *brother, grpc_json *parent,
175 : const char *key, const char *value,
176 : grpc_json_type type) {
177 64 : grpc_json *child = grpc_json_create(type);
178 64 : if (brother) brother->next = child;
179 64 : if (!parent->child) parent->child = child;
180 64 : child->parent = parent;
181 64 : child->value = value;
182 64 : child->key = key;
183 64 : return child;
184 : }
185 :
186 8 : static char *encoded_jwt_header(const char *key_id, const char *algorithm) {
187 8 : grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT);
188 8 : grpc_json *child = NULL;
189 8 : char *json_str = NULL;
190 8 : char *result = NULL;
191 :
192 8 : child = create_child(NULL, json, "alg", algorithm, GRPC_JSON_STRING);
193 8 : child = create_child(child, json, "typ", GRPC_JWT_TYPE, GRPC_JSON_STRING);
194 8 : create_child(child, json, "kid", key_id, GRPC_JSON_STRING);
195 :
196 8 : json_str = grpc_json_dump_to_string(json, 0);
197 8 : result = grpc_base64_encode(json_str, strlen(json_str), 1, 0);
198 8 : gpr_free(json_str);
199 8 : grpc_json_destroy(json);
200 8 : return result;
201 : }
202 :
203 8 : static char *encoded_jwt_claim(const grpc_auth_json_key *json_key,
204 : const char *audience,
205 : gpr_timespec token_lifetime, const char *scope) {
206 8 : grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT);
207 8 : grpc_json *child = NULL;
208 8 : char *json_str = NULL;
209 8 : char *result = NULL;
210 8 : gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
211 8 : gpr_timespec expiration = gpr_time_add(now, token_lifetime);
212 : char now_str[GPR_LTOA_MIN_BUFSIZE];
213 : char expiration_str[GPR_LTOA_MIN_BUFSIZE];
214 8 : if (gpr_time_cmp(token_lifetime, grpc_max_auth_token_lifetime) > 0) {
215 0 : gpr_log(GPR_INFO, "Cropping token lifetime to maximum allowed value.");
216 0 : expiration = gpr_time_add(now, grpc_max_auth_token_lifetime);
217 : }
218 8 : gpr_int64toa(now.tv_sec, now_str);
219 8 : gpr_int64toa(expiration.tv_sec, expiration_str);
220 :
221 8 : child =
222 8 : create_child(NULL, json, "iss", json_key->client_email, GRPC_JSON_STRING);
223 8 : if (scope != NULL) {
224 1 : child = create_child(child, json, "scope", scope, GRPC_JSON_STRING);
225 : } else {
226 : /* Unscoped JWTs need a sub field. */
227 7 : child = create_child(child, json, "sub", json_key->client_email,
228 : GRPC_JSON_STRING);
229 : }
230 :
231 8 : child = create_child(child, json, "aud", audience, GRPC_JSON_STRING);
232 8 : child = create_child(child, json, "iat", now_str, GRPC_JSON_NUMBER);
233 8 : create_child(child, json, "exp", expiration_str, GRPC_JSON_NUMBER);
234 :
235 8 : json_str = grpc_json_dump_to_string(json, 0);
236 8 : result = grpc_base64_encode(json_str, strlen(json_str), 1, 0);
237 8 : gpr_free(json_str);
238 8 : grpc_json_destroy(json);
239 8 : return result;
240 : }
241 :
242 16 : static char *dot_concat_and_free_strings(char *str1, char *str2) {
243 16 : size_t str1_len = strlen(str1);
244 16 : size_t str2_len = strlen(str2);
245 16 : size_t result_len = str1_len + 1 /* dot */ + str2_len;
246 16 : char *result = gpr_malloc(result_len + 1 /* NULL terminated */);
247 16 : char *current = result;
248 16 : memcpy(current, str1, str1_len);
249 16 : current += str1_len;
250 16 : *(current++) = '.';
251 16 : memcpy(current, str2, str2_len);
252 16 : current += str2_len;
253 16 : GPR_ASSERT(current >= result);
254 16 : GPR_ASSERT((gpr_uintptr)(current - result) == result_len);
255 16 : *current = '\0';
256 16 : gpr_free(str1);
257 16 : gpr_free(str2);
258 16 : return result;
259 : }
260 :
261 8 : const EVP_MD *openssl_digest_from_algorithm(const char *algorithm) {
262 8 : if (strcmp(algorithm, GRPC_JWT_RSA_SHA256_ALGORITHM) == 0) {
263 8 : return EVP_sha256();
264 : } else {
265 0 : gpr_log(GPR_ERROR, "Unknown algorithm %s.", algorithm);
266 0 : return NULL;
267 : }
268 : }
269 :
270 8 : char *compute_and_encode_signature(const grpc_auth_json_key *json_key,
271 : const char *signature_algorithm,
272 : const char *to_sign) {
273 8 : const EVP_MD *md = openssl_digest_from_algorithm(signature_algorithm);
274 8 : EVP_MD_CTX *md_ctx = NULL;
275 8 : EVP_PKEY *key = EVP_PKEY_new();
276 8 : size_t sig_len = 0;
277 8 : unsigned char *sig = NULL;
278 8 : char *result = NULL;
279 8 : if (md == NULL) return NULL;
280 8 : md_ctx = EVP_MD_CTX_create();
281 8 : if (md_ctx == NULL) {
282 0 : gpr_log(GPR_ERROR, "Could not create MD_CTX");
283 0 : goto end;
284 : }
285 8 : EVP_PKEY_set1_RSA(key, json_key->private_key);
286 8 : if (EVP_DigestSignInit(md_ctx, NULL, md, NULL, key) != 1) {
287 0 : gpr_log(GPR_ERROR, "DigestInit failed.");
288 0 : goto end;
289 : }
290 8 : if (EVP_DigestSignUpdate(md_ctx, to_sign, strlen(to_sign)) != 1) {
291 0 : gpr_log(GPR_ERROR, "DigestUpdate failed.");
292 0 : goto end;
293 : }
294 8 : if (EVP_DigestSignFinal(md_ctx, NULL, &sig_len) != 1) {
295 0 : gpr_log(GPR_ERROR, "DigestFinal (get signature length) failed.");
296 0 : goto end;
297 : }
298 8 : sig = gpr_malloc(sig_len);
299 8 : if (EVP_DigestSignFinal(md_ctx, sig, &sig_len) != 1) {
300 0 : gpr_log(GPR_ERROR, "DigestFinal (signature compute) failed.");
301 0 : goto end;
302 : }
303 8 : result = grpc_base64_encode(sig, sig_len, 1, 0);
304 :
305 : end:
306 8 : if (key != NULL) EVP_PKEY_free(key);
307 8 : if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
308 8 : if (sig != NULL) gpr_free(sig);
309 8 : return result;
310 : }
311 :
312 11 : char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key,
313 : const char *audience,
314 : gpr_timespec token_lifetime, const char *scope) {
315 11 : if (g_jwt_encode_and_sign_override != NULL) {
316 3 : return g_jwt_encode_and_sign_override(json_key, audience, token_lifetime,
317 : scope);
318 : } else {
319 8 : const char *sig_algo = GRPC_JWT_RSA_SHA256_ALGORITHM;
320 16 : char *to_sign = dot_concat_and_free_strings(
321 8 : encoded_jwt_header(json_key->private_key_id, sig_algo),
322 : encoded_jwt_claim(json_key, audience, token_lifetime, scope));
323 8 : char *sig = compute_and_encode_signature(json_key, sig_algo, to_sign);
324 8 : if (sig == NULL) {
325 0 : gpr_free(to_sign);
326 0 : return NULL;
327 : }
328 8 : return dot_concat_and_free_strings(to_sign, sig);
329 : }
330 : }
331 :
332 6 : void grpc_jwt_encode_and_sign_set_override(
333 : grpc_jwt_encode_and_sign_override func) {
334 6 : g_jwt_encode_and_sign_override = func;
335 6 : }
336 :
337 : /* --- grpc_auth_refresh_token --- */
338 :
339 10 : int grpc_auth_refresh_token_is_valid(
340 : const grpc_auth_refresh_token *refresh_token) {
341 20 : return (refresh_token != NULL) &&
342 10 : strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID);
343 : }
344 :
345 9 : grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
346 : const grpc_json *json) {
347 : grpc_auth_refresh_token result;
348 : const char *prop_value;
349 9 : int success = 0;
350 :
351 9 : memset(&result, 0, sizeof(grpc_auth_refresh_token));
352 9 : result.type = GRPC_AUTH_JSON_TYPE_INVALID;
353 9 : if (json == NULL) {
354 1 : gpr_log(GPR_ERROR, "Invalid json.");
355 1 : goto end;
356 : }
357 :
358 8 : prop_value = json_get_string_property(json, "type");
359 15 : if (prop_value == NULL ||
360 7 : strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) {
361 : goto end;
362 : }
363 7 : result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER;
364 :
365 7 : if (!set_json_key_string_property(json, "client_secret",
366 6 : &result.client_secret) ||
367 11 : !set_json_key_string_property(json, "client_id", &result.client_id) ||
368 5 : !set_json_key_string_property(json, "refresh_token",
369 : &result.refresh_token)) {
370 : goto end;
371 : }
372 4 : success = 1;
373 :
374 : end:
375 9 : if (!success) grpc_auth_refresh_token_destruct(&result);
376 9 : return result;
377 : }
378 :
379 8 : grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string(
380 : const char *json_string) {
381 8 : char *scratchpad = gpr_strdup(json_string);
382 8 : grpc_json *json = grpc_json_parse_string(scratchpad);
383 8 : grpc_auth_refresh_token result =
384 : grpc_auth_refresh_token_create_from_json(json);
385 8 : if (json != NULL) grpc_json_destroy(json);
386 8 : gpr_free(scratchpad);
387 8 : return result;
388 : }
389 :
390 9 : void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token *refresh_token) {
391 18 : if (refresh_token == NULL) return;
392 9 : refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID;
393 9 : if (refresh_token->client_id != NULL) {
394 5 : gpr_free(refresh_token->client_id);
395 5 : refresh_token->client_id = NULL;
396 : }
397 9 : if (refresh_token->client_secret != NULL) {
398 6 : gpr_free(refresh_token->client_secret);
399 6 : refresh_token->client_secret = NULL;
400 : }
401 9 : if (refresh_token->refresh_token != NULL) {
402 4 : gpr_free(refresh_token->refresh_token);
403 4 : refresh_token->refresh_token = NULL;
404 : }
405 : }
|