| Index: third_party/grpc/src/core/security/json_token.c
|
| diff --git a/third_party/grpc/src/core/security/json_token.c b/third_party/grpc/src/core/security/json_token.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..372e5bfc5aba3dca48031fd18dde37bcb92ef9d4
|
| --- /dev/null
|
| +++ b/third_party/grpc/src/core/security/json_token.c
|
| @@ -0,0 +1,411 @@
|
| +/*
|
| + *
|
| + * Copyright 2015-2016, Google Inc.
|
| + * All rights reserved.
|
| + *
|
| + * Redistribution and use in source and binary forms, with or without
|
| + * modification, are permitted provided that the following conditions are
|
| + * met:
|
| + *
|
| + * * Redistributions of source code must retain the above copyright
|
| + * notice, this list of conditions and the following disclaimer.
|
| + * * Redistributions in binary form must reproduce the above
|
| + * copyright notice, this list of conditions and the following disclaimer
|
| + * in the documentation and/or other materials provided with the
|
| + * distribution.
|
| + * * Neither the name of Google Inc. nor the names of its
|
| + * contributors may be used to endorse or promote products derived from
|
| + * this software without specific prior written permission.
|
| + *
|
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| + *
|
| + */
|
| +
|
| +#include "src/core/security/json_token.h"
|
| +
|
| +#include <string.h>
|
| +
|
| +#include <grpc/support/alloc.h>
|
| +#include <grpc/support/log.h>
|
| +#include <grpc/support/string_util.h>
|
| +
|
| +#include "src/core/security/b64.h"
|
| +#include "src/core/support/string.h"
|
| +
|
| +#include <openssl/bio.h>
|
| +#include <openssl/evp.h>
|
| +#include <openssl/pem.h>
|
| +
|
| +/* --- Constants. --- */
|
| +
|
| +/* 1 hour max. */
|
| +gpr_timespec grpc_max_auth_token_lifetime() {
|
| + gpr_timespec out;
|
| + out.tv_sec = 3600;
|
| + out.tv_nsec = 0;
|
| + out.clock_type = GPR_TIMESPAN;
|
| + return out;
|
| +}
|
| +
|
| +#define GRPC_JWT_RSA_SHA256_ALGORITHM "RS256"
|
| +#define GRPC_JWT_TYPE "JWT"
|
| +
|
| +/* --- Override for testing. --- */
|
| +
|
| +static grpc_jwt_encode_and_sign_override g_jwt_encode_and_sign_override = NULL;
|
| +
|
| +/* --- grpc_auth_json_key. --- */
|
| +
|
| +static const char *json_get_string_property(const grpc_json *json,
|
| + const char *prop_name) {
|
| + grpc_json *child;
|
| + for (child = json->child; child != NULL; child = child->next) {
|
| + if (strcmp(child->key, prop_name) == 0) break;
|
| + }
|
| + if (child == NULL || child->type != GRPC_JSON_STRING) {
|
| + gpr_log(GPR_ERROR, "Invalid or missing %s property.", prop_name);
|
| + return NULL;
|
| + }
|
| + return child->value;
|
| +}
|
| +
|
| +static int set_json_key_string_property(const grpc_json *json,
|
| + const char *prop_name,
|
| + char **json_key_field) {
|
| + const char *prop_value = json_get_string_property(json, prop_name);
|
| + if (prop_value == NULL) return 0;
|
| + *json_key_field = gpr_strdup(prop_value);
|
| + return 1;
|
| +}
|
| +
|
| +int grpc_auth_json_key_is_valid(const grpc_auth_json_key *json_key) {
|
| + return (json_key != NULL) &&
|
| + strcmp(json_key->type, GRPC_AUTH_JSON_TYPE_INVALID);
|
| +}
|
| +
|
| +grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json *json) {
|
| + grpc_auth_json_key result;
|
| + BIO *bio = NULL;
|
| + const char *prop_value;
|
| + int success = 0;
|
| +
|
| + memset(&result, 0, sizeof(grpc_auth_json_key));
|
| + result.type = GRPC_AUTH_JSON_TYPE_INVALID;
|
| + if (json == NULL) {
|
| + gpr_log(GPR_ERROR, "Invalid json.");
|
| + goto end;
|
| + }
|
| +
|
| + prop_value = json_get_string_property(json, "type");
|
| + if (prop_value == NULL ||
|
| + strcmp(prop_value, GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT)) {
|
| + goto end;
|
| + }
|
| + result.type = GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT;
|
| +
|
| + if (!set_json_key_string_property(json, "private_key_id",
|
| + &result.private_key_id) ||
|
| + !set_json_key_string_property(json, "client_id", &result.client_id) ||
|
| + !set_json_key_string_property(json, "client_email",
|
| + &result.client_email)) {
|
| + goto end;
|
| + }
|
| +
|
| + prop_value = json_get_string_property(json, "private_key");
|
| + if (prop_value == NULL) {
|
| + goto end;
|
| + }
|
| + bio = BIO_new(BIO_s_mem());
|
| + success = BIO_puts(bio, prop_value);
|
| + if ((success < 0) || ((size_t)success != strlen(prop_value))) {
|
| + gpr_log(GPR_ERROR, "Could not write into openssl BIO.");
|
| + goto end;
|
| + }
|
| + result.private_key = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, "");
|
| + if (result.private_key == NULL) {
|
| + gpr_log(GPR_ERROR, "Could not deserialize private key.");
|
| + goto end;
|
| + }
|
| + success = 1;
|
| +
|
| +end:
|
| + if (bio != NULL) BIO_free(bio);
|
| + if (!success) grpc_auth_json_key_destruct(&result);
|
| + return result;
|
| +}
|
| +
|
| +grpc_auth_json_key grpc_auth_json_key_create_from_string(
|
| + const char *json_string) {
|
| + char *scratchpad = gpr_strdup(json_string);
|
| + grpc_json *json = grpc_json_parse_string(scratchpad);
|
| + grpc_auth_json_key result = grpc_auth_json_key_create_from_json(json);
|
| + if (json != NULL) grpc_json_destroy(json);
|
| + gpr_free(scratchpad);
|
| + return result;
|
| +}
|
| +
|
| +void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key) {
|
| + if (json_key == NULL) return;
|
| + json_key->type = GRPC_AUTH_JSON_TYPE_INVALID;
|
| + if (json_key->client_id != NULL) {
|
| + gpr_free(json_key->client_id);
|
| + json_key->client_id = NULL;
|
| + }
|
| + if (json_key->private_key_id != NULL) {
|
| + gpr_free(json_key->private_key_id);
|
| + json_key->private_key_id = NULL;
|
| + }
|
| + if (json_key->client_email != NULL) {
|
| + gpr_free(json_key->client_email);
|
| + json_key->client_email = NULL;
|
| + }
|
| + if (json_key->private_key != NULL) {
|
| + RSA_free(json_key->private_key);
|
| + json_key->private_key = NULL;
|
| + }
|
| +}
|
| +
|
| +/* --- jwt encoding and signature. --- */
|
| +
|
| +static grpc_json *create_child(grpc_json *brother, grpc_json *parent,
|
| + const char *key, const char *value,
|
| + grpc_json_type type) {
|
| + grpc_json *child = grpc_json_create(type);
|
| + if (brother) brother->next = child;
|
| + if (!parent->child) parent->child = child;
|
| + child->parent = parent;
|
| + child->value = value;
|
| + child->key = key;
|
| + return child;
|
| +}
|
| +
|
| +static char *encoded_jwt_header(const char *key_id, const char *algorithm) {
|
| + grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT);
|
| + grpc_json *child = NULL;
|
| + char *json_str = NULL;
|
| + char *result = NULL;
|
| +
|
| + child = create_child(NULL, json, "alg", algorithm, GRPC_JSON_STRING);
|
| + child = create_child(child, json, "typ", GRPC_JWT_TYPE, GRPC_JSON_STRING);
|
| + create_child(child, json, "kid", key_id, GRPC_JSON_STRING);
|
| +
|
| + json_str = grpc_json_dump_to_string(json, 0);
|
| + result = grpc_base64_encode(json_str, strlen(json_str), 1, 0);
|
| + gpr_free(json_str);
|
| + grpc_json_destroy(json);
|
| + return result;
|
| +}
|
| +
|
| +static char *encoded_jwt_claim(const grpc_auth_json_key *json_key,
|
| + const char *audience,
|
| + gpr_timespec token_lifetime, const char *scope) {
|
| + grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT);
|
| + grpc_json *child = NULL;
|
| + char *json_str = NULL;
|
| + char *result = NULL;
|
| + gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
|
| + gpr_timespec expiration = gpr_time_add(now, token_lifetime);
|
| + char now_str[GPR_LTOA_MIN_BUFSIZE];
|
| + char expiration_str[GPR_LTOA_MIN_BUFSIZE];
|
| + if (gpr_time_cmp(token_lifetime, grpc_max_auth_token_lifetime()) > 0) {
|
| + gpr_log(GPR_INFO, "Cropping token lifetime to maximum allowed value.");
|
| + expiration = gpr_time_add(now, grpc_max_auth_token_lifetime());
|
| + }
|
| + int64_ttoa(now.tv_sec, now_str);
|
| + int64_ttoa(expiration.tv_sec, expiration_str);
|
| +
|
| + child =
|
| + create_child(NULL, json, "iss", json_key->client_email, GRPC_JSON_STRING);
|
| + if (scope != NULL) {
|
| + child = create_child(child, json, "scope", scope, GRPC_JSON_STRING);
|
| + } else {
|
| + /* Unscoped JWTs need a sub field. */
|
| + child = create_child(child, json, "sub", json_key->client_email,
|
| + GRPC_JSON_STRING);
|
| + }
|
| +
|
| + child = create_child(child, json, "aud", audience, GRPC_JSON_STRING);
|
| + child = create_child(child, json, "iat", now_str, GRPC_JSON_NUMBER);
|
| + create_child(child, json, "exp", expiration_str, GRPC_JSON_NUMBER);
|
| +
|
| + json_str = grpc_json_dump_to_string(json, 0);
|
| + result = grpc_base64_encode(json_str, strlen(json_str), 1, 0);
|
| + gpr_free(json_str);
|
| + grpc_json_destroy(json);
|
| + return result;
|
| +}
|
| +
|
| +static char *dot_concat_and_free_strings(char *str1, char *str2) {
|
| + size_t str1_len = strlen(str1);
|
| + size_t str2_len = strlen(str2);
|
| + size_t result_len = str1_len + 1 /* dot */ + str2_len;
|
| + char *result = gpr_malloc(result_len + 1 /* NULL terminated */);
|
| + char *current = result;
|
| + memcpy(current, str1, str1_len);
|
| + current += str1_len;
|
| + *(current++) = '.';
|
| + memcpy(current, str2, str2_len);
|
| + current += str2_len;
|
| + GPR_ASSERT(current >= result);
|
| + GPR_ASSERT((uintptr_t)(current - result) == result_len);
|
| + *current = '\0';
|
| + gpr_free(str1);
|
| + gpr_free(str2);
|
| + return result;
|
| +}
|
| +
|
| +const EVP_MD *openssl_digest_from_algorithm(const char *algorithm) {
|
| + if (strcmp(algorithm, GRPC_JWT_RSA_SHA256_ALGORITHM) == 0) {
|
| + return EVP_sha256();
|
| + } else {
|
| + gpr_log(GPR_ERROR, "Unknown algorithm %s.", algorithm);
|
| + return NULL;
|
| + }
|
| +}
|
| +
|
| +char *compute_and_encode_signature(const grpc_auth_json_key *json_key,
|
| + const char *signature_algorithm,
|
| + const char *to_sign) {
|
| + const EVP_MD *md = openssl_digest_from_algorithm(signature_algorithm);
|
| + EVP_MD_CTX *md_ctx = NULL;
|
| + EVP_PKEY *key = EVP_PKEY_new();
|
| + size_t sig_len = 0;
|
| + unsigned char *sig = NULL;
|
| + char *result = NULL;
|
| + if (md == NULL) return NULL;
|
| + md_ctx = EVP_MD_CTX_create();
|
| + if (md_ctx == NULL) {
|
| + gpr_log(GPR_ERROR, "Could not create MD_CTX");
|
| + goto end;
|
| + }
|
| + EVP_PKEY_set1_RSA(key, json_key->private_key);
|
| + if (EVP_DigestSignInit(md_ctx, NULL, md, NULL, key) != 1) {
|
| + gpr_log(GPR_ERROR, "DigestInit failed.");
|
| + goto end;
|
| + }
|
| + if (EVP_DigestSignUpdate(md_ctx, to_sign, strlen(to_sign)) != 1) {
|
| + gpr_log(GPR_ERROR, "DigestUpdate failed.");
|
| + goto end;
|
| + }
|
| + if (EVP_DigestSignFinal(md_ctx, NULL, &sig_len) != 1) {
|
| + gpr_log(GPR_ERROR, "DigestFinal (get signature length) failed.");
|
| + goto end;
|
| + }
|
| + sig = gpr_malloc(sig_len);
|
| + if (EVP_DigestSignFinal(md_ctx, sig, &sig_len) != 1) {
|
| + gpr_log(GPR_ERROR, "DigestFinal (signature compute) failed.");
|
| + goto end;
|
| + }
|
| + result = grpc_base64_encode(sig, sig_len, 1, 0);
|
| +
|
| +end:
|
| + if (key != NULL) EVP_PKEY_free(key);
|
| + if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
|
| + if (sig != NULL) gpr_free(sig);
|
| + return result;
|
| +}
|
| +
|
| +char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key,
|
| + const char *audience,
|
| + gpr_timespec token_lifetime, const char *scope) {
|
| + if (g_jwt_encode_and_sign_override != NULL) {
|
| + return g_jwt_encode_and_sign_override(json_key, audience, token_lifetime,
|
| + scope);
|
| + } else {
|
| + const char *sig_algo = GRPC_JWT_RSA_SHA256_ALGORITHM;
|
| + char *to_sign = dot_concat_and_free_strings(
|
| + encoded_jwt_header(json_key->private_key_id, sig_algo),
|
| + encoded_jwt_claim(json_key, audience, token_lifetime, scope));
|
| + char *sig = compute_and_encode_signature(json_key, sig_algo, to_sign);
|
| + if (sig == NULL) {
|
| + gpr_free(to_sign);
|
| + return NULL;
|
| + }
|
| + return dot_concat_and_free_strings(to_sign, sig);
|
| + }
|
| +}
|
| +
|
| +void grpc_jwt_encode_and_sign_set_override(
|
| + grpc_jwt_encode_and_sign_override func) {
|
| + g_jwt_encode_and_sign_override = func;
|
| +}
|
| +
|
| +/* --- grpc_auth_refresh_token --- */
|
| +
|
| +int grpc_auth_refresh_token_is_valid(
|
| + const grpc_auth_refresh_token *refresh_token) {
|
| + return (refresh_token != NULL) &&
|
| + strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID);
|
| +}
|
| +
|
| +grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
|
| + const grpc_json *json) {
|
| + grpc_auth_refresh_token result;
|
| + const char *prop_value;
|
| + int success = 0;
|
| +
|
| + memset(&result, 0, sizeof(grpc_auth_refresh_token));
|
| + result.type = GRPC_AUTH_JSON_TYPE_INVALID;
|
| + if (json == NULL) {
|
| + gpr_log(GPR_ERROR, "Invalid json.");
|
| + goto end;
|
| + }
|
| +
|
| + prop_value = json_get_string_property(json, "type");
|
| + if (prop_value == NULL ||
|
| + strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) {
|
| + goto end;
|
| + }
|
| + result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER;
|
| +
|
| + if (!set_json_key_string_property(json, "client_secret",
|
| + &result.client_secret) ||
|
| + !set_json_key_string_property(json, "client_id", &result.client_id) ||
|
| + !set_json_key_string_property(json, "refresh_token",
|
| + &result.refresh_token)) {
|
| + goto end;
|
| + }
|
| + success = 1;
|
| +
|
| +end:
|
| + if (!success) grpc_auth_refresh_token_destruct(&result);
|
| + return result;
|
| +}
|
| +
|
| +grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string(
|
| + const char *json_string) {
|
| + char *scratchpad = gpr_strdup(json_string);
|
| + grpc_json *json = grpc_json_parse_string(scratchpad);
|
| + grpc_auth_refresh_token result =
|
| + grpc_auth_refresh_token_create_from_json(json);
|
| + if (json != NULL) grpc_json_destroy(json);
|
| + gpr_free(scratchpad);
|
| + return result;
|
| +}
|
| +
|
| +void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token *refresh_token) {
|
| + if (refresh_token == NULL) return;
|
| + refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID;
|
| + if (refresh_token->client_id != NULL) {
|
| + gpr_free(refresh_token->client_id);
|
| + refresh_token->client_id = NULL;
|
| + }
|
| + if (refresh_token->client_secret != NULL) {
|
| + gpr_free(refresh_token->client_secret);
|
| + refresh_token->client_secret = NULL;
|
| + }
|
| + if (refresh_token->refresh_token != NULL) {
|
| + gpr_free(refresh_token->refresh_token);
|
| + refresh_token->refresh_token = NULL;
|
| + }
|
| +}
|
|
|