Adding JWT generation and signing code for service accounts credentials.

Change on 2014/12/16 by jboeuf <jboeuf@google.com>
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=82279249
pull/1/merge
jboeuf 10 years ago committed by Nicolas Noble
parent 081e3200c8
commit ab4f91458b
  1. 14
      include/grpc/grpc_security.h
  2. 142
      src/core/security/json_token.c
  3. 9
      src/core/security/json_token.h
  4. 151
      test/core/security/json_token_test.c

@ -78,13 +78,17 @@ grpc_credentials *grpc_composite_credentials_create(grpc_credentials *creds1,
/* Creates a compute engine credentials object. */
grpc_credentials *grpc_compute_engine_credentials_create(void);
extern const gpr_timespec grpc_max_service_accounts_token_validity;
extern const gpr_timespec grpc_max_auth_token_lifetime;
/* Creates a service account credentials object. May return NULL if the input is
invalid. time_validity should not exceed grpc_max_service_accounts_token
validity or will be cropped to this value. */
grpc_credentials *grpc_service_accounts_credentials_create(
const char *json_key, const char *scope, gpr_timespec token_validity);
invalid.
- json_key is the JSON key string containing the client's private key.
- scope is a space-delimited list of the requested permissions.
- token_lifetime is the lifetime of each token acquired through this service
account credentials. It should not exceed grpc_max_auth_token_lifetime
or will be cropped to this value. */
grpc_credentials *grpc_service_account_credentials_create(
const char *json_key, const char *scope, gpr_timespec token_lifetime);
/* Creates a fake transport security credentials object for testing. */
grpc_credentials *grpc_fake_transport_security_credentials_create(void);

@ -38,19 +38,26 @@
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string.h>
#include "src/core/security/base64.h"
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include "third_party/cJSON/cJSON.h"
/* --- Constants. --- */
/* 1 hour max. */
const gpr_timespec grpc_max_service_accounts_token_validity = {3600, 0};
const gpr_timespec grpc_max_auth_token_lifetime = {3600, 0};
#define GRPC_AUTH_JSON_KEY_TYPE_INVALID "invalid"
#define GRPC_AUTH_JSON_KEY_TYPE_SERVICE_ACCOUNT "service_account"
#define GRPC_JWT_AUDIENCE "https://www.googleapis.com/oauth2/v3/token"
#define GRPC_JWT_RSA_SHA256_ALGORITHM "RS256"
#define GRPC_JWT_TYPE "JWT"
/* --- grpc_auth_json_key. --- */
static const char *json_get_string_property(cJSON *json,
@ -150,3 +157,134 @@ void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key) {
json_key->private_key = NULL;
}
}
/* --- jwt encoding and signature. --- */
static char *encoded_jwt_header(const char *algorithm) {
cJSON *json = cJSON_CreateObject();
cJSON *child = cJSON_CreateString(algorithm);
char *json_str = NULL;
char *result = NULL;
cJSON_AddItemToObject(json, "alg", child);
child = cJSON_CreateString(GRPC_JWT_TYPE);
cJSON_AddItemToObject(json, "typ", child);
json_str = cJSON_PrintUnformatted(json);
result = grpc_base64_encode(json_str, strlen(json_str), 1, 0);
free(json_str);
cJSON_Delete(json);
return result;
}
static char *encoded_jwt_claim(const grpc_auth_json_key *json_key,
const char *scope, gpr_timespec token_lifetime) {
cJSON *json = cJSON_CreateObject();
cJSON *child = NULL;
char *json_str = NULL;
char *result = NULL;
gpr_timespec now = gpr_now();
gpr_timespec expiration = gpr_time_add(now, token_lifetime);
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);
}
child = cJSON_CreateString(json_key->client_email);
cJSON_AddItemToObject(json, "iss", child);
child = cJSON_CreateString(scope);
cJSON_AddItemToObject(json, "scope", child);
child = cJSON_CreateString(GRPC_JWT_AUDIENCE);
cJSON_AddItemToObject(json, "aud", child);
child = cJSON_CreateNumber(now.tv_sec);
cJSON_SetIntValue(child, now.tv_sec);
cJSON_AddItemToObject(json, "iat", child);
child = cJSON_CreateNumber(expiration.tv_sec);
cJSON_SetIntValue(child, expiration.tv_sec);
cJSON_AddItemToObject(json, "exp", child);
json_str = cJSON_PrintUnformatted(json);
result = grpc_base64_encode(json_str, strlen(json_str), 1, 0);
free(json_str);
cJSON_Delete(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;
strncpy(current, str1, str1_len);
current += str1_len;
*(current++) = '.';
strncpy(current, str2, str2_len);
current += str2_len;
GPR_ASSERT((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)) {
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 *scope, gpr_timespec token_lifetime) {
const char *sig_algo = GRPC_JWT_RSA_SHA256_ALGORITHM;
char *to_sign = dot_concat_and_free_strings(
encoded_jwt_header(sig_algo),
encoded_jwt_claim(json_key, scope, token_lifetime));
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);
}

@ -37,7 +37,7 @@
#include <grpc/support/slice.h>
#include <openssl/rsa.h>
/* --- json_key parsing. Exposed for testing only. --- */
/* --- auth_json_key parsing. Exposed for testing only. --- */
typedef struct {
char *type;
@ -58,4 +58,11 @@ grpc_auth_json_key grpc_auth_json_key_create_from_string(
/* Destructs the object. */
void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key);
/* --- json token encoding and signing. --- */
/* Caller is responsible for calling gpr_free on the returned value. May return
NULL on invalid input. */
char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key,
const char *scope, gpr_timespec token_lifetime);
#endif /* __GRPC_INTERNAL_SECURITY_JSON_TOKEN_H_ */

@ -35,10 +35,14 @@
#include <string.h>
#include "src/core/security/base64.h"
#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/slice.h>
#include "test/core/util/test_config.h"
#include "third_party/cJSON/cJSON.h"
#include <openssl/evp.h>
/* This JSON key was generated with the GCE console and revoked immediately.
The identifiers have been changed as well.
@ -70,6 +74,8 @@ static const char test_json_key_str_part3[] =
"\"777-abaslkan11hlb6nmim3bpspl31ud.apps.googleusercontent."
"com\", \"type\": \"service_account\" }";
static const char test_scope[] = "myperm1 myperm2";
static char *test_json_key_str(const char *bad_part3) {
const char *part3 = bad_part3 != NULL ? bad_part3 : test_json_key_str_part3;
size_t result_len = strlen(test_json_key_str_part1) +
@ -84,7 +90,7 @@ static char *test_json_key_str(const char *bad_part3) {
return result;
}
static void test_parse_json_key_success() {
static void test_parse_json_key_success(void) {
char *json_string = test_json_key_str(NULL);
grpc_auth_json_key json_key =
grpc_auth_json_key_create_from_string(json_string);
@ -107,7 +113,7 @@ static void test_parse_json_key_success() {
grpc_auth_json_key_destruct(&json_key);
}
static void test_parse_json_key_failure_bad_json() {
static void test_parse_json_key_failure_bad_json(void) {
const char non_closing_part3[] =
"\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_email\": "
@ -123,7 +129,7 @@ static void test_parse_json_key_failure_bad_json() {
grpc_auth_json_key_destruct(&json_key);
}
static void test_parse_json_key_failure_no_type() {
static void test_parse_json_key_failure_no_type(void) {
const char no_type_part3[] =
"\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_email\": "
@ -139,7 +145,7 @@ static void test_parse_json_key_failure_no_type() {
grpc_auth_json_key_destruct(&json_key);
}
static void test_parse_json_key_failure_no_client_id() {
static void test_parse_json_key_failure_no_client_id(void) {
const char no_client_id_part3[] =
"\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_email\": "
@ -154,7 +160,7 @@ static void test_parse_json_key_failure_no_client_id() {
grpc_auth_json_key_destruct(&json_key);
}
static void test_parse_json_key_failure_no_client_email() {
static void test_parse_json_key_failure_no_client_email(void) {
const char no_client_email_part3[] =
"\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_id\": "
@ -168,7 +174,7 @@ static void test_parse_json_key_failure_no_client_email() {
grpc_auth_json_key_destruct(&json_key);
}
static void test_parse_json_key_failure_no_private_key_id() {
static void test_parse_json_key_failure_no_private_key_id(void) {
const char no_private_key_id_part3[] =
"\"client_email\": "
"\"777-abaslkan11hlb6nmim3bpspl31ud@developer.gserviceaccount."
@ -183,7 +189,7 @@ static void test_parse_json_key_failure_no_private_key_id() {
grpc_auth_json_key_destruct(&json_key);
}
static void test_parse_json_key_failure_no_private_key() {
static void test_parse_json_key_failure_no_private_key(void) {
const char no_private_key_json_string[] =
"{ \"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_email\": "
@ -197,6 +203,136 @@ static void test_parse_json_key_failure_no_private_key() {
grpc_auth_json_key_destruct(&json_key);
}
static cJSON *parse_json_part_from_jwt(const char *str, size_t len) {
char *b64;
char *decoded;
cJSON *json;
gpr_slice slice;
b64 = gpr_malloc(len + 1);
strncpy(b64, str, len);
b64[len] = '\0';
slice = grpc_base64_decode(b64, 1);
GPR_ASSERT(!GPR_SLICE_IS_EMPTY(slice));
decoded = gpr_malloc(GPR_SLICE_LENGTH(slice) + 1);
strncpy(decoded, GPR_SLICE_START_PTR(slice), GPR_SLICE_LENGTH(slice));
decoded[GPR_SLICE_LENGTH(slice)] = '\0';
json = cJSON_Parse(decoded);
gpr_free(b64);
gpr_free(decoded);
gpr_slice_unref(slice);
return json;
}
static void check_jwt_header(cJSON *header) {
cJSON *child = cJSON_GetObjectItem(header, "alg");
GPR_ASSERT(child != NULL);
GPR_ASSERT(child->type == cJSON_String);
GPR_ASSERT(!strcmp(child->valuestring, "RS256"));
child = cJSON_GetObjectItem(header, "typ");
GPR_ASSERT(child != NULL);
GPR_ASSERT(child->type == cJSON_String);
GPR_ASSERT(!strcmp(child->valuestring, "JWT"));
}
static void check_jwt_claim(cJSON *claim) {
gpr_timespec exp = {0, 0};
gpr_timespec issue_time = {0, 0};
gpr_timespec parsed_lifetime;
cJSON *child = cJSON_GetObjectItem(claim, "iss");
GPR_ASSERT(child != NULL);
GPR_ASSERT(child->type == cJSON_String);
GPR_ASSERT(
!strcmp(
child->valuestring,
"777-abaslkan11hlb6nmim3bpspl31ud@developer.gserviceaccount.com"));
child = cJSON_GetObjectItem(claim, "scope");
GPR_ASSERT(child != NULL);
GPR_ASSERT(child->type == cJSON_String);
GPR_ASSERT(!strcmp(child->valuestring, test_scope));
child = cJSON_GetObjectItem(claim, "aud");
GPR_ASSERT(child != NULL);
GPR_ASSERT(child->type == cJSON_String);
GPR_ASSERT(!strcmp(child->valuestring,
"https://www.googleapis.com/oauth2/v3/token"));
child = cJSON_GetObjectItem(claim, "exp");
GPR_ASSERT(child != NULL);
GPR_ASSERT(child->type == cJSON_Number);
exp.tv_sec = child->valueint;
child = cJSON_GetObjectItem(claim, "iat");
GPR_ASSERT(child != NULL);
GPR_ASSERT(child->type == cJSON_Number);
issue_time.tv_sec = child->valueint;
parsed_lifetime = gpr_time_sub(exp, issue_time);
GPR_ASSERT(parsed_lifetime.tv_sec == grpc_max_auth_token_lifetime.tv_sec);
}
static void check_jwt_signature(const char *b64_signature, RSA *rsa_key,
const char *signed_data,
size_t signed_data_size) {
EVP_MD_CTX *md_ctx = EVP_MD_CTX_create();
EVP_PKEY *key = EVP_PKEY_new();
gpr_slice sig = grpc_base64_decode(b64_signature, 1);
GPR_ASSERT(!GPR_SLICE_IS_EMPTY(sig));
GPR_ASSERT(GPR_SLICE_LENGTH(sig) == 128);
GPR_ASSERT(md_ctx != NULL);
GPR_ASSERT(key != NULL);
EVP_PKEY_set1_RSA(key, rsa_key);
GPR_ASSERT(EVP_DigestVerifyInit(md_ctx, NULL, EVP_sha256(), NULL, key) == 1);
GPR_ASSERT(EVP_DigestVerifyUpdate(md_ctx, signed_data, signed_data_size) ==
1);
GPR_ASSERT(EVP_DigestVerifyFinal(md_ctx, GPR_SLICE_START_PTR(sig),
GPR_SLICE_LENGTH(sig)) == 1);
gpr_slice_unref(sig);
if (key != NULL) EVP_PKEY_free(key);
if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
}
static void test_jwt_encode_and_sign(void) {
char *json_string = test_json_key_str(NULL);
cJSON *parsed_header = NULL;
cJSON *parsed_claim = NULL;
grpc_auth_json_key json_key =
grpc_auth_json_key_create_from_string(json_string);
const char *b64_signature;
size_t offset = 0;
char *jwt = grpc_jwt_encode_and_sign(&json_key, test_scope,
grpc_max_auth_token_lifetime);
const char *dot = strchr(jwt, '.');
GPR_ASSERT(dot != NULL);
parsed_header = parse_json_part_from_jwt(jwt, dot - jwt);
GPR_ASSERT(parsed_header != NULL);
check_jwt_header(parsed_header);
offset = dot - jwt + 1;
dot = strchr(jwt + offset, '.');
GPR_ASSERT(dot != NULL);
parsed_claim = parse_json_part_from_jwt(jwt + offset, dot - (jwt + offset));
GPR_ASSERT(parsed_claim != NULL);
check_jwt_claim(parsed_claim);
offset = dot - jwt + 1;
dot = strchr(jwt + offset, '.');
GPR_ASSERT(dot == NULL); /* no more part. */
b64_signature = jwt + offset;
check_jwt_signature(b64_signature, json_key.private_key, jwt, offset - 1);
gpr_free(json_string);
cJSON_Delete(parsed_header);
cJSON_Delete(parsed_claim);
grpc_auth_json_key_destruct(&json_key);
gpr_free(jwt);
}
int main(int argc, char **argv) {
grpc_test_init(argc, argv);
test_parse_json_key_success();
@ -206,5 +342,6 @@ int main(int argc, char **argv) {
test_parse_json_key_failure_no_client_email();
test_parse_json_key_failure_no_private_key_id();
test_parse_json_key_failure_no_private_key();
test_jwt_encode_and_sign();
return 0;
}

Loading…
Cancel
Save