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