Adding JWT verifier.

Still missing:
- Caching of the already checked JWTs (although it could be done at an
  upper layer).
- Caching of the jwks_uri to avoid 2 roundtrips for each verification.
pull/2252/head
Julien Boeuf 10 years ago
parent a6de02f5ef
commit feca1bf06c
  1. 4
      BUILD
  2. 68
      Makefile
  3. 30
      build.json
  4. 3
      gRPC.podspec
  5. 4
      src/core/httpcli/httpcli.h
  6. 6
      src/core/security/base64.c
  7. 4
      src/core/security/base64.h
  8. 3
      src/core/security/credentials.c
  9. 830
      src/core/security/jwt_verifier.c
  10. 136
      src/core/security/jwt_verifier.h
  11. 565
      test/core/security/jwt_verifier_test.c
  12. 4
      test/core/security/print_google_default_creds_token.c
  13. 119
      test/core/security/verify_jwt.c
  14. 2
      tools/doxygen/Doxyfile.core.internal
  15. 31
      tools/run_tests/sources_and_headers.json
  16. 9
      tools/run_tests/tests.json
  17. 16
      vsprojects/Grpc.mak
  18. 3
      vsprojects/grpc/grpc.vcxproj
  19. 6
      vsprojects/grpc/grpc.vcxproj.filters

@ -138,6 +138,7 @@ cc_library(
"src/core/security/base64.h",
"src/core/security/credentials.h",
"src/core/security/json_token.h",
"src/core/security/jwt_verifier.h",
"src/core/security/secure_endpoint.h",
"src/core/security/secure_transport_setup.h",
"src/core/security/security_connector.h",
@ -244,6 +245,7 @@ cc_library(
"src/core/security/credentials_win32.c",
"src/core/security/google_default_credentials.c",
"src/core/security/json_token.c",
"src/core/security/jwt_verifier.c",
"src/core/security/secure_endpoint.c",
"src/core/security/secure_transport_setup.c",
"src/core/security/security_connector.c",
@ -906,6 +908,7 @@ objc_library(
"src/core/security/credentials_win32.c",
"src/core/security/google_default_credentials.c",
"src/core/security/json_token.c",
"src/core/security/jwt_verifier.c",
"src/core/security/secure_endpoint.c",
"src/core/security/secure_transport_setup.c",
"src/core/security/security_connector.c",
@ -1033,6 +1036,7 @@ objc_library(
"src/core/security/base64.h",
"src/core/security/credentials.h",
"src/core/security/json_token.h",
"src/core/security/jwt_verifier.h",
"src/core/security/secure_endpoint.h",
"src/core/security/secure_transport_setup.h",
"src/core/security/security_connector.h",

File diff suppressed because one or more lines are too long

@ -431,6 +431,7 @@
"src/core/security/base64.h",
"src/core/security/credentials.h",
"src/core/security/json_token.h",
"src/core/security/jwt_verifier.h",
"src/core/security/secure_endpoint.h",
"src/core/security/secure_transport_setup.h",
"src/core/security/security_connector.h",
@ -453,6 +454,7 @@
"src/core/security/credentials_win32.c",
"src/core/security/google_default_credentials.c",
"src/core/security/json_token.c",
"src/core/security/jwt_verifier.c",
"src/core/security/secure_endpoint.c",
"src/core/security/secure_transport_setup.c",
"src/core/security/security_connector.c",
@ -1316,6 +1318,20 @@
"gpr"
]
},
{
"name": "grpc_jwt_verifier_test",
"build": "test",
"language": "c",
"src": [
"test/core/security/jwt_verifier_test.c"
],
"deps": [
"grpc_test_util",
"grpc",
"gpr_test_util",
"gpr"
]
},
{
"name": "grpc_print_google_default_creds_token",
"build": "tool",
@ -1358,6 +1374,20 @@
"gpr"
]
},
{
"name": "grpc_verify_jwt",
"build": "tool",
"language": "c",
"src": [
"test/core/security/verify_jwt.c"
],
"deps": [
"grpc_test_util",
"grpc",
"gpr_test_util",
"gpr"
]
},
{
"name": "hpack_parser_test",
"build": "test",

@ -140,6 +140,7 @@ Pod::Spec.new do |s|
'src/core/security/base64.h',
'src/core/security/credentials.h',
'src/core/security/json_token.h',
'src/core/security/jwt_verifier.h',
'src/core/security/secure_endpoint.h',
'src/core/security/secure_transport_setup.h',
'src/core/security/security_connector.h',
@ -253,6 +254,7 @@ Pod::Spec.new do |s|
'src/core/security/credentials_win32.c',
'src/core/security/google_default_credentials.c',
'src/core/security/json_token.c',
'src/core/security/jwt_verifier.c',
'src/core/security/secure_endpoint.c',
'src/core/security/secure_transport_setup.c',
'src/core/security/security_connector.c',
@ -378,6 +380,7 @@ Pod::Spec.new do |s|
'src/core/security/base64.h',
'src/core/security/credentials.h',
'src/core/security/json_token.h',
'src/core/security/jwt_verifier.h',
'src/core/security/secure_endpoint.h',
'src/core/security/secure_transport_setup.h',
'src/core/security/security_connector.h',

@ -85,7 +85,7 @@ typedef struct grpc_httpcli_response {
char *body;
} grpc_httpcli_response;
/* Callback for grpc_httpcli_get */
/* Callback for grpc_httpcli_get and grpc_httpcli_post. */
typedef void (*grpc_httpcli_response_cb)(void *user_data,
const grpc_httpcli_response *response);
@ -100,8 +100,6 @@ void grpc_httpcli_context_destroy(grpc_httpcli_context *context);
'request' contains request parameters - these are caller owned and can be
destroyed once the call returns
'deadline' contains a deadline for the request (or gpr_inf_future)
'em' points to a caller owned event manager that must be alive for the
lifetime of the request
'on_response' is a callback to report results to (and 'user_data' is a user
supplied pointer to pass to said call) */
void grpc_httpcli_get(grpc_httpcli_context *context, grpc_pollset *pollset,

@ -120,7 +120,11 @@ char *grpc_base64_encode(const void *vdata, size_t data_size, int url_safe,
}
gpr_slice grpc_base64_decode(const char *b64, int url_safe) {
size_t b64_len = strlen(b64);
return grpc_base64_decode_with_len(b64, strlen(b64), url_safe);
}
gpr_slice grpc_base64_decode_with_len(const char *b64, size_t b64_len,
int url_safe) {
gpr_slice result = gpr_slice_malloc(b64_len);
unsigned char *current = GPR_SLICE_START_PTR(result);
size_t result_size = 0;

@ -45,4 +45,8 @@ char *grpc_base64_encode(const void *data, size_t data_size, int url_safe,
slice in case of failure. */
gpr_slice grpc_base64_decode(const char *b64, int url_safe);
/* Same as above except that the length is provided by the caller. */
gpr_slice grpc_base64_decode_with_len(const char *b64, size_t b64_len,
int url_safe);
#endif /* GRPC_INTERNAL_CORE_SECURITY_BASE64_H */

@ -461,7 +461,6 @@ typedef struct {
grpc_credentials_md_store *access_token_md;
gpr_timespec token_expiration;
grpc_httpcli_context httpcli_context;
grpc_pollset_set pollset_set;
grpc_fetch_oauth2_func fetch_func;
} grpc_oauth2_token_fetcher_credentials;
@ -635,7 +634,7 @@ static void init_oauth2_token_fetcher(grpc_oauth2_token_fetcher_credentials *c,
gpr_mu_init(&c->mu);
c->token_expiration = gpr_inf_past;
c->fetch_func = fetch_func;
grpc_pollset_set_init(&c->pollset_set);
grpc_httpcli_context_init(&c->httpcli_context);
}
/* -- ComputeEngine credentials. -- */

@ -0,0 +1,830 @@
/*
*
* Copyright 2015, 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 disclaimser.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimser
* 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/jwt_verifier.h"
#include <string.h>
#include "src/core/httpcli/httpcli.h"
#include "src/core/security/base64.h"
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpc/support/sync.h>
#include <openssl/pem.h>
/* --- Utils. --- */
const char *grpc_jwt_verifier_status_to_string(
grpc_jwt_verifier_status status) {
switch (status) {
case GRPC_JWT_VERIFIER_OK:
return "OK";
case GRPC_JWT_VERIFIER_BAD_SIGNATURE:
return "BAD_SIGNATURE";
case GRPC_JWT_VERIFIER_BAD_FORMAT:
return "BAD_FORMAT";
case GRPC_JWT_VERIFIER_BAD_AUDIENCE:
return "BAD_AUDIENCE";
case GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR:
return "KEY_RETRIEVAL_ERROR";
case GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE:
return "TIME_CONSTRAINT_FAILURE";
case GRPC_JWT_VERIFIER_GENERIC_ERROR:
return "GENERIC_ERROR";
default:
return "UNKNOWN";
}
}
static const EVP_MD *evp_md_from_alg(const char *alg) {
if (strcmp(alg, "RS256") == 0) {
return EVP_sha256();
} else if (strcmp(alg, "RS384") == 0) {
return EVP_sha384();
} else if (strcmp(alg, "RS512") == 0) {
return EVP_sha512();
} else {
return NULL;
}
}
static grpc_json *parse_json_part_from_jwt(const char *str, size_t len,
gpr_slice *buffer) {
grpc_json *json;
*buffer = grpc_base64_decode_with_len(str, len, 1);
if (GPR_SLICE_IS_EMPTY(*buffer)) {
gpr_log(GPR_ERROR, "Invalid base64.");
return NULL;
}
json = grpc_json_parse_string_with_len((char *)GPR_SLICE_START_PTR(*buffer),
GPR_SLICE_LENGTH(*buffer));
if (json == NULL) {
gpr_slice_unref(*buffer);
gpr_log(GPR_ERROR, "JSON parsing error.");
}
return json;
}
static const char *validate_string_field(const grpc_json *json,
const char *key) {
if (json->type != GRPC_JSON_STRING) {
gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
return NULL;
}
return json->value;
}
static gpr_timespec validate_time_field(const grpc_json *json,
const char *key) {
gpr_timespec result = gpr_time_0;
if (json->type != GRPC_JSON_NUMBER) {
gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
return result;
}
result.tv_sec = strtol(json->value, NULL, 10);
return result;
}
/* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */
typedef struct {
const char *alg;
const char *kid;
const char *typ;
/* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */
gpr_slice buffer;
} jose_header;
static void jose_header_destroy(jose_header *h) {
gpr_slice_unref(h->buffer);
gpr_free(h);
}
/* Takes ownership of json and buffer. */
static jose_header *jose_header_from_json(grpc_json *json, gpr_slice buffer) {
grpc_json *cur;
jose_header *h = gpr_malloc(sizeof(jose_header));
memset(h, 0, sizeof(jose_header));
h->buffer = buffer;
for (cur = json->child; cur != NULL; cur = cur->next) {
if (strcmp(cur->key, "alg") == 0) {
/* We only support RSA-1.5 signatures for now.
Beware of this if we add HMAC support:
https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
*/
if (cur->type != GRPC_JSON_STRING || strncmp(cur->value, "RS", 2) ||
evp_md_from_alg(cur->value) == NULL) {
gpr_log(GPR_ERROR, "Invalid alg field [%s]", cur->value);
goto error;
}
h->alg = cur->value;
} else if (strcmp(cur->key, "typ") == 0) {
h->typ = validate_string_field(cur, "typ");
if (h->typ == NULL) goto error;
} else if (strcmp(cur->key, "kid") == 0) {
h->kid = validate_string_field(cur, "kid");
if (h->kid == NULL) goto error;
}
}
if (h->alg == NULL) {
gpr_log(GPR_ERROR, "Missing alg field.");
goto error;
}
grpc_json_destroy(json);
h->buffer = buffer;
return h;
error:
grpc_json_destroy(json);
jose_header_destroy(h);
return NULL;
}
/* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */
struct grpc_jwt_claims {
/* Well known properties already parsed. */
const char *sub;
const char *iss;
const char *aud;
const char *jti;
gpr_timespec iat;
gpr_timespec exp;
gpr_timespec nbf;
grpc_json *json;
gpr_slice buffer;
};
void grpc_jwt_claims_destroy(grpc_jwt_claims *claims) {
grpc_json_destroy(claims->json);
gpr_slice_unref(claims->buffer);
gpr_free(claims);
}
const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims) {
if (claims == NULL) return NULL;
return claims->json;
}
const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims) {
if (claims == NULL) return NULL;
return claims->sub;
}
const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims) {
if (claims == NULL) return NULL;
return claims->iss;
}
const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims) {
if (claims == NULL) return NULL;
return claims->jti;
}
const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims) {
if (claims == NULL) return NULL;
return claims->aud;
}
gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims) {
if (claims == NULL) return gpr_inf_past;
return claims->iat;
}
gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims) {
if (claims == NULL) return gpr_inf_future;
return claims->exp;
}
gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims) {
if (claims == NULL) return gpr_inf_past;
return claims->nbf;
}
/* Takes ownership of json and buffer even in case of failure. */
grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_json *json, gpr_slice buffer) {
grpc_json *cur;
grpc_jwt_claims *claims = gpr_malloc(sizeof(grpc_jwt_claims));
memset(claims, 0, sizeof(grpc_jwt_claims));
claims->json = json;
claims->buffer = buffer;
claims->iat = gpr_inf_past;
claims->nbf = gpr_inf_past;
claims->exp = gpr_inf_future;
/* Per the spec, all fields are optional. */
for (cur = json->child; cur != NULL; cur = cur->next) {
if (strcmp(cur->key, "sub") == 0) {
claims->sub = validate_string_field(cur, "sub");
if (claims->sub == NULL) goto error;
} else if (strcmp(cur->key, "iss") == 0) {
claims->iss = validate_string_field(cur, "iss");
if (claims->iss == NULL) goto error;
} else if (strcmp(cur->key, "aud") == 0) {
claims->aud = validate_string_field(cur, "aud");
if (claims->aud == NULL) goto error;
} else if (strcmp(cur->key, "jti") == 0) {
claims->jti = validate_string_field(cur, "jti");
if (claims->jti == NULL) goto error;
} else if (strcmp(cur->key, "iat") == 0) {
claims->iat = validate_time_field(cur, "iat");
if (gpr_time_cmp(claims->iat, gpr_time_0) == 0) goto error;
} else if (strcmp(cur->key, "exp") == 0) {
claims->exp = validate_time_field(cur, "exp");
if (gpr_time_cmp(claims->exp, gpr_time_0) == 0) goto error;
} else if (strcmp(cur->key, "nbf") == 0) {
claims->nbf = validate_time_field(cur, "nbf");
if (gpr_time_cmp(claims->nbf, gpr_time_0) == 0) goto error;
}
}
return claims;
error:
grpc_jwt_claims_destroy(claims);
return NULL;
}
grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims,
const char *audience) {
gpr_timespec skewed_now;
int audience_ok;
GPR_ASSERT(claims != NULL);
skewed_now = gpr_time_add(gpr_now(), grpc_jwt_verifier_clock_skew);
if (gpr_time_cmp(skewed_now, claims->nbf) < 0) {
gpr_log(GPR_ERROR, "JWT is not valid yet.");
return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
}
skewed_now = gpr_time_sub(gpr_now(), grpc_jwt_verifier_clock_skew);
if (gpr_time_cmp(skewed_now, claims->exp) > 0) {
gpr_log(GPR_ERROR, "JWT is expired.");
return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE;
}
if (audience == NULL) {
audience_ok = claims->aud == NULL;
} else {
audience_ok = claims->aud != NULL && strcmp(audience, claims->aud) == 0;
}
if (!audience_ok) {
gpr_log(GPR_ERROR, "Audience mismatch: expected %s and found %s.",
audience == NULL ? "NULL" : audience,
claims->aud == NULL ? "NULL" : claims->aud);
return GRPC_JWT_VERIFIER_BAD_AUDIENCE;
}
return GRPC_JWT_VERIFIER_OK;
}
/* --- verifier_cb_ctx object. --- */
typedef struct {
grpc_jwt_verifier *verifier;
grpc_pollset *pollset;
jose_header *header;
grpc_jwt_claims *claims;
char *audience;
gpr_slice signature;
gpr_slice signed_data;
void *user_data;
grpc_jwt_verification_done_cb user_cb;
} verifier_cb_ctx;
/* Takes ownership of the header, claims and signature. */
static verifier_cb_ctx *verifier_cb_ctx_create(
grpc_jwt_verifier *verifier, grpc_pollset *pollset,
jose_header * header, grpc_jwt_claims *claims, const char *audience,
gpr_slice signature, const char *signed_jwt, size_t signed_jwt_len,
void *user_data, grpc_jwt_verification_done_cb cb) {
verifier_cb_ctx *ctx = gpr_malloc(sizeof(verifier_cb_ctx));
memset(ctx, 0, sizeof(verifier_cb_ctx));
ctx->verifier = verifier;
ctx->pollset = pollset;
ctx->header = header;
ctx->audience = gpr_strdup(audience);
ctx->claims = claims;
ctx->signature = signature;
ctx->signed_data = gpr_slice_from_copied_buffer(signed_jwt, signed_jwt_len);
ctx->user_data = user_data;
ctx->user_cb = cb;
return ctx;
}
void verifier_cb_ctx_destroy(verifier_cb_ctx *ctx) {
if (ctx->audience != NULL) gpr_free(ctx->audience);
if (ctx->claims != NULL) grpc_jwt_claims_destroy(ctx->claims);
gpr_slice_unref(ctx->signature);
gpr_slice_unref(ctx->signed_data);
jose_header_destroy(ctx->header);
/* TODO: see what to do with claims... */
gpr_free(ctx);
}
/* --- grpc_jwt_verifier object. --- */
/* Clock skew defaults to one minute. */
gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0};
/* Max delay defaults to one minute. */
gpr_timespec grpc_jwt_verifier_max_delay = {60, 0};
typedef struct {
char *email_domain;
char *key_url_prefix;
} email_key_mapping;
struct grpc_jwt_verifier {
email_key_mapping *mappings;
size_t num_mappings; /* Should be very few, linear search ok. */
size_t allocated_mappings;
grpc_httpcli_context http_ctx;
};
static grpc_json *json_from_http(const grpc_httpcli_response *response) {
grpc_json *json = NULL;
if (response == NULL) {
gpr_log(GPR_ERROR, "HTTP response is NULL.");
return NULL;
}
if (response->status != 200) {
gpr_log(GPR_ERROR, "Call to http server failed with error %d.",
response->status);
return NULL;
}
json = grpc_json_parse_string_with_len(response->body, response->body_length);
if (json == NULL) {
gpr_log(GPR_ERROR, "Invalid JSON found in response.");
}
return json;
}
static const grpc_json *find_property_by_name(const grpc_json *json,
const char *name) {
const grpc_json *cur;
for (cur = json->child; cur != NULL; cur = cur->next) {
if (strcmp(cur->key, name) == 0) return cur;
}
return NULL;
}
static EVP_PKEY *extract_pkey_from_x509(const char *x509_str) {
X509 *x509 = NULL;
EVP_PKEY *result = NULL;
BIO *bio = BIO_new(BIO_s_mem());
BIO_write(bio, x509_str, strlen(x509_str));
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
if (x509 == NULL) {
gpr_log(GPR_ERROR, "Unable to parse x509 cert.");
goto end;
}
result = X509_get_pubkey(x509);
if (result == NULL) {
gpr_log(GPR_ERROR, "Cannot find public key in X509 cert.");
}
end:
BIO_free(bio);
if (x509 != NULL) X509_free(x509);
return result;
}
static BIGNUM *bignum_from_base64(const char *b64) {
BIGNUM *result = NULL;
gpr_slice bin;
if (b64 == NULL) return NULL;
bin = grpc_base64_decode(b64, 1);
if (GPR_SLICE_IS_EMPTY(bin)) {
gpr_log(GPR_ERROR, "Invalid base64 for big num.");
return NULL;
}
result = BN_bin2bn(GPR_SLICE_START_PTR(bin), GPR_SLICE_LENGTH(bin), NULL);
gpr_slice_unref(bin);
return result;
}
static EVP_PKEY *pkey_from_jwk(const grpc_json *json, const char *kty) {
const grpc_json *key_prop;
RSA *rsa = NULL;
EVP_PKEY *result = NULL;
GPR_ASSERT(kty != NULL && json != NULL);
if (strcmp(kty, "RSA") != 0) {
gpr_log(GPR_ERROR, "Unsupported key type %s.", kty);
goto end;
}
rsa = RSA_new();
if (rsa == NULL) {
gpr_log(GPR_ERROR, "Could not create rsa key.");
goto end;
}
for (key_prop = json->child; key_prop != NULL; key_prop = key_prop->next) {
if (strcmp(key_prop->key, "n") == 0) {
rsa->n = bignum_from_base64(validate_string_field(key_prop, "n"));
if (rsa->n == NULL) goto end;
} else if (strcmp(key_prop->key, "e") == 0) {
rsa->e = bignum_from_base64(validate_string_field(key_prop, "e"));
if (rsa->e == NULL) goto end;
}
}
if (rsa->e == NULL || rsa->n == NULL) {
gpr_log(GPR_ERROR, "Missing RSA public key field.");
goto end;
}
result = EVP_PKEY_new();
EVP_PKEY_set1_RSA(result, rsa); /* uprefs rsa. */
end:
if (rsa != NULL) RSA_free(rsa);
return result;
}
static EVP_PKEY *find_verification_key(const grpc_json *json,
const char *header_alg,
const char *header_kid) {
const grpc_json *jkey;
const grpc_json *jwk_keys;
/* Try to parse the json as a JWK set:
https://tools.ietf.org/html/rfc7517#section-5. */
jwk_keys = find_property_by_name(json, "keys");
if (jwk_keys == NULL) {
/* Use the google proprietary format which is:
{ <kid1>: <x5091>, <kid2>: <x5092>, ... } */
const grpc_json *cur = find_property_by_name(json, header_kid);
if (cur == NULL) return NULL;
return extract_pkey_from_x509(cur->value);
}
if (jwk_keys->type != GRPC_JSON_ARRAY) {
gpr_log(GPR_ERROR,
"Unexpected value type of keys property in jwks key set.");
return NULL;
}
/* Key format is specified in:
https://tools.ietf.org/html/rfc7518#section-6. */
for (jkey = jwk_keys->child; jkey != NULL; jkey = jkey->next) {
grpc_json *key_prop;
const char *alg = NULL;
const char *kid = NULL;
const char *kty = NULL;
if (jkey->type != GRPC_JSON_OBJECT) continue;
for (key_prop = jkey->child; key_prop != NULL; key_prop = key_prop->next) {
if (strcmp(key_prop->key, "alg") == 0 &&
key_prop->type == GRPC_JSON_STRING) {
alg = key_prop->value;
} else if (strcmp(key_prop->key, "kid") == 0 &&
key_prop->type == GRPC_JSON_STRING) {
kid = key_prop->value;
} else if (strcmp(key_prop->key, "kty") == 0 &&
key_prop->type == GRPC_JSON_STRING) {
kty = key_prop->value;
}
}
if (alg != NULL && kid != NULL && kty != NULL &&
strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) {
return pkey_from_jwk(jkey, kty);
}
}
gpr_log(GPR_ERROR,
"Could not find matching key in key set for kid=%s and alg=%s",
header_kid, header_alg);
return NULL;
}
static int verify_jwt_signature(EVP_PKEY *key, const char *alg,
gpr_slice signature, gpr_slice signed_data) {
EVP_MD_CTX *md_ctx = EVP_MD_CTX_create();
const EVP_MD *md = evp_md_from_alg(alg);
int result = 0;
GPR_ASSERT(md != NULL); /* Checked before. */
if (md_ctx == NULL) {
gpr_log(GPR_ERROR, "Could not create EVP_MD_CTX.");
goto end;
}
if (EVP_DigestVerifyInit(md_ctx, NULL, md, NULL, key) != 1) {
gpr_log(GPR_ERROR, "EVP_DigestVerifyInit failed.");
goto end;
}
if (EVP_DigestVerifyUpdate(md_ctx, GPR_SLICE_START_PTR(signed_data),
GPR_SLICE_LENGTH(signed_data)) != 1) {
gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed.");
goto end;
}
if (EVP_DigestVerifyFinal(md_ctx, GPR_SLICE_START_PTR(signature),
GPR_SLICE_LENGTH(signature)) != 1) {
gpr_log(GPR_ERROR, "JWT signature verification failed.");
goto end;
}
result = 1;
end:
if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx);
return result;
}
static void on_keys_retrieved(void *user_data,
const grpc_httpcli_response *response) {
grpc_json *json = json_from_http(response);
verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
EVP_PKEY *verification_key = NULL;
grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR;
grpc_jwt_claims *claims = NULL;
if (json == NULL) {
status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
goto end;
}
verification_key =
find_verification_key(json, ctx->header->alg, ctx->header->kid);
if (verification_key == NULL) {
gpr_log(GPR_ERROR, "Could not find verification key with kid %s.",
ctx->header->kid);
status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
goto end;
}
if (!verify_jwt_signature(verification_key, ctx->header->alg, ctx->signature,
ctx->signed_data)) {
status = GRPC_JWT_VERIFIER_BAD_SIGNATURE;
goto end;
}
status = grpc_jwt_claims_check(ctx->claims, ctx->audience);
if (status == GRPC_JWT_VERIFIER_OK) {
/* Pass ownership. */
claims = ctx->claims;
ctx->claims = NULL;
}
end:
if (json != NULL) grpc_json_destroy(json);
if (verification_key != NULL) EVP_PKEY_free(verification_key);
ctx->user_cb(ctx->user_data, status, claims);
verifier_cb_ctx_destroy(ctx);
}
static void on_openid_config_retrieved(void *user_data,
const grpc_httpcli_response *response) {
const grpc_json* cur;
grpc_json *json = json_from_http(response);
verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data;
grpc_httpcli_request req;
const char *jwks_uri;
/* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time.*/
if (json == NULL) goto error;
cur = find_property_by_name(json, "jwks_uri");
if (cur == NULL) {
gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config.");
goto error;
}
jwks_uri = validate_string_field(cur, "jwks_uri");
if (jwks_uri == NULL) goto error;
if (strstr(jwks_uri, "https://") != jwks_uri) {
gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri);
goto error;
}
jwks_uri += 8;
req.use_ssl = 1;
req.host = gpr_strdup(jwks_uri);
req.path = strchr(jwks_uri, '/');
if (req.path == NULL) {
req.path = "";
} else {
*(req.host + (req.path - jwks_uri)) = '\0';
}
grpc_httpcli_get(&ctx->verifier->http_ctx, ctx->pollset, &req,
gpr_time_add(gpr_now(), grpc_jwt_verifier_max_delay),
on_keys_retrieved, ctx);
grpc_json_destroy(json);
gpr_free(req.host);
return;
error:
if (json != NULL) grpc_json_destroy(json);
ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL);
verifier_cb_ctx_destroy(ctx);
}
static email_key_mapping *verifier_get_mapping(
grpc_jwt_verifier *v, const char *email_domain) {
size_t i;
if (v->mappings == NULL) return NULL;
for (i = 0; i < v->num_mappings; i++) {
if (strcmp(email_domain, v->mappings[i].email_domain) == 0) {
return &v->mappings[i];
}
}
return NULL;
}
static void verifier_put_mapping(grpc_jwt_verifier *v, const char *email_domain,
const char *key_url_prefix) {
email_key_mapping *mapping = verifier_get_mapping(v, email_domain);
GPR_ASSERT(v->num_mappings < v->allocated_mappings);
if (mapping != NULL) {
gpr_free(mapping->key_url_prefix);
mapping->key_url_prefix = gpr_strdup(key_url_prefix);
return;
}
v->mappings[v->num_mappings].email_domain = gpr_strdup(email_domain);
v->mappings[v->num_mappings].key_url_prefix = gpr_strdup(key_url_prefix);
v->num_mappings++;
GPR_ASSERT(v->num_mappings <= v->allocated_mappings);
}
/* Takes ownership of ctx. */
static void retrieve_key_and_verify(verifier_cb_ctx *ctx) {
const char *at_sign;
grpc_httpcli_response_cb http_cb;
char *path_prefix = NULL;
const char *iss;
grpc_httpcli_request req;
memset(&req, 0, sizeof(grpc_httpcli_request));
req.use_ssl = 1;
GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL);
iss = ctx->claims->iss;
if (ctx->header->kid == NULL) {
gpr_log(GPR_ERROR, "Missing kid in jose header.");
goto error;
}
if (iss == NULL) {
gpr_log(GPR_ERROR, "Missing iss in claims.");
goto error;
}
/* This code relies on:
https://openid.net/specs/openid-connect-discovery-1_0.html
Nobody seems to implement the account/email/webfinger part 2. of the spec
so we will rely instead on email/url mappings if we detect such an issuer.
Part 4, on the other hand is implemented by both google and salesforce. */
/* Very non-sophisticated way to detect an email address. Should be good
enough for now... */
at_sign = strchr(iss, '@');
if (at_sign != NULL) {
email_key_mapping *mapping;
const char *email_domain = at_sign + 1;
GPR_ASSERT(ctx->verifier != NULL);
mapping = verifier_get_mapping(ctx->verifier, email_domain);
if (mapping == NULL) {
gpr_log(GPR_ERROR, "Missing mapping for issuer email.");
goto error;
}
req.host = gpr_strdup(mapping->key_url_prefix);
path_prefix = strchr(req.host, '/');
if (path_prefix == NULL) {
gpr_asprintf(&req.path, "/%s", iss);
} else {
*(path_prefix++) = '\0';
gpr_asprintf(&req.path, "/%s/%s", path_prefix, iss);
}
http_cb = on_keys_retrieved;
} else {
req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss);
path_prefix = strchr(req.host, '/');
if (path_prefix == NULL) {
req.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX);
} else {
*(path_prefix++) = 0;
gpr_asprintf(&req.path, "/%s%s", path_prefix,
GRPC_OPENID_CONFIG_URL_SUFFIX);
}
http_cb = on_openid_config_retrieved;
}
grpc_httpcli_get(&ctx->verifier->http_ctx, ctx->pollset, &req,
gpr_time_add(gpr_now(), grpc_jwt_verifier_max_delay),
http_cb, ctx);
gpr_free(req.host);
gpr_free(req.path);
return;
error:
ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL);
verifier_cb_ctx_destroy(ctx);
}
void grpc_jwt_verifier_verify(grpc_jwt_verifier *verifier,
grpc_pollset *pollset, const char *jwt,
const char *audience,
grpc_jwt_verification_done_cb cb,
void *user_data) {
const char *dot = NULL;
grpc_json *json;
jose_header *header = NULL;
grpc_jwt_claims *claims = NULL;
gpr_slice header_buffer;
gpr_slice claims_buffer;
gpr_slice signature;
size_t signed_jwt_len;
const char *cur = jwt;
GPR_ASSERT(verifier != NULL && jwt != NULL && audience != NULL && cb != NULL);
dot = strchr(cur, '.');
if (dot == NULL) goto error;
json = parse_json_part_from_jwt(cur, dot - cur, &header_buffer);
if (json == NULL) goto error;
header = jose_header_from_json(json, header_buffer);
if (header == NULL) goto error;
cur = dot + 1;
dot = strchr(cur, '.');
if (dot == NULL) goto error;
json = parse_json_part_from_jwt(cur, dot - cur, &claims_buffer);
if (json == NULL) goto error;
claims = grpc_jwt_claims_from_json(json, claims_buffer);
if (claims == NULL) goto error;
signed_jwt_len = (size_t)(dot - jwt);
cur = dot + 1;
signature = grpc_base64_decode(cur, 1);
if (GPR_SLICE_IS_EMPTY(signature)) goto error;
retrieve_key_and_verify(
verifier_cb_ctx_create(verifier, pollset, header, claims, audience,
signature, jwt, signed_jwt_len, user_data, cb));
return;
error:
if (header != NULL) jose_header_destroy(header);
if (claims != NULL) grpc_jwt_claims_destroy(claims);
cb(user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, NULL);
}
grpc_jwt_verifier *grpc_jwt_verifier_create(
const grpc_jwt_verifier_email_domain_key_url_mapping *mappings,
size_t num_mappings) {
grpc_jwt_verifier *v = gpr_malloc(sizeof(grpc_jwt_verifier));
memset(v, 0, sizeof(grpc_jwt_verifier));
grpc_httpcli_context_init(&v->http_ctx);
/* We know at least of one mapping. */
v->allocated_mappings = 1 + num_mappings;
v->mappings = gpr_malloc(v->allocated_mappings * sizeof(email_key_mapping));
verifier_put_mapping(v, GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN,
GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX);
/* User-Provided mappings. */
if (mappings != NULL) {
size_t i;
for (i = 0; i < num_mappings; i++) {
verifier_put_mapping(v, mappings[i].email_domain,
mappings[i].key_url_prefix);
}
}
return v;
}
void grpc_jwt_verifier_destroy(grpc_jwt_verifier *v) {
size_t i;
if (v == NULL) return;
grpc_httpcli_context_destroy(&v->http_ctx);
if (v->mappings != NULL) {
for (i = 0; i < v->num_mappings; i++) {
gpr_free(v->mappings[i].email_domain);
gpr_free(v->mappings[i].key_url_prefix);
}
gpr_free(v->mappings);
}
gpr_free(v);
}

@ -0,0 +1,136 @@
/*
*
* Copyright 2015, 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 disclaimser.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimser
* 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.
*
*/
#ifndef GRPC_INTERNAL_CORE_SECURITY_JWT_VERIFIER_H
#define GRPC_INTERNAL_CORE_SECURITY_JWT_VERIFIER_H
#include "src/core/iomgr/pollset.h"
#include "src/core/json/json.h"
#include <grpc/support/slice.h>
#include <grpc/support/time.h>
/* --- Constants. --- */
#define GRPC_OPENID_CONFIG_URL_SUFFIX "/.well-known/openid-configuration"
#define GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN \
"developer.gserviceaccount.com"
#define GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX \
"www.googleapis.com/robot/v1/metadata/x509"
/* --- grpc_jwt_verifier_status. --- */
typedef enum {
GRPC_JWT_VERIFIER_OK = 0,
GRPC_JWT_VERIFIER_BAD_SIGNATURE,
GRPC_JWT_VERIFIER_BAD_FORMAT,
GRPC_JWT_VERIFIER_BAD_AUDIENCE,
GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR,
GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE,
GRPC_JWT_VERIFIER_GENERIC_ERROR
} grpc_jwt_verifier_status;
const char *grpc_jwt_verifier_status_to_string(grpc_jwt_verifier_status status);
/* --- grpc_jwt_claims. --- */
typedef struct grpc_jwt_claims grpc_jwt_claims;
void grpc_jwt_claims_destroy(grpc_jwt_claims *claims);
/* Returns the whole JSON tree of the claims. */
const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims);
/* Access to registered claims in https://tools.ietf.org/html/rfc7519#page-9 */
const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims);
const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims);
const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims);
const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims);
gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims);
gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims);
gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims);
/* --- grpc_jwt_verifier. --- */
typedef struct grpc_jwt_verifier grpc_jwt_verifier;
typedef struct {
/* The email domain is the part after the @ sign. */
const char *email_domain;
/* The key url prefix will be used to get the public key from the issuer:
https://<key_url_prefix>/<issuer_email>
Therefore the key_url_prefix must NOT contain https://. */
const char *key_url_prefix;
} grpc_jwt_verifier_email_domain_key_url_mapping;
/* Globals to control the verifier. Not thread-safe. */
extern gpr_timespec grpc_jwt_verifier_clock_skew;
extern gpr_timespec grpc_jwt_verifier_max_delay;
/* The verifier can be created with some custom mappings to help with key
discovery in the case where the issuer is an email address.
mappings can be NULL in which case num_mappings MUST be 0.
A verifier object has one built-in mapping (unless overridden):
GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN ->
GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX.*/
grpc_jwt_verifier *grpc_jwt_verifier_create(
const grpc_jwt_verifier_email_domain_key_url_mapping *mappings,
size_t num_mappings);
/*The verifier must not be destroyed if there are still outstanding callbacks.*/
void grpc_jwt_verifier_destroy(grpc_jwt_verifier *verifier);
/* User provided callback that will be called when the verification of the JWT
is done (maybe in another thread).
It is the responsibility of the callee to call grpc_jwt_claims_destroy on
the claims. */
typedef void (*grpc_jwt_verification_done_cb)(void *user_data,
grpc_jwt_verifier_status status,
grpc_jwt_claims *claims);
/* Verifies for the JWT for the given expected audience. */
void grpc_jwt_verifier_verify(grpc_jwt_verifier *verifier,
grpc_pollset *pollset, const char *jwt,
const char *audience,
grpc_jwt_verification_done_cb cb,
void *user_data);
/* --- TESTING ONLY exposed functions. --- */
grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_json *json, gpr_slice buffer);
grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims,
const char *audience);
#endif /* GRPC_INTERNAL_CORE_SECURITY_JWT_VERIFIER_H */

@ -0,0 +1,565 @@
/*
*
* Copyright 2015, 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/jwt_verifier.h"
#include <string.h>
#include "src/core/httpcli/httpcli.h"
#include "src/core/security/base64.h"
#include "src/core/security/json_token.h"
#include "test/core/util/test_config.h"
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/slice.h>
#include <grpc/support/string_util.h>
/* This JSON key was generated with the GCE console and revoked immediately.
The identifiers have been changed as well.
Maximum size for a string literal is 509 chars in C89, yay! */
static const char json_key_str_part1[] =
"{ \"private_key\": \"-----BEGIN PRIVATE KEY-----"
"\\nMIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOEvJsnoHnyHkXcp\\n7mJE"
"qg"
"WGjiw71NfXByguekSKho65FxaGbsnSM9SMQAqVk7Q2rG+I0OpsT0LrWQtZ\\nyjSeg/"
"rWBQvS4hle4LfijkP3J5BG+"
"IXDMP8RfziNRQsenAXDNPkY4kJCvKux2xdD\\nOnVF6N7dL3nTYZg+"
"uQrNsMTz9UxVAgMBAAECgYEAzbLewe1xe9vy+2GoSsfib+28\\nDZgSE6Bu/"
"zuFoPrRc6qL9p2SsnV7txrunTyJkkOnPLND9ABAXybRTlcVKP/sGgza\\n/"
"8HpCqFYM9V8f34SBWfD4fRFT+n/"
"73cfRUtGXdXpseva2lh8RilIQfPhNZAncenU\\ngqXjDvpkypEusgXAykECQQD+";
static const char json_key_str_part2[] =
"53XxNVnxBHsYb+AYEfklR96yVi8HywjVHP34+OQZ\\nCslxoHQM8s+"
"dBnjfScLu22JqkPv04xyxmt0QAKm9+vTdAkEA4ib7YvEAn2jXzcCI\\nEkoy2L/"
"XydR1GCHoacdfdAwiL2npOdnbvi4ZmdYRPY1LSTO058tQHKVXV7NLeCa3\\nAARh2QJBAMKeDA"
"G"
"W303SQv2cZTdbeaLKJbB5drz3eo3j7dDKjrTD9JupixFbzcGw\\n8FZi5c8idxiwC36kbAL6Hz"
"A"
"ZoX+ofI0CQE6KCzPJTtYNqyShgKAZdJ8hwOcvCZtf\\n6z8RJm0+"
"6YBd38lfh5j8mZd7aHFf6I17j5AQY7oPEc47TjJj/"
"5nZ68ECQQDvYuI3\\nLyK5fS8g0SYbmPOL9TlcHDOqwG0mrX9qpg5DC2fniXNSrrZ64GTDKdzZ"
"Y"
"Ap6LI9W\\nIqv4vr6y38N79TTC\\n-----END PRIVATE KEY-----\\n\", ";
static const char json_key_str_part3_for_google_email_issuer[] =
"\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_email\": "
"\"777-abaslkan11hlb6nmim3bpspl31ud@developer.gserviceaccount."
"com\", \"client_id\": "
"\"777-abaslkan11hlb6nmim3bpspl31ud.apps.googleusercontent."
"com\", \"type\": \"service_account\" }";
/* Trick our JWT library into issuing a JWT with iss=accounts.google.com. */
static const char json_key_str_part3_for_url_issuer[] =
"\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_email\": \"accounts.google.com\", "
"\"client_id\": "
"\"777-abaslkan11hlb6nmim3bpspl31ud.apps.googleusercontent."
"com\", \"type\": \"service_account\" }";
static const char json_key_str_part3_for_custom_email_issuer[] =
"\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
"\"client_email\": "
"\"foo@bar.com\", \"client_id\": "
"\"777-abaslkan11hlb6nmim3bpspl31ud.apps.googleusercontent."
"com\", \"type\": \"service_account\" }";
static grpc_jwt_verifier_email_domain_key_url_mapping custom_mapping = {
"bar.com", "keys.bar.com/jwk"
};
static const char expected_user_data[] = "user data";
static const char good_jwk_set[] =
"{"
" \"keys\": ["
" {"
" \"kty\": \"RSA\","
" \"alg\": \"RS256\","
" \"use\": \"sig\","
" \"kid\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\","
" \"n\": "
"\"4S8myegefIeRdynuYkSqBYaOLDvU19cHKC56RIqGjrkXFoZuydIz1IxACpWTtDasb4jQ6mxP"
"QutZC1nKNJ6D-tYFC9LiGV7gt-KOQ_cnkEb4hcMw_xF_OI1FCx6cBcM0-"
"RjiQkK8q7HbF0M6dUXo3t0vedNhmD65Cs2wxPP1TFU=\","
" \"e\": \"AQAB\""
" }"
" ]"
"}";
static gpr_timespec expected_lifetime = {3600, 0};
static const char good_google_email_keys_part1[] =
"{\"e6b5137873db8d2ef81e06a47289e6434ec8a165\": \"-----BEGIN "
"CERTIFICATE-----"
"\\nMIICATCCAWoCCQDEywLhxvHjnDANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB\\nVTET"
"MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0\\ncyBQdHkgTHR"
"kMB4XDTE1MDYyOTA4Mzk1MFoXDTI1MDYyNjA4Mzk1MFowRTELMAkG\\nA1UEBhMCQVUxEzARBg"
"NVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0\\nIFdpZGdpdHMgUHR5IEx0ZDCBn"
"zANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4S8m\\nyegefIeRdynuYkSqBYaOLDvU19cHKC56"
"RIqGjrkXFoZuydIz1IxACpWTtDasb4jQ\\n6mxPQutZC1nKNJ6D+tYFC9LiGV7gt+KOQ/";
static const char good_google_email_keys_part2[] =
"cnkEb4hcMw/xF/OI1FCx6cBcM0+"
"Rji\\nQkK8q7HbF0M6dUXo3t0vedNhmD65Cs2wxPP1TFUCAwEAATANBgkqhkiG9w0BAQsF\\nA"
"AOBgQBfu69FkPmBknbKNFgurPz78kbs3VNN+k/"
"PUgO5DHKskJmgK2TbtvX2VMpx\\nkftmHGzgzMzUlOtigCaGMgHWjfqjpP9uuDbahXrZBJzB8c"
"Oq7MrQF8r17qVvo3Ue\\nPjTKQMAsU8uxTEMmeuz9L6yExs0rfd6bPOrQkAoVfFfiYB3/"
"pA==\\n-----END CERTIFICATE-----\\n\"}";
static const char expected_audience[] = "https://foo.com";
static const char good_openid_config[] =
"{"
" \"issuer\": \"https://accounts.google.com\","
" \"authorization_endpoint\": "
"\"https://accounts.google.com/o/oauth2/v2/auth\","
" \"token_endpoint\": \"https://www.googleapis.com/oauth2/v4/token\","
" \"userinfo_endpoint\": \"https://www.googleapis.com/oauth2/v3/userinfo\","
" \"revocation_endpoint\": \"https://accounts.google.com/o/oauth2/revoke\","
" \"jwks_uri\": \"https://www.googleapis.com/oauth2/v3/certs\""
"}";
static const char expired_claims[] =
"{ \"aud\": \"https://foo.com\","
" \"iss\": \"blah.foo.com\","
" \"sub\": \"juju@blah.foo.com\","
" \"jti\": \"jwtuniqueid\","
" \"iat\": 100," /* Way back in the past... */
" \"exp\": 120,"
" \"nbf\": 60,"
" \"foo\": \"bar\"}";
static const char claims_without_time_constraint[] =
"{ \"aud\": \"https://foo.com\","
" \"iss\": \"blah.foo.com\","
" \"sub\": \"juju@blah.foo.com\","
" \"jti\": \"jwtuniqueid\","
" \"foo\": \"bar\"}";
static const char invalid_claims[] =
"{ \"aud\": \"https://foo.com\","
" \"iss\": 46," /* Issuer cannot be a number. */
" \"sub\": \"juju@blah.foo.com\","
" \"jti\": \"jwtuniqueid\","
" \"foo\": \"bar\"}";
typedef struct {
grpc_jwt_verifier_status expected_status;
const char *expected_issuer;
const char *expected_subject;
} verifier_test_config;
static void test_claims_success(void) {
grpc_jwt_claims *claims;
gpr_slice s = gpr_slice_from_copied_string(claims_without_time_constraint);
grpc_json *json = grpc_json_parse_string_with_len(
(char *)GPR_SLICE_START_PTR(s), GPR_SLICE_LENGTH(s));
GPR_ASSERT(json != NULL);
claims = grpc_jwt_claims_from_json(json, s);
GPR_ASSERT(claims != NULL);
GPR_ASSERT(grpc_jwt_claims_json(claims) == json);
GPR_ASSERT(strcmp(grpc_jwt_claims_audience(claims), "https://foo.com") == 0);
GPR_ASSERT(strcmp(grpc_jwt_claims_issuer(claims), "blah.foo.com") == 0);
GPR_ASSERT(strcmp(grpc_jwt_claims_subject(claims), "juju@blah.foo.com") == 0);
GPR_ASSERT(strcmp(grpc_jwt_claims_id(claims), "jwtuniqueid") == 0);
GPR_ASSERT(grpc_jwt_claims_check(claims, "https://foo.com") ==
GRPC_JWT_VERIFIER_OK);
grpc_jwt_claims_destroy(claims);
}
static void test_expired_claims_failure(void) {
grpc_jwt_claims *claims;
gpr_slice s = gpr_slice_from_copied_string(expired_claims);
grpc_json *json = grpc_json_parse_string_with_len(
(char *)GPR_SLICE_START_PTR(s), GPR_SLICE_LENGTH(s));
gpr_timespec exp_iat = {100, 0};
gpr_timespec exp_exp = {120, 0};
gpr_timespec exp_nbf = {60, 0};
GPR_ASSERT(json != NULL);
claims = grpc_jwt_claims_from_json(json, s);
GPR_ASSERT(claims != NULL);
GPR_ASSERT(grpc_jwt_claims_json(claims) == json);
GPR_ASSERT(strcmp(grpc_jwt_claims_audience(claims), "https://foo.com") == 0);
GPR_ASSERT(strcmp(grpc_jwt_claims_issuer(claims), "blah.foo.com") == 0);
GPR_ASSERT(strcmp(grpc_jwt_claims_subject(claims), "juju@blah.foo.com") == 0);
GPR_ASSERT(strcmp(grpc_jwt_claims_id(claims), "jwtuniqueid") == 0);
GPR_ASSERT(gpr_time_cmp(grpc_jwt_claims_issued_at(claims), exp_iat) == 0);
GPR_ASSERT(gpr_time_cmp(grpc_jwt_claims_expires_at(claims), exp_exp) == 0);
GPR_ASSERT(gpr_time_cmp(grpc_jwt_claims_not_before(claims), exp_nbf) == 0);
GPR_ASSERT(grpc_jwt_claims_check(claims, "https://foo.com") ==
GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE);
grpc_jwt_claims_destroy(claims);
}
static void test_invalid_claims_failure(void) {
gpr_slice s = gpr_slice_from_copied_string(invalid_claims);
grpc_json *json = grpc_json_parse_string_with_len(
(char *)GPR_SLICE_START_PTR(s), GPR_SLICE_LENGTH(s));
GPR_ASSERT(grpc_jwt_claims_from_json(json, s) == NULL);
}
static void test_bad_audience_claims_failure(void) {
grpc_jwt_claims *claims;
gpr_slice s = gpr_slice_from_copied_string(claims_without_time_constraint);
grpc_json *json = grpc_json_parse_string_with_len(
(char *)GPR_SLICE_START_PTR(s), GPR_SLICE_LENGTH(s));
GPR_ASSERT(json != NULL);
claims = grpc_jwt_claims_from_json(json, s);
GPR_ASSERT(claims != NULL);
GPR_ASSERT(grpc_jwt_claims_check(claims, "https://bar.com") ==
GRPC_JWT_VERIFIER_BAD_AUDIENCE);
grpc_jwt_claims_destroy(claims);
}
static char *json_key_str(const char *last_part) {
size_t result_len = strlen(json_key_str_part1) + strlen(json_key_str_part2) +
strlen(last_part);
char *result = gpr_malloc(result_len + 1);
char *current = result;
strcpy(result, json_key_str_part1);
current += strlen(json_key_str_part1);
strcpy(current, json_key_str_part2);
current += strlen(json_key_str_part2);
strcpy(current, last_part);
return result;
}
static char *good_google_email_keys(void) {
size_t result_len = strlen(good_google_email_keys_part1) +
strlen(good_google_email_keys_part2);
char *result = gpr_malloc(result_len + 1);
char *current = result;
strcpy(result, good_google_email_keys_part1);
current += strlen(good_google_email_keys_part1);
strcpy(current, good_google_email_keys_part2);
return result;
}
static grpc_httpcli_response http_response(int status, char *body) {
grpc_httpcli_response response;
memset(&response, 0, sizeof(grpc_httpcli_response));
response.status = status;
response.body = body;
response.body_length = strlen(body);
return response;
}
static int httpcli_post_should_not_be_called(
const grpc_httpcli_request *request, const char *body_bytes,
size_t body_size, gpr_timespec deadline,
grpc_httpcli_response_cb on_response, void *user_data) {
GPR_ASSERT("HTTP POST should not be called" == NULL);
return 1;
}
static int httpcli_get_google_keys_for_email(
const grpc_httpcli_request *request, gpr_timespec deadline,
grpc_httpcli_response_cb on_response, void *user_data) {
grpc_httpcli_response response = http_response(200, good_google_email_keys());
GPR_ASSERT(request->use_ssl);
GPR_ASSERT(strcmp(request->host, "www.googleapis.com") == 0);
GPR_ASSERT(strcmp(request->path,
"/robot/v1/metadata/x509/"
"777-abaslkan11hlb6nmim3bpspl31ud@developer."
"gserviceaccount.com") == 0);
on_response(user_data, &response);
gpr_free(response.body);
return 1;
}
static void on_verification_success(void *user_data,
grpc_jwt_verifier_status status,
grpc_jwt_claims *claims) {
GPR_ASSERT(status == GRPC_JWT_VERIFIER_OK);
GPR_ASSERT(claims != NULL);
GPR_ASSERT(user_data == (void *)expected_user_data);
GPR_ASSERT(strcmp(grpc_jwt_claims_audience(claims), expected_audience) == 0);
grpc_jwt_claims_destroy(claims);
}
static void test_jwt_verifier_google_email_issuer_success(void) {
grpc_jwt_verifier *verifier = grpc_jwt_verifier_create(NULL, 0);
char *jwt = NULL;
char *key_str = json_key_str(json_key_str_part3_for_google_email_issuer);
grpc_auth_json_key key = grpc_auth_json_key_create_from_string(key_str);
gpr_free(key_str);
GPR_ASSERT(grpc_auth_json_key_is_valid(&key));
grpc_httpcli_set_override(httpcli_get_google_keys_for_email,
httpcli_post_should_not_be_called);
jwt =
grpc_jwt_encode_and_sign(&key, expected_audience, expected_lifetime, NULL);
grpc_auth_json_key_destruct(&key);
GPR_ASSERT(jwt != NULL);
grpc_jwt_verifier_verify(verifier, NULL, jwt, expected_audience,
on_verification_success, (void *)expected_user_data);
gpr_free(jwt);
grpc_jwt_verifier_destroy(verifier);
grpc_httpcli_set_override(NULL, NULL);
}
static int httpcli_get_custom_keys_for_email(
const grpc_httpcli_request *request, gpr_timespec deadline,
grpc_httpcli_response_cb on_response, void *user_data) {
grpc_httpcli_response response = http_response(200, gpr_strdup(good_jwk_set));
GPR_ASSERT(request->use_ssl);
GPR_ASSERT(strcmp(request->host, "keys.bar.com") == 0);
GPR_ASSERT(strcmp(request->path, "/jwk/foo@bar.com") == 0);
on_response(user_data, &response);
gpr_free(response.body);
return 1;
}
static void test_jwt_verifier_custom_email_issuer_success(void) {
grpc_jwt_verifier *verifier = grpc_jwt_verifier_create(&custom_mapping, 1);
char *jwt = NULL;
char *key_str = json_key_str(json_key_str_part3_for_custom_email_issuer);
grpc_auth_json_key key = grpc_auth_json_key_create_from_string(key_str);
gpr_free(key_str);
GPR_ASSERT(grpc_auth_json_key_is_valid(&key));
grpc_httpcli_set_override(httpcli_get_custom_keys_for_email,
httpcli_post_should_not_be_called);
jwt =
grpc_jwt_encode_and_sign(&key, expected_audience, expected_lifetime, NULL);
grpc_auth_json_key_destruct(&key);
GPR_ASSERT(jwt != NULL);
grpc_jwt_verifier_verify(verifier, NULL, jwt, expected_audience,
on_verification_success, (void *)expected_user_data);
gpr_free(jwt);
grpc_jwt_verifier_destroy(verifier);
grpc_httpcli_set_override(NULL, NULL);
}
static int httpcli_get_jwk_set(
const grpc_httpcli_request *request, gpr_timespec deadline,
grpc_httpcli_response_cb on_response, void *user_data) {
grpc_httpcli_response response = http_response(200, gpr_strdup(good_jwk_set));
GPR_ASSERT(request->use_ssl);
GPR_ASSERT(strcmp(request->host, "www.googleapis.com") == 0);
GPR_ASSERT(strcmp(request->path, "/oauth2/v3/certs") == 0);
on_response(user_data, &response);
gpr_free(response.body);
return 1;
}
static int httpcli_get_openid_config(const grpc_httpcli_request *request,
gpr_timespec deadline,
grpc_httpcli_response_cb on_response,
void *user_data) {
grpc_httpcli_response response =
http_response(200, gpr_strdup(good_openid_config));
GPR_ASSERT(request->use_ssl);
GPR_ASSERT(strcmp(request->host, "accounts.google.com") == 0);
GPR_ASSERT(strcmp(request->path, GRPC_OPENID_CONFIG_URL_SUFFIX) == 0);
grpc_httpcli_set_override(httpcli_get_jwk_set,
httpcli_post_should_not_be_called);
on_response(user_data, &response);
gpr_free(response.body);
return 1;
}
static void test_jwt_verifier_url_issuer_success(void) {
grpc_jwt_verifier *verifier = grpc_jwt_verifier_create(NULL, 0);
char *jwt = NULL;
char *key_str = json_key_str(json_key_str_part3_for_url_issuer);
grpc_auth_json_key key = grpc_auth_json_key_create_from_string(key_str);
gpr_free(key_str);
GPR_ASSERT(grpc_auth_json_key_is_valid(&key));
grpc_httpcli_set_override(httpcli_get_openid_config,
httpcli_post_should_not_be_called);
jwt =
grpc_jwt_encode_and_sign(&key, expected_audience, expected_lifetime, NULL);
grpc_auth_json_key_destruct(&key);
GPR_ASSERT(jwt != NULL);
grpc_jwt_verifier_verify(verifier, NULL, jwt, expected_audience,
on_verification_success, (void *)expected_user_data);
gpr_free(jwt);
grpc_jwt_verifier_destroy(verifier);
grpc_httpcli_set_override(NULL, NULL);
}
static void on_verification_key_retrieval_error(void *user_data,
grpc_jwt_verifier_status status,
grpc_jwt_claims *claims) {
GPR_ASSERT(status == GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR);
GPR_ASSERT(claims == NULL);
GPR_ASSERT(user_data == (void *)expected_user_data);
}
static int httpcli_get_bad_json(const grpc_httpcli_request *request,
gpr_timespec deadline,
grpc_httpcli_response_cb on_response,
void *user_data) {
grpc_httpcli_response response =
http_response(200, gpr_strdup("{\"bad\": \"stuff\"}"));
GPR_ASSERT(request->use_ssl);
on_response(user_data, &response);
gpr_free(response.body);
return 1;
}
static void test_jwt_verifier_url_issuer_bad_config(void) {
grpc_jwt_verifier *verifier = grpc_jwt_verifier_create(NULL, 0);
char *jwt = NULL;
char *key_str = json_key_str(json_key_str_part3_for_url_issuer);
grpc_auth_json_key key = grpc_auth_json_key_create_from_string(key_str);
gpr_free(key_str);
GPR_ASSERT(grpc_auth_json_key_is_valid(&key));
grpc_httpcli_set_override(httpcli_get_bad_json,
httpcli_post_should_not_be_called);
jwt =
grpc_jwt_encode_and_sign(&key, expected_audience, expected_lifetime, NULL);
grpc_auth_json_key_destruct(&key);
GPR_ASSERT(jwt != NULL);
grpc_jwt_verifier_verify(verifier, NULL, jwt, expected_audience,
on_verification_key_retrieval_error,
(void *)expected_user_data);
gpr_free(jwt);
grpc_jwt_verifier_destroy(verifier);
grpc_httpcli_set_override(NULL, NULL);
}
static void test_jwt_verifier_bad_json_key(void) {
grpc_jwt_verifier *verifier = grpc_jwt_verifier_create(NULL, 0);
char *jwt = NULL;
char *key_str = json_key_str(json_key_str_part3_for_google_email_issuer);
grpc_auth_json_key key = grpc_auth_json_key_create_from_string(key_str);
gpr_free(key_str);
GPR_ASSERT(grpc_auth_json_key_is_valid(&key));
grpc_httpcli_set_override(httpcli_get_bad_json,
httpcli_post_should_not_be_called);
jwt =
grpc_jwt_encode_and_sign(&key, expected_audience, expected_lifetime, NULL);
grpc_auth_json_key_destruct(&key);
GPR_ASSERT(jwt != NULL);
grpc_jwt_verifier_verify(verifier, NULL, jwt, expected_audience,
on_verification_key_retrieval_error,
(void *)expected_user_data);
gpr_free(jwt);
grpc_jwt_verifier_destroy(verifier);
grpc_httpcli_set_override(NULL, NULL);
}
static void corrupt_jwt_sig(char *jwt) {
gpr_slice sig;
char *bad_b64_sig;
gpr_uint8 *sig_bytes;
char *last_dot = strrchr(jwt, '.');
GPR_ASSERT(last_dot != NULL);
sig = grpc_base64_decode(last_dot + 1, 1);
GPR_ASSERT(!GPR_SLICE_IS_EMPTY(sig));
sig_bytes = GPR_SLICE_START_PTR(sig);
(*sig_bytes)++; /* Corrupt first byte. */
bad_b64_sig =
grpc_base64_encode(GPR_SLICE_START_PTR(sig), GPR_SLICE_LENGTH(sig), 1, 0);
memcpy(last_dot + 1, bad_b64_sig, strlen(bad_b64_sig));
gpr_free(bad_b64_sig);
gpr_slice_unref(sig);
}
static void on_verification_bad_signature(void *user_data,
grpc_jwt_verifier_status status,
grpc_jwt_claims *claims) {
GPR_ASSERT(status == GRPC_JWT_VERIFIER_BAD_SIGNATURE);
GPR_ASSERT(claims == NULL);
GPR_ASSERT(user_data == (void *)expected_user_data);
}
static void test_jwt_verifier_bad_signature(void) {
grpc_jwt_verifier *verifier = grpc_jwt_verifier_create(NULL, 0);
char *jwt = NULL;
char *key_str = json_key_str(json_key_str_part3_for_url_issuer);
grpc_auth_json_key key = grpc_auth_json_key_create_from_string(key_str);
gpr_free(key_str);
GPR_ASSERT(grpc_auth_json_key_is_valid(&key));
grpc_httpcli_set_override(httpcli_get_openid_config,
httpcli_post_should_not_be_called);
jwt =
grpc_jwt_encode_and_sign(&key, expected_audience, expected_lifetime, NULL);
grpc_auth_json_key_destruct(&key);
corrupt_jwt_sig(jwt);
GPR_ASSERT(jwt != NULL);
grpc_jwt_verifier_verify(verifier, NULL, jwt, expected_audience,
on_verification_bad_signature,
(void *)expected_user_data);
gpr_free(jwt);
grpc_jwt_verifier_destroy(verifier);
grpc_httpcli_set_override(NULL, NULL);
}
static int httpcli_get_should_not_be_called(
const grpc_httpcli_request *request, gpr_timespec deadline,
grpc_httpcli_response_cb on_response, void *user_data) {
GPR_ASSERT(0);
return 1;
}
static void on_verification_bad_format(void *user_data,
grpc_jwt_verifier_status status,
grpc_jwt_claims *claims) {
GPR_ASSERT(status == GRPC_JWT_VERIFIER_BAD_FORMAT);
GPR_ASSERT(claims == NULL);
GPR_ASSERT(user_data == (void *)expected_user_data);
}
static void test_jwt_verifier_bad_format(void) {
grpc_jwt_verifier *verifier = grpc_jwt_verifier_create(NULL, 0);
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
httpcli_post_should_not_be_called);
grpc_jwt_verifier_verify(verifier, NULL, "bad jwt", expected_audience,
on_verification_bad_format,
(void *)expected_user_data);
grpc_jwt_verifier_destroy(verifier);
grpc_httpcli_set_override(NULL, NULL);
}
/* find verification key: bad jks, cannot find key in jks */
/* bad signature custom provided email*/
/* bad key */
int main(int argc, char **argv) {
grpc_test_init(argc, argv);
test_claims_success();
test_expired_claims_failure();
test_invalid_claims_failure();
test_bad_audience_claims_failure();
test_jwt_verifier_google_email_issuer_success();
test_jwt_verifier_custom_email_issuer_success();
test_jwt_verifier_url_issuer_success();
test_jwt_verifier_url_issuer_bad_config();
test_jwt_verifier_bad_json_key();
test_jwt_verifier_bad_signature();
test_jwt_verifier_bad_format();
return 0;
}

@ -88,13 +88,13 @@ int main(int argc, char **argv) {
grpc_pollset_init(&sync.pollset);
sync.is_done = 0;
grpc_credentials_get_request_metadata(creds, &sync.pollset, "", on_metadata_response, &sync);
grpc_credentials_get_request_metadata(creds, &sync.pollset, service_url,
on_metadata_response, &sync);
gpr_mu_lock(GRPC_POLLSET_MU(&sync.pollset));
while (!sync.is_done) grpc_pollset_work(&sync.pollset, gpr_inf_future);
gpr_mu_unlock(GRPC_POLLSET_MU(&sync.pollset));
grpc_pollset_destroy(&sync.pollset);
grpc_credentials_release(creds);
end:

@ -0,0 +1,119 @@
/*
*
* Copyright 2015, 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 <stdio.h>
#include <string.h>
#include "src/core/security/jwt_verifier.h"
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include <grpc/support/cmdline.h>
#include <grpc/support/log.h>
#include <grpc/support/slice.h>
#include <grpc/support/sync.h>
typedef struct {
grpc_pollset pollset;
int is_done;
int success;
} synchronizer;
static void print_usage_and_exit(gpr_cmdline *cl, const char *argv0) {
char *usage = gpr_cmdline_usage_string(cl, argv0);
fprintf(stderr, "%s", usage);
gpr_free(usage);
gpr_cmdline_destroy(cl);
exit(1);
}
static void on_jwt_verification_done(void *user_data,
grpc_jwt_verifier_status status,
grpc_jwt_claims *claims) {
synchronizer *sync = user_data;
sync->success = (status == GRPC_JWT_VERIFIER_OK);
if (sync->success) {
char *claims_str;
GPR_ASSERT(claims != NULL);
claims_str =
grpc_json_dump_to_string((grpc_json *)grpc_jwt_claims_json(claims), 2);
printf("Claims: \n\n%s\n", claims_str);
gpr_free(claims_str);
grpc_jwt_claims_destroy(claims);
} else {
GPR_ASSERT(claims == NULL);
fprintf(stderr, "Verification failed with error %s\n",
grpc_jwt_verifier_status_to_string(status));
}
gpr_mu_lock(GRPC_POLLSET_MU(&sync->pollset));
sync->is_done = 1;
grpc_pollset_kick(&sync->pollset);
gpr_mu_unlock(GRPC_POLLSET_MU(&sync->pollset));
}
int main(int argc, char **argv) {
synchronizer sync;
grpc_jwt_verifier *verifier;
gpr_cmdline *cl;
char *jwt = NULL;
char *aud = NULL;
cl = gpr_cmdline_create("JWT verifier tool");
gpr_cmdline_add_string(cl, "jwt", "JSON web token to verify", &jwt);
gpr_cmdline_add_string(cl, "aud", "Audience for the JWT", &aud);
gpr_cmdline_parse(cl, argc, argv);
if (jwt == NULL || aud == NULL) {
print_usage_and_exit(cl, argv[0]);
}
verifier = grpc_jwt_verifier_create(NULL, 0);
grpc_init();
grpc_pollset_init(&sync.pollset);
sync.is_done = 0;
grpc_jwt_verifier_verify(verifier, &sync.pollset, jwt, aud,
on_jwt_verification_done, &sync);
gpr_mu_lock(GRPC_POLLSET_MU(&sync.pollset));
while (!sync.is_done) grpc_pollset_work(&sync.pollset, gpr_inf_future);
gpr_mu_unlock(GRPC_POLLSET_MU(&sync.pollset));
grpc_jwt_verifier_destroy(verifier);
gpr_cmdline_destroy(cl);
return !sync.success;
}

@ -775,6 +775,7 @@ src/core/security/auth_filters.h \
src/core/security/base64.h \
src/core/security/credentials.h \
src/core/security/json_token.h \
src/core/security/jwt_verifier.h \
src/core/security/secure_endpoint.h \
src/core/security/secure_transport_setup.h \
src/core/security/security_connector.h \
@ -881,6 +882,7 @@ src/core/security/credentials_posix.c \
src/core/security/credentials_win32.c \
src/core/security/google_default_credentials.c \
src/core/security/json_token.c \
src/core/security/jwt_verifier.c \
src/core/security/secure_endpoint.c \
src/core/security/secure_transport_setup.c \
src/core/security/security_connector.c \

@ -515,6 +515,20 @@
"test/core/security/json_token_test.c"
]
},
{
"deps": [
"gpr",
"gpr_test_util",
"grpc",
"grpc_test_util"
],
"headers": [],
"language": "c",
"name": "grpc_jwt_verifier_test",
"src": [
"test/core/security/jwt_verifier_test.c"
]
},
{
"deps": [
"gpr",
@ -557,6 +571,20 @@
"test/core/transport/stream_op_test.c"
]
},
{
"deps": [
"gpr",
"gpr_test_util",
"grpc",
"grpc_test_util"
],
"headers": [],
"language": "c",
"name": "grpc_verify_jwt",
"src": [
"test/core/security/verify_jwt.c"
]
},
{
"deps": [
"gpr",
@ -8691,6 +8719,7 @@
"src/core/security/base64.h",
"src/core/security/credentials.h",
"src/core/security/json_token.h",
"src/core/security/jwt_verifier.h",
"src/core/security/secure_endpoint.h",
"src/core/security/secure_transport_setup.h",
"src/core/security/security_connector.h",
@ -8872,6 +8901,8 @@
"src/core/security/google_default_credentials.c",
"src/core/security/json_token.c",
"src/core/security/json_token.h",
"src/core/security/jwt_verifier.c",
"src/core/security/jwt_verifier.h",
"src/core/security/secure_endpoint.c",
"src/core/security/secure_endpoint.h",
"src/core/security/secure_transport_setup.c",

@ -303,6 +303,15 @@
"posix"
]
},
{
"flaky": false,
"language": "c",
"name": "grpc_jwt_verifier_test",
"platforms": [
"windows",
"posix"
]
},
{
"flaky": false,
"language": "c",

File diff suppressed because one or more lines are too long

@ -164,6 +164,7 @@
<ClInclude Include="..\..\src\core\security\base64.h" />
<ClInclude Include="..\..\src\core\security\credentials.h" />
<ClInclude Include="..\..\src\core\security\json_token.h" />
<ClInclude Include="..\..\src\core\security\jwt_verifier.h" />
<ClInclude Include="..\..\src\core\security\secure_endpoint.h" />
<ClInclude Include="..\..\src\core\security\secure_transport_setup.h" />
<ClInclude Include="..\..\src\core\security\security_connector.h" />
@ -284,6 +285,8 @@
</ClCompile>
<ClCompile Include="..\..\src\core\security\json_token.c">
</ClCompile>
<ClCompile Include="..\..\src\core\security\jwt_verifier.c">
</ClCompile>
<ClCompile Include="..\..\src\core\security\secure_endpoint.c">
</ClCompile>
<ClCompile Include="..\..\src\core\security\secure_transport_setup.c">

@ -37,6 +37,9 @@
<ClCompile Include="..\..\src\core\security\json_token.c">
<Filter>src\core\security</Filter>
</ClCompile>
<ClCompile Include="..\..\src\core\security\jwt_verifier.c">
<Filter>src\core\security</Filter>
</ClCompile>
<ClCompile Include="..\..\src\core\security\secure_endpoint.c">
<Filter>src\core\security</Filter>
</ClCompile>
@ -416,6 +419,9 @@
<ClInclude Include="..\..\src\core\security\json_token.h">
<Filter>src\core\security</Filter>
</ClInclude>
<ClInclude Include="..\..\src\core\security\jwt_verifier.h">
<Filter>src\core\security</Filter>
</ClInclude>
<ClInclude Include="..\..\src\core\security\secure_endpoint.h">
<Filter>src\core\security</Filter>
</ClInclude>

Loading…
Cancel
Save