Merge pull request #21256 from markdroth/json_new_api_security

Convert security code to use new JSON API
reviewable/pr21846/r1
Mark D. Roth 5 years ago committed by GitHub
commit 568f490aef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      src/core/lib/security/credentials/google_default/google_default_credentials.cc
  2. 82
      src/core/lib/security/credentials/jwt/json_token.cc
  3. 3
      src/core/lib/security/credentials/jwt/json_token.h
  4. 26
      src/core/lib/security/credentials/jwt/jwt_credentials.cc
  5. 308
      src/core/lib/security/credentials/jwt/jwt_verifier.cc
  6. 5
      src/core/lib/security/credentials/jwt/jwt_verifier.h
  7. 71
      src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
  8. 2
      src/core/lib/security/credentials/oauth2/oauth2_credentials.h
  9. 37
      src/core/lib/security/util/json_util.cc
  10. 4
      src/core/lib/security/util/json_util.h
  11. 36
      src/cpp/client/secure_credentials.cc
  12. 173
      test/core/security/json_token_test.cc
  13. 70
      test/core/security/jwt_verifier_test.cc
  14. 7
      test/core/security/verify_jwt.cc

@ -44,6 +44,8 @@
#include "src/core/lib/slice/slice_string_helpers.h"
#include "src/core/lib/surface/api_trace.h"
using grpc_core::Json;
/* -- Constants. -- */
#define GRPC_COMPUTE_ENGINE_DETECTION_HOST "metadata.google.internal."
@ -216,24 +218,25 @@ static int is_metadata_server_reachable() {
/* Takes ownership of creds_path if not NULL. */
static grpc_error* create_default_creds_from_path(
char* creds_path, grpc_core::RefCountedPtr<grpc_call_credentials>* creds) {
grpc_json* json = nullptr;
grpc_auth_json_key key;
grpc_auth_refresh_token token;
grpc_core::RefCountedPtr<grpc_call_credentials> result;
grpc_slice creds_data = grpc_empty_slice();
grpc_error* error = GRPC_ERROR_NONE;
Json json;
grpc_core::StringView str;
if (creds_path == nullptr) {
error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("creds_path unset");
goto end;
}
error = grpc_load_file(creds_path, 0, &creds_data);
if (error != GRPC_ERROR_NONE) {
goto end;
}
json = grpc_json_parse_string_with_len(
reinterpret_cast<char*> GRPC_SLICE_START_PTR(creds_data),
if (error != GRPC_ERROR_NONE) goto end;
str = grpc_core::StringView(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(creds_data)),
GRPC_SLICE_LENGTH(creds_data));
if (json == nullptr) {
json = Json::Parse(str, &error);
if (error != GRPC_ERROR_NONE) goto end;
if (json.type() != Json::Type::OBJECT) {
error = grpc_error_set_str(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("Failed to parse JSON"),
GRPC_ERROR_STR_RAW_BYTES, grpc_slice_ref_internal(creds_data));
@ -271,7 +274,6 @@ end:
GPR_ASSERT((result == nullptr) + (error == GRPC_ERROR_NONE) == 1);
if (creds_path != nullptr) gpr_free(creds_path);
grpc_slice_unref_internal(creds_data);
grpc_json_destroy(json);
*creds = result;
return error;
}

@ -39,6 +39,8 @@ extern "C" {
#include <openssl/pem.h>
}
using grpc_core::Json;
/* --- Constants. --- */
/* 1 hour max. */
@ -65,7 +67,7 @@ int grpc_auth_json_key_is_valid(const grpc_auth_json_key* json_key) {
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 grpc_auth_json_key_create_from_json(const Json& json) {
grpc_auth_json_key result;
BIO* bio = nullptr;
const char* prop_value;
@ -74,7 +76,7 @@ grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json* json) {
memset(&result, 0, sizeof(grpc_auth_json_key));
result.type = GRPC_AUTH_JSON_TYPE_INVALID;
if (json == nullptr) {
if (json.type() == Json::Type::JSON_NULL) {
gpr_log(GPR_ERROR, "Invalid json.");
goto end;
}
@ -122,12 +124,10 @@ end:
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);
grpc_json_destroy(json);
gpr_free(scratchpad);
return result;
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(json_string, &error);
GRPC_LOG_IF_ERROR("JSON key parsing", error);
return grpc_auth_json_key_create_from_json(std::move(json));
}
void grpc_auth_json_key_destruct(grpc_auth_json_key* json_key) {
@ -153,72 +153,42 @@ void grpc_auth_json_key_destruct(grpc_auth_json_key* json_key) {
/* --- 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 = nullptr;
char* json_str = nullptr;
char* result = nullptr;
child = create_child(nullptr, 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;
Json json = Json::Object{
{"alg", algorithm},
{"typ", GRPC_JWT_TYPE},
{"kid", key_id},
};
std::string json_str = json.Dump();
return grpc_base64_encode(json_str.c_str(), json_str.size(), 1, 0);
}
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 = nullptr;
char* json_str = nullptr;
char* result = nullptr;
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(nullptr, json, "iss", json_key->client_email,
GRPC_JSON_STRING);
Json::Object object = {
{"iss", json_key->client_email},
{"aud", audience},
{"iat", now.tv_sec},
{"exp", expiration.tv_sec},
};
if (scope != nullptr) {
child = create_child(child, json, "scope", scope, GRPC_JSON_STRING);
object["scope"] = scope;
} else {
/* Unscoped JWTs need a sub field. */
child = create_child(child, json, "sub", json_key->client_email,
GRPC_JSON_STRING);
object["sub"] = json_key->client_email;
}
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;
Json json(object);
std::string json_str = json.Dump();
return grpc_base64_encode(json_str.c_str(), json_str.size(), 1, 0);
}
static char* dot_concat_and_free_strings(char* str1, char* str2) {

@ -52,7 +52,8 @@ grpc_auth_json_key grpc_auth_json_key_create_from_string(
/* Creates a json_key object from parsed json. Returns an invalid object if a
parsing error has been encountered. */
grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json* json);
grpc_auth_json_key grpc_auth_json_key_create_from_json(
const grpc_core::Json& json);
/* Destructs the object. */
void grpc_auth_json_key_destruct(grpc_auth_json_key* json_key);

@ -32,6 +32,8 @@
#include <grpc/support/string_util.h>
#include <grpc/support/sync.h>
using grpc_core::Json;
void grpc_service_account_jwt_access_credentials::reset_cache() {
GRPC_MDELEM_UNREF(cached_.jwt_md);
cached_.jwt_md = GRPC_MDNULL;
@ -136,26 +138,14 @@ grpc_service_account_jwt_access_credentials_create_from_auth_json_key(
}
static char* redact_private_key(const char* json_key) {
char* json_copy = gpr_strdup(json_key);
grpc_json* json = grpc_json_parse_string(json_copy);
if (!json) {
gpr_free(json_copy);
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(json_key, &error);
if (error != GRPC_ERROR_NONE || json.type() != Json::Type::OBJECT) {
GRPC_ERROR_UNREF(error);
return gpr_strdup("<Json failed to parse.>");
}
const char* redacted = "<redacted>";
grpc_json* current = json->child;
while (current) {
if (current->type == GRPC_JSON_STRING &&
strcmp(current->key, "private_key") == 0) {
current->value = const_cast<char*>(redacted);
break;
}
current = current->next;
}
char* clean_json = grpc_json_dump_to_string(json, 2);
gpr_free(json_copy);
grpc_json_destroy(json);
return clean_json;
(*json.mutable_object())["private_key"] = "<redacted>";
return gpr_strdup(json.Dump(/*indent=*/2).c_str());
}
grpc_call_credentials* grpc_service_account_jwt_access_credentials_create(

@ -37,12 +37,15 @@ extern "C" {
}
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gprpp/manual_constructor.h"
#include "src/core/lib/http/httpcli.h"
#include "src/core/lib/iomgr/polling_entity.h"
#include "src/core/lib/slice/b64.h"
#include "src/core/lib/slice/slice_internal.h"
#include "src/core/tsi/ssl_types.h"
using grpc_core::Json;
/* --- Utils. --- */
const char* grpc_jwt_verifier_status_to_string(
@ -79,42 +82,41 @@ static const EVP_MD* evp_md_from_alg(const char* alg) {
}
}
static grpc_json* parse_json_part_from_jwt(const char* str, size_t len,
grpc_slice* buffer) {
grpc_json* json;
*buffer = grpc_base64_decode_with_len(str, len, 1);
if (GRPC_SLICE_IS_EMPTY(*buffer)) {
static Json parse_json_part_from_jwt(const char* str, size_t len) {
grpc_slice slice = grpc_base64_decode_with_len(str, len, 1);
if (GRPC_SLICE_IS_EMPTY(slice)) {
gpr_log(GPR_ERROR, "Invalid base64.");
return nullptr;
}
json = grpc_json_parse_string_with_len(
reinterpret_cast<char*> GRPC_SLICE_START_PTR(*buffer),
GRPC_SLICE_LENGTH(*buffer));
if (json == nullptr) {
grpc_slice_unref_internal(*buffer);
gpr_log(GPR_ERROR, "JSON parsing error.");
}
return Json(); // JSON null
}
grpc_core::StringView string(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(slice)),
GRPC_SLICE_LENGTH(slice));
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(string, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
GRPC_ERROR_UNREF(error);
json = Json(); // JSON null
}
grpc_slice_unref_internal(slice);
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);
static const char* validate_string_field(const Json& json, const char* key) {
if (json.type() != Json::Type::STRING) {
gpr_log(GPR_ERROR, "Invalid %s field", key);
return nullptr;
}
return json->value;
return json.string_value().c_str();
}
static gpr_timespec validate_time_field(const grpc_json* json,
const char* key) {
static gpr_timespec validate_time_field(const Json& json, const char* key) {
gpr_timespec result = gpr_time_0(GPR_CLOCK_REALTIME);
if (json->type != GRPC_JSON_NUMBER) {
gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value);
if (json.type() != Json::Type::NUMBER) {
gpr_log(GPR_ERROR, "Invalid %s field", key);
return result;
}
result.tv_sec = strtol(json->value, nullptr, 10);
result.tv_sec = strtol(json.string_value().c_str(), nullptr, 10);
return result;
}
@ -125,50 +127,55 @@ typedef struct {
const char* kid;
const char* typ;
/* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */
grpc_slice buffer;
grpc_core::ManualConstructor<Json> json;
} jose_header;
static void jose_header_destroy(jose_header* h) {
grpc_slice_unref_internal(h->buffer);
h->json.Destroy();
gpr_free(h);
}
/* Takes ownership of json and buffer. */
static jose_header* jose_header_from_json(grpc_json* json,
const grpc_slice& buffer) {
grpc_json* cur;
static jose_header* jose_header_from_json(Json json) {
const char* alg_value;
Json::Object::const_iterator it;
jose_header* h = static_cast<jose_header*>(gpr_zalloc(sizeof(jose_header)));
h->buffer = buffer;
for (cur = json->child; cur != nullptr; 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) == nullptr) {
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 == nullptr) goto error;
} else if (strcmp(cur->key, "kid") == 0) {
h->kid = validate_string_field(cur, "kid");
if (h->kid == nullptr) goto error;
}
if (json.type() != Json::Type::OBJECT) {
gpr_log(GPR_ERROR, "JSON value is not an object");
goto error;
}
if (h->alg == nullptr) {
// Check alg field.
it = json.object_value().find("alg");
if (it == json.object_value().end()) {
gpr_log(GPR_ERROR, "Missing alg field.");
goto error;
}
grpc_json_destroy(json);
h->buffer = buffer;
/* 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/
*/
alg_value = it->second.string_value().c_str();
if (it->second.type() != Json::Type::STRING || strncmp(alg_value, "RS", 2) ||
evp_md_from_alg(alg_value) == nullptr) {
gpr_log(GPR_ERROR, "Invalid alg field");
goto error;
}
h->alg = alg_value;
// Check typ field.
it = json.object_value().find("typ");
if (it != json.object_value().end()) {
h->typ = validate_string_field(it->second, "typ");
if (h->typ == nullptr) goto error;
}
// Check kid field.
it = json.object_value().find("kid");
if (it != json.object_value().end()) {
h->kid = validate_string_field(it->second, "kid");
if (h->kid == nullptr) goto error;
}
h->json.Init(std::move(json));
return h;
error:
grpc_json_destroy(json);
jose_header_destroy(h);
return nullptr;
}
@ -185,19 +192,17 @@ struct grpc_jwt_claims {
gpr_timespec exp;
gpr_timespec nbf;
grpc_json* json;
grpc_slice buffer;
grpc_core::ManualConstructor<Json> json;
};
void grpc_jwt_claims_destroy(grpc_jwt_claims* claims) {
grpc_json_destroy(claims->json);
grpc_slice_unref_internal(claims->buffer);
claims->json.Destroy();
gpr_free(claims);
}
const grpc_json* grpc_jwt_claims_json(const grpc_jwt_claims* claims) {
const Json* grpc_jwt_claims_json(const grpc_jwt_claims* claims) {
if (claims == nullptr) return nullptr;
return claims->json;
return claims->json.get();
}
const char* grpc_jwt_claims_subject(const grpc_jwt_claims* claims) {
@ -235,45 +240,43 @@ gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims* claims) {
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,
const grpc_slice& buffer) {
grpc_json* cur;
grpc_jwt_claims* grpc_jwt_claims_from_json(Json json) {
grpc_jwt_claims* claims =
static_cast<grpc_jwt_claims*>(gpr_malloc(sizeof(grpc_jwt_claims)));
memset(claims, 0, sizeof(grpc_jwt_claims));
claims->json = json;
claims->buffer = buffer;
static_cast<grpc_jwt_claims*>(gpr_zalloc(sizeof(grpc_jwt_claims)));
claims->json.Init(std::move(json));
claims->iat = gpr_inf_past(GPR_CLOCK_REALTIME);
claims->nbf = gpr_inf_past(GPR_CLOCK_REALTIME);
claims->exp = gpr_inf_future(GPR_CLOCK_REALTIME);
/* Per the spec, all fields are optional. */
for (cur = json->child; cur != nullptr; cur = cur->next) {
if (strcmp(cur->key, "sub") == 0) {
claims->sub = validate_string_field(cur, "sub");
for (const auto& p : claims->json->object_value()) {
if (p.first == "sub") {
claims->sub = validate_string_field(p.second, "sub");
if (claims->sub == nullptr) goto error;
} else if (strcmp(cur->key, "iss") == 0) {
claims->iss = validate_string_field(cur, "iss");
} else if (p.first == "iss") {
claims->iss = validate_string_field(p.second, "iss");
if (claims->iss == nullptr) goto error;
} else if (strcmp(cur->key, "aud") == 0) {
claims->aud = validate_string_field(cur, "aud");
} else if (p.first == "aud") {
claims->aud = validate_string_field(p.second, "aud");
if (claims->aud == nullptr) goto error;
} else if (strcmp(cur->key, "jti") == 0) {
claims->jti = validate_string_field(cur, "jti");
} else if (p.first == "jti") {
claims->jti = validate_string_field(p.second, "jti");
if (claims->jti == nullptr) 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(GPR_CLOCK_REALTIME)) == 0)
} else if (p.first == "iat") {
claims->iat = validate_time_field(p.second, "iat");
if (gpr_time_cmp(claims->iat, gpr_time_0(GPR_CLOCK_REALTIME)) == 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(GPR_CLOCK_REALTIME)) == 0)
}
} else if (p.first == "exp") {
claims->exp = validate_time_field(p.second, "exp");
if (gpr_time_cmp(claims->exp, gpr_time_0(GPR_CLOCK_REALTIME)) == 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(GPR_CLOCK_REALTIME)) == 0)
}
} else if (p.first == "nbf") {
claims->nbf = validate_time_field(p.second, "nbf");
if (gpr_time_cmp(claims->nbf, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) {
goto error;
}
}
}
return claims;
@ -405,33 +408,32 @@ struct grpc_jwt_verifier {
grpc_httpcli_context http_ctx;
};
static grpc_json* json_from_http(const grpc_httpcli_response* response) {
grpc_json* json = nullptr;
static Json json_from_http(const grpc_httpcli_response* response) {
if (response == nullptr) {
gpr_log(GPR_ERROR, "HTTP response is NULL.");
return nullptr;
return Json(); // JSON null
}
if (response->status != 200) {
gpr_log(GPR_ERROR, "Call to http server failed with error %d.",
response->status);
return nullptr;
return Json(); // JSON null
}
json = grpc_json_parse_string_with_len(response->body, response->body_length);
if (json == nullptr) {
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(
grpc_core::StringView(response->body, response->body_length), &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "Invalid JSON found in response.");
return Json(); // JSON null
}
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 != nullptr; cur = cur->next) {
if (strcmp(cur->key, name) == 0) return cur;
static const Json* find_property_by_name(const Json& json, const char* name) {
auto it = json.object_value().find(name);
if (it == json.object_value().end()) {
return nullptr;
}
return nullptr;
return &it->second;
}
static EVP_PKEY* extract_pkey_from_x509(const char* x509_str) {
@ -502,14 +504,15 @@ static int RSA_set0_key(RSA* r, BIGNUM* n, BIGNUM* e, BIGNUM* d) {
}
#endif // OPENSSL_VERSION_NUMBER < 0x10100000L
static EVP_PKEY* pkey_from_jwk(const grpc_json* json, const char* kty) {
const grpc_json* key_prop;
static EVP_PKEY* pkey_from_jwk(const Json& json, const char* kty) {
RSA* rsa = nullptr;
EVP_PKEY* result = nullptr;
BIGNUM* tmp_n = nullptr;
BIGNUM* tmp_e = nullptr;
Json::Object::const_iterator it;
GPR_ASSERT(kty != nullptr && json != nullptr);
GPR_ASSERT(json.type() == Json::Type::OBJECT);
GPR_ASSERT(kty != nullptr);
if (strcmp(kty, "RSA") != 0) {
gpr_log(GPR_ERROR, "Unsupported key type %s.", kty);
goto end;
@ -519,19 +522,20 @@ static EVP_PKEY* pkey_from_jwk(const grpc_json* json, const char* kty) {
gpr_log(GPR_ERROR, "Could not create rsa key.");
goto end;
}
for (key_prop = json->child; key_prop != nullptr; key_prop = key_prop->next) {
if (strcmp(key_prop->key, "n") == 0) {
tmp_n = bignum_from_base64(validate_string_field(key_prop, "n"));
if (tmp_n == nullptr) goto end;
} else if (strcmp(key_prop->key, "e") == 0) {
tmp_e = bignum_from_base64(validate_string_field(key_prop, "e"));
if (tmp_e == nullptr) goto end;
}
it = json.object_value().find("n");
if (it == json.object_value().end()) {
gpr_log(GPR_ERROR, "Missing RSA public key field.");
goto end;
}
if (tmp_e == nullptr || tmp_n == nullptr) {
tmp_n = bignum_from_base64(validate_string_field(it->second, "n"));
if (tmp_n == nullptr) goto end;
it = json.object_value().find("e");
if (it == json.object_value().end()) {
gpr_log(GPR_ERROR, "Missing RSA public key field.");
goto end;
}
tmp_e = bignum_from_base64(validate_string_field(it->second, "e"));
if (tmp_e == nullptr) goto end;
if (!RSA_set0_key(rsa, tmp_n, tmp_e, nullptr)) {
gpr_log(GPR_ERROR, "Cannot set RSA key from inputs.");
goto end;
@ -549,48 +553,41 @@ end:
return result;
}
static EVP_PKEY* find_verification_key(const grpc_json* json,
const char* header_alg,
static EVP_PKEY* find_verification_key(const 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 == nullptr) {
const Json* jwt_keys = find_property_by_name(json, "keys");
if (jwt_keys == nullptr) {
/* Use the google proprietary format which is:
{ <kid1>: <x5091>, <kid2>: <x5092>, ... } */
const grpc_json* cur = find_property_by_name(json, header_kid);
const Json* cur = find_property_by_name(json, header_kid);
if (cur == nullptr) return nullptr;
return extract_pkey_from_x509(cur->value);
return extract_pkey_from_x509(cur->string_value().c_str());
}
if (jwk_keys->type != GRPC_JSON_ARRAY) {
if (jwt_keys->type() != Json::Type::ARRAY) {
gpr_log(GPR_ERROR,
"Unexpected value type of keys property in jwks key set.");
return nullptr;
}
/* Key format is specified in:
https://tools.ietf.org/html/rfc7518#section-6. */
for (jkey = jwk_keys->child; jkey != nullptr; jkey = jkey->next) {
grpc_json* key_prop;
for (const Json& jkey : jwt_keys->array_value()) {
if (jkey.type() != Json::Type::OBJECT) continue;
const char* alg = nullptr;
auto it = jkey.object_value().find("alg");
if (it != jkey.object_value().end()) {
alg = validate_string_field(it->second, "alg");
}
const char* kid = nullptr;
it = jkey.object_value().find("kid");
if (it != jkey.object_value().end()) {
kid = validate_string_field(it->second, "kid");
}
const char* kty = nullptr;
if (jkey->type != GRPC_JSON_OBJECT) continue;
for (key_prop = jkey->child; key_prop != nullptr;
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;
}
it = jkey.object_value().find("kty");
if (it != jkey.object_value().end()) {
kty = validate_string_field(it->second, "kty");
}
if (alg != nullptr && kid != nullptr && kty != nullptr &&
strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) {
@ -638,12 +635,12 @@ end:
static void on_keys_retrieved(void* user_data, grpc_error* /*error*/) {
verifier_cb_ctx* ctx = static_cast<verifier_cb_ctx*>(user_data);
grpc_json* json = json_from_http(&ctx->responses[HTTP_RESPONSE_KEYS]);
Json json = json_from_http(&ctx->responses[HTTP_RESPONSE_KEYS]);
EVP_PKEY* verification_key = nullptr;
grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR;
grpc_jwt_claims* claims = nullptr;
if (json == nullptr) {
if (json.type() == Json::Type::JSON_NULL) {
status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR;
goto end;
}
@ -670,29 +667,28 @@ static void on_keys_retrieved(void* user_data, grpc_error* /*error*/) {
}
end:
grpc_json_destroy(json);
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, grpc_error* /*error*/) {
const grpc_json* cur;
verifier_cb_ctx* ctx = static_cast<verifier_cb_ctx*>(user_data);
const grpc_http_response* response = &ctx->responses[HTTP_RESPONSE_OPENID];
grpc_json* json = json_from_http(response);
Json json = json_from_http(response);
grpc_httpcli_request req;
const char* jwks_uri;
grpc_resource_quota* resource_quota = nullptr;
const Json* cur;
/* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */
if (json == nullptr) goto error;
if (json.type() == Json::Type::JSON_NULL) goto error;
cur = find_property_by_name(json, "jwks_uri");
if (cur == nullptr) {
gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config.");
goto error;
}
jwks_uri = validate_string_field(cur, "jwks_uri");
jwks_uri = validate_string_field(*cur, "jwks_uri");
if (jwks_uri == nullptr) goto error;
if (strstr(jwks_uri, "https://") != jwks_uri) {
gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri);
@ -718,12 +714,10 @@ static void on_openid_config_retrieved(void* user_data, grpc_error* /*error*/) {
GRPC_CLOSURE_CREATE(on_keys_retrieved, ctx, grpc_schedule_on_exec_ctx),
&ctx->responses[HTTP_RESPONSE_KEYS]);
grpc_resource_quota_unref_internal(resource_quota);
grpc_json_destroy(json);
gpr_free(req.host);
return;
error:
grpc_json_destroy(json);
ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, nullptr);
verifier_cb_ctx_destroy(ctx);
}
@ -860,32 +854,28 @@ void grpc_jwt_verifier_verify(grpc_jwt_verifier* verifier,
grpc_jwt_verification_done_cb cb,
void* user_data) {
const char* dot = nullptr;
grpc_json* json;
jose_header* header = nullptr;
grpc_jwt_claims* claims = nullptr;
grpc_slice header_buffer;
grpc_slice claims_buffer;
grpc_slice signature;
size_t signed_jwt_len;
const char* cur = jwt;
Json json;
GPR_ASSERT(verifier != nullptr && jwt != nullptr && audience != nullptr &&
cb != nullptr);
dot = strchr(cur, '.');
if (dot == nullptr) goto error;
json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur),
&header_buffer);
if (json == nullptr) goto error;
header = jose_header_from_json(json, header_buffer);
json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur));
if (json.type() == Json::Type::JSON_NULL) goto error;
header = jose_header_from_json(std::move(json));
if (header == nullptr) goto error;
cur = dot + 1;
dot = strchr(cur, '.');
if (dot == nullptr) goto error;
json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur),
&claims_buffer);
if (json == nullptr) goto error;
claims = grpc_jwt_claims_from_json(json, claims_buffer);
json = parse_json_part_from_jwt(cur, static_cast<size_t>(dot - cur));
if (json.type() == Json::Type::JSON_NULL) goto error;
claims = grpc_jwt_claims_from_json(std::move(json));
if (claims == nullptr) goto error;
signed_jwt_len = static_cast<size_t>(dot - jwt);

@ -56,7 +56,7 @@ 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);
const grpc_core::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);
@ -115,8 +115,7 @@ void grpc_jwt_verifier_verify(grpc_jwt_verifier* verifier,
/* --- TESTING ONLY exposed functions. --- */
grpc_jwt_claims* grpc_jwt_claims_from_json(grpc_json* json,
const grpc_slice& buffer);
grpc_jwt_claims* grpc_jwt_claims_from_json(grpc_core::Json json);
grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims* claims,
const char* audience);
const char* grpc_jwt_issuer_email_domain(const char* issuer);

@ -40,6 +40,8 @@
#include "src/core/lib/surface/api_trace.h"
#include "src/core/lib/uri/uri_parser.h"
using grpc_core::Json;
//
// Auth Refresh Token.
//
@ -51,7 +53,7 @@ int grpc_auth_refresh_token_is_valid(
}
grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
const grpc_json* json) {
const Json& json) {
grpc_auth_refresh_token result;
const char* prop_value;
int success = 0;
@ -59,7 +61,7 @@ grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
memset(&result, 0, sizeof(grpc_auth_refresh_token));
result.type = GRPC_AUTH_JSON_TYPE_INVALID;
if (json == nullptr) {
if (json.type() != Json::Type::OBJECT) {
gpr_log(GPR_ERROR, "Invalid json.");
goto end;
}
@ -88,13 +90,13 @@ end:
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);
grpc_json_destroy(json);
gpr_free(scratchpad);
return result;
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(json_string, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parsing failed: %s", grpc_error_string(error));
GRPC_ERROR_UNREF(error);
}
return grpc_auth_refresh_token_create_from_json(std::move(json));
}
void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token) {
@ -133,7 +135,7 @@ grpc_oauth2_token_fetcher_credentials_parse_server_response(
char* null_terminated_body = nullptr;
char* new_access_token = nullptr;
grpc_credentials_status status = GRPC_CREDENTIALS_OK;
grpc_json* json = nullptr;
Json json;
if (response == nullptr) {
gpr_log(GPR_ERROR, "Received NULL response.");
@ -155,48 +157,50 @@ grpc_oauth2_token_fetcher_credentials_parse_server_response(
status = GRPC_CREDENTIALS_ERROR;
goto end;
} else {
grpc_json* access_token = nullptr;
grpc_json* token_type = nullptr;
grpc_json* expires_in = nullptr;
grpc_json* ptr;
json = grpc_json_parse_string(null_terminated_body);
if (json == nullptr) {
gpr_log(GPR_ERROR, "Could not parse JSON from %s", null_terminated_body);
const char* access_token = nullptr;
const char* token_type = nullptr;
const char* expires_in = nullptr;
Json::Object::const_iterator it;
grpc_error* error = GRPC_ERROR_NONE;
json = Json::Parse(null_terminated_body, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "Could not parse JSON from %s: %s",
null_terminated_body, grpc_error_string(error));
GRPC_ERROR_UNREF(error);
status = GRPC_CREDENTIALS_ERROR;
goto end;
}
if (json->type != GRPC_JSON_OBJECT) {
if (json.type() != Json::Type::OBJECT) {
gpr_log(GPR_ERROR, "Response should be a JSON object");
status = GRPC_CREDENTIALS_ERROR;
goto end;
}
for (ptr = json->child; ptr; ptr = ptr->next) {
if (strcmp(ptr->key, "access_token") == 0) {
access_token = ptr;
} else if (strcmp(ptr->key, "token_type") == 0) {
token_type = ptr;
} else if (strcmp(ptr->key, "expires_in") == 0) {
expires_in = ptr;
}
}
if (access_token == nullptr || access_token->type != GRPC_JSON_STRING) {
it = json.object_value().find("access_token");
if (it == json.object_value().end() ||
it->second.type() != Json::Type::STRING) {
gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON.");
status = GRPC_CREDENTIALS_ERROR;
goto end;
}
if (token_type == nullptr || token_type->type != GRPC_JSON_STRING) {
access_token = it->second.string_value().c_str();
it = json.object_value().find("token_type");
if (it == json.object_value().end() ||
it->second.type() != Json::Type::STRING) {
gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON.");
status = GRPC_CREDENTIALS_ERROR;
goto end;
}
if (expires_in == nullptr || expires_in->type != GRPC_JSON_NUMBER) {
token_type = it->second.string_value().c_str();
it = json.object_value().find("expires_in");
if (it == json.object_value().end() ||
it->second.type() != Json::Type::NUMBER) {
gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON.");
status = GRPC_CREDENTIALS_ERROR;
goto end;
}
gpr_asprintf(&new_access_token, "%s %s", token_type->value,
access_token->value);
*token_lifetime = strtol(expires_in->value, nullptr, 10) * GPR_MS_PER_SEC;
expires_in = it->second.string_value().c_str();
gpr_asprintf(&new_access_token, "%s %s", token_type, access_token);
*token_lifetime = strtol(expires_in, nullptr, 10) * GPR_MS_PER_SEC;
if (!GRPC_MDISNULL(*token_md)) GRPC_MDELEM_UNREF(*token_md);
*token_md = grpc_mdelem_from_slices(
grpc_core::ExternallyManagedSlice(GRPC_AUTHORIZATION_METADATA_KEY),
@ -211,7 +215,6 @@ end:
}
if (null_terminated_body != nullptr) gpr_free(null_terminated_body);
if (new_access_token != nullptr) gpr_free(new_access_token);
grpc_json_destroy(json);
return status;
}

@ -51,7 +51,7 @@ grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string(
/// Creates a refresh token object from parsed json. Returns an invalid object
/// if a parsing error has been encountered.
grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
const grpc_json* json);
const grpc_core::Json& json);
/// Destructs the object.
void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token);

@ -26,34 +26,41 @@
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
const char* grpc_json_get_string_property(const grpc_json* json,
const char* grpc_json_get_string_property(const grpc_core::Json& json,
const char* prop_name,
grpc_error** error) {
grpc_json* child = nullptr;
if (error != nullptr) *error = GRPC_ERROR_NONE;
for (child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
if (error != nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid (null) JSON key encountered");
}
return nullptr;
if (json.type() != grpc_core::Json::Type::OBJECT) {
if (error != nullptr) {
*error =
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON value is not an object");
}
if (strcmp(child->key, prop_name) == 0) break;
return nullptr;
}
auto it = json.object_value().find(prop_name);
if (it == json.object_value().end()) {
if (error != nullptr) {
char* error_msg;
gpr_asprintf(&error_msg, "Property %s not found in JSON object.",
prop_name);
*error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
gpr_free(error_msg);
}
return nullptr;
}
if (child == nullptr || child->type != GRPC_JSON_STRING) {
if (it->second.type() != grpc_core::Json::Type::STRING) {
if (error != nullptr) {
char* error_msg;
gpr_asprintf(&error_msg, "Invalid or missing %s property.", prop_name);
gpr_asprintf(&error_msg, "Property %s in JSON object is not a string.",
prop_name);
*error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
gpr_free(error_msg);
}
return nullptr;
}
return child->value;
return it->second.string_value().c_str();
}
bool grpc_copy_json_string_property(const grpc_json* json,
bool grpc_copy_json_string_property(const grpc_core::Json& json,
const char* prop_name,
char** copied_value) {
grpc_error* error = GRPC_ERROR_NONE;

@ -32,13 +32,13 @@
#define GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER "authorized_user"
// Gets a child property from a json node.
const char* grpc_json_get_string_property(const grpc_json* json,
const char* grpc_json_get_string_property(const grpc_core::Json& json,
const char* prop_name,
grpc_error** error);
// Copies the value of the json child property specified by prop_name.
// Returns false if the property was not found.
bool grpc_copy_json_string_property(const grpc_json* json,
bool grpc_copy_json_string_property(const grpc_core::Json& json,
const char* prop_name, char** copied_value);
#endif /* GRPC_CORE_LIB_SECURITY_UTIL_JSON_UTIL_H */

@ -135,41 +135,36 @@ void ClearStsCredentialsOptions(StsCredentialsOptions* options) {
// Builds STS credentials options from JSON.
grpc::Status StsCredentialsOptionsFromJson(const grpc::string& json_string,
StsCredentialsOptions* options) {
struct GrpcJsonDeleter {
void operator()(grpc_json* json) { grpc_json_destroy(json); }
};
if (options == nullptr) {
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
"options cannot be nullptr.");
}
ClearStsCredentialsOptions(options);
std::vector<char> scratchpad(json_string.c_str(),
json_string.c_str() + json_string.size() + 1);
std::unique_ptr<grpc_json, GrpcJsonDeleter> json(
grpc_json_parse_string(&scratchpad[0]));
if (json == nullptr) {
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::Json json = grpc_core::Json::Parse(json_string.c_str(), &error);
if (error != GRPC_ERROR_NONE ||
json.type() != grpc_core::Json::Type::OBJECT) {
GRPC_ERROR_UNREF(error);
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Invalid json.");
}
// Required fields.
const char* value = grpc_json_get_string_property(
json.get(), "token_exchange_service_uri", nullptr);
json, "token_exchange_service_uri", nullptr);
if (value == nullptr) {
ClearStsCredentialsOptions(options);
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
"token_exchange_service_uri must be specified.");
}
options->token_exchange_service_uri.assign(value);
value =
grpc_json_get_string_property(json.get(), "subject_token_path", nullptr);
value = grpc_json_get_string_property(json, "subject_token_path", nullptr);
if (value == nullptr) {
ClearStsCredentialsOptions(options);
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
"subject_token_path must be specified.");
}
options->subject_token_path.assign(value);
value =
grpc_json_get_string_property(json.get(), "subject_token_type", nullptr);
value = grpc_json_get_string_property(json, "subject_token_type", nullptr);
if (value == nullptr) {
ClearStsCredentialsOptions(options);
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
@ -178,20 +173,17 @@ grpc::Status StsCredentialsOptionsFromJson(const grpc::string& json_string,
options->subject_token_type.assign(value);
// Optional fields.
value = grpc_json_get_string_property(json.get(), "resource", nullptr);
value = grpc_json_get_string_property(json, "resource", nullptr);
if (value != nullptr) options->resource.assign(value);
value = grpc_json_get_string_property(json.get(), "audience", nullptr);
value = grpc_json_get_string_property(json, "audience", nullptr);
if (value != nullptr) options->audience.assign(value);
value = grpc_json_get_string_property(json.get(), "scope", nullptr);
value = grpc_json_get_string_property(json, "scope", nullptr);
if (value != nullptr) options->scope.assign(value);
value = grpc_json_get_string_property(json.get(), "requested_token_type",
nullptr);
value = grpc_json_get_string_property(json, "requested_token_type", nullptr);
if (value != nullptr) options->requested_token_type.assign(value);
value =
grpc_json_get_string_property(json.get(), "actor_token_path", nullptr);
value = grpc_json_get_string_property(json, "actor_token_path", nullptr);
if (value != nullptr) options->actor_token_path.assign(value);
value =
grpc_json_get_string_property(json.get(), "actor_token_type", nullptr);
value = grpc_json_get_string_property(json, "actor_token_type", nullptr);
if (value != nullptr) options->actor_token_type.assign(value);
return grpc::Status();

@ -32,6 +32,8 @@
#include "src/core/lib/slice/slice_internal.h"
#include "test/core/util/test_config.h"
using grpc_core::Json;
/* 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! */
@ -205,122 +207,78 @@ static void test_parse_json_key_failure_no_private_key(void) {
grpc_auth_json_key_destruct(&json_key);
}
static grpc_json* parse_json_part_from_jwt(const char* str, size_t len,
char** scratchpad) {
static Json parse_json_part_from_jwt(const char* str, size_t len) {
grpc_core::ExecCtx exec_ctx;
char* b64;
char* decoded;
grpc_json* json;
grpc_slice slice;
b64 = static_cast<char*>(gpr_malloc(len + 1));
char* b64 = static_cast<char*>(gpr_malloc(len + 1));
strncpy(b64, str, len);
b64[len] = '\0';
slice = grpc_base64_decode(b64, 1);
GPR_ASSERT(!GRPC_SLICE_IS_EMPTY(slice));
decoded = static_cast<char*>(gpr_malloc(GRPC_SLICE_LENGTH(slice) + 1));
strncpy(decoded, reinterpret_cast<const char*> GRPC_SLICE_START_PTR(slice),
GRPC_SLICE_LENGTH(slice));
decoded[GRPC_SLICE_LENGTH(slice)] = '\0';
json = grpc_json_parse_string(decoded);
grpc_slice slice = grpc_base64_decode(b64, 1);
gpr_free(b64);
*scratchpad = decoded;
GPR_ASSERT(!GRPC_SLICE_IS_EMPTY(slice));
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::StringView string(
reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(slice)),
GRPC_SLICE_LENGTH(slice));
Json json = Json::Parse(string, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
GRPC_ERROR_UNREF(error);
}
grpc_slice_unref(slice);
return json;
}
static void check_jwt_header(grpc_json* header) {
grpc_json* ptr;
grpc_json* alg = nullptr;
grpc_json* typ = nullptr;
grpc_json* kid = nullptr;
for (ptr = header->child; ptr; ptr = ptr->next) {
if (strcmp(ptr->key, "alg") == 0) {
alg = ptr;
} else if (strcmp(ptr->key, "typ") == 0) {
typ = ptr;
} else if (strcmp(ptr->key, "kid") == 0) {
kid = ptr;
}
}
GPR_ASSERT(alg != nullptr);
GPR_ASSERT(alg->type == GRPC_JSON_STRING);
GPR_ASSERT(strcmp(alg->value, "RS256") == 0);
GPR_ASSERT(typ != nullptr);
GPR_ASSERT(typ->type == GRPC_JSON_STRING);
GPR_ASSERT(strcmp(typ->value, "JWT") == 0);
GPR_ASSERT(kid != nullptr);
GPR_ASSERT(kid->type == GRPC_JSON_STRING);
GPR_ASSERT(strcmp(kid->value, "e6b5137873db8d2ef81e06a47289e6434ec8a165") ==
0);
static void check_jwt_header(const Json& header) {
Json::Object object = header.object_value();
Json value = object["alg"];
GPR_ASSERT(value.type() == Json::Type::STRING);
GPR_ASSERT(strcmp(value.string_value().c_str(), "RS256") == 0);
value = object["typ"];
GPR_ASSERT(value.type() == Json::Type::STRING);
GPR_ASSERT(strcmp(value.string_value().c_str(), "JWT") == 0);
value = object["kid"];
GPR_ASSERT(value.type() == Json::Type::STRING);
GPR_ASSERT(strcmp(value.string_value().c_str(),
"e6b5137873db8d2ef81e06a47289e6434ec8a165") == 0);
}
static void check_jwt_claim(grpc_json* claim, const char* expected_audience,
static void check_jwt_claim(const Json& claim, const char* expected_audience,
const char* expected_scope) {
gpr_timespec expiration = gpr_time_0(GPR_CLOCK_REALTIME);
gpr_timespec issue_time = gpr_time_0(GPR_CLOCK_REALTIME);
gpr_timespec parsed_lifetime;
grpc_json* iss = nullptr;
grpc_json* scope = nullptr;
grpc_json* aud = nullptr;
grpc_json* exp = nullptr;
grpc_json* iat = nullptr;
grpc_json* sub = nullptr;
grpc_json* ptr;
for (ptr = claim->child; ptr; ptr = ptr->next) {
if (strcmp(ptr->key, "iss") == 0) {
iss = ptr;
} else if (strcmp(ptr->key, "sub") == 0) {
sub = ptr;
} else if (strcmp(ptr->key, "scope") == 0) {
scope = ptr;
} else if (strcmp(ptr->key, "aud") == 0) {
aud = ptr;
} else if (strcmp(ptr->key, "exp") == 0) {
exp = ptr;
} else if (strcmp(ptr->key, "iat") == 0) {
iat = ptr;
}
}
Json::Object object = claim.object_value();
GPR_ASSERT(iss != nullptr);
GPR_ASSERT(iss->type == GRPC_JSON_STRING);
GPR_ASSERT(
strcmp(
iss->value,
"777-abaslkan11hlb6nmim3bpspl31ud@developer.gserviceaccount.com") ==
0);
Json value = object["iss"];
GPR_ASSERT(value.type() == Json::Type::STRING);
GPR_ASSERT(value.string_value() ==
"777-abaslkan11hlb6nmim3bpspl31ud@developer.gserviceaccount.com");
if (expected_scope != nullptr) {
GPR_ASSERT(scope != nullptr);
GPR_ASSERT(sub == nullptr);
GPR_ASSERT(scope->type == GRPC_JSON_STRING);
GPR_ASSERT(strcmp(scope->value, expected_scope) == 0);
GPR_ASSERT(object.find("sub") == object.end());
value = object["scope"];
GPR_ASSERT(value.type() == Json::Type::STRING);
GPR_ASSERT(value.string_value() == expected_scope);
} else {
/* Claims without scope must have a sub. */
GPR_ASSERT(scope == nullptr);
GPR_ASSERT(sub != nullptr);
GPR_ASSERT(sub->type == GRPC_JSON_STRING);
GPR_ASSERT(strcmp(iss->value, sub->value) == 0);
GPR_ASSERT(object.find("scope") == object.end());
value = object["sub"];
GPR_ASSERT(value.type() == Json::Type::STRING);
GPR_ASSERT(value.string_value() == object["iss"].string_value());
}
GPR_ASSERT(aud != nullptr);
GPR_ASSERT(aud->type == GRPC_JSON_STRING);
GPR_ASSERT(strcmp(aud->value, expected_audience) == 0);
value = object["aud"];
GPR_ASSERT(value.type() == Json::Type::STRING);
GPR_ASSERT(value.string_value() == expected_audience);
GPR_ASSERT(exp != nullptr);
GPR_ASSERT(exp->type == GRPC_JSON_NUMBER);
expiration.tv_sec = strtol(exp->value, nullptr, 10);
gpr_timespec expiration = gpr_time_0(GPR_CLOCK_REALTIME);
value = object["exp"];
GPR_ASSERT(value.type() == Json::Type::NUMBER);
expiration.tv_sec = strtol(value.string_value().c_str(), nullptr, 10);
GPR_ASSERT(iat != nullptr);
GPR_ASSERT(iat->type == GRPC_JSON_NUMBER);
issue_time.tv_sec = strtol(iat->value, nullptr, 10);
gpr_timespec issue_time = gpr_time_0(GPR_CLOCK_REALTIME);
value = object["iat"];
GPR_ASSERT(value.type() == Json::Type::NUMBER);
issue_time.tv_sec = strtol(value.string_value().c_str(), nullptr, 10);
parsed_lifetime = gpr_time_sub(expiration, issue_time);
gpr_timespec parsed_lifetime = gpr_time_sub(expiration, issue_time);
GPR_ASSERT(parsed_lifetime.tv_sec == grpc_max_auth_token_lifetime().tv_sec);
}
@ -363,21 +321,18 @@ static char* jwt_creds_jwt_encode_and_sign(const grpc_auth_json_key* key) {
grpc_max_auth_token_lifetime(), nullptr);
}
static void service_account_creds_check_jwt_claim(grpc_json* claim) {
static void service_account_creds_check_jwt_claim(const Json& claim) {
check_jwt_claim(claim, GRPC_JWT_OAUTH2_AUDIENCE, test_scope);
}
static void jwt_creds_check_jwt_claim(grpc_json* claim) {
static void jwt_creds_check_jwt_claim(const Json& claim) {
check_jwt_claim(claim, test_service_url, nullptr);
}
static void test_jwt_encode_and_sign(
char* (*jwt_encode_and_sign_func)(const grpc_auth_json_key*),
void (*check_jwt_claim_func)(grpc_json*)) {
void (*check_jwt_claim_func)(const Json&)) {
char* json_string = test_json_key_str(nullptr);
grpc_json* parsed_header = nullptr;
grpc_json* parsed_claim = nullptr;
char* scratchpad;
grpc_auth_json_key json_key =
grpc_auth_json_key_create_from_string(json_string);
const char* b64_signature;
@ -385,23 +340,19 @@ static void test_jwt_encode_and_sign(
char* jwt = jwt_encode_and_sign_func(&json_key);
const char* dot = strchr(jwt, '.');
GPR_ASSERT(dot != nullptr);
parsed_header = parse_json_part_from_jwt(jwt, static_cast<size_t>(dot - jwt),
&scratchpad);
GPR_ASSERT(parsed_header != nullptr);
Json parsed_header =
parse_json_part_from_jwt(jwt, static_cast<size_t>(dot - jwt));
GPR_ASSERT(parsed_header.type() == Json::Type::OBJECT);
check_jwt_header(parsed_header);
offset = static_cast<size_t>(dot - jwt) + 1;
grpc_json_destroy(parsed_header);
gpr_free(scratchpad);
dot = strchr(jwt + offset, '.');
GPR_ASSERT(dot != nullptr);
parsed_claim = parse_json_part_from_jwt(
jwt + offset, static_cast<size_t>(dot - (jwt + offset)), &scratchpad);
GPR_ASSERT(parsed_claim != nullptr);
Json parsed_claim = parse_json_part_from_jwt(
jwt + offset, static_cast<size_t>(dot - (jwt + offset)));
GPR_ASSERT(parsed_claim.type() == Json::Type::OBJECT);
check_jwt_claim_func(parsed_claim);
offset = static_cast<size_t>(dot - jwt) + 1;
grpc_json_destroy(parsed_claim);
gpr_free(scratchpad);
dot = strchr(jwt + offset, '.');
GPR_ASSERT(dot == nullptr); /* no more part. */

@ -32,6 +32,8 @@
#include "src/core/lib/slice/b64.h"
#include "test/core/util/test_config.h"
using grpc_core::Json;
/* 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! */
@ -205,14 +207,17 @@ static void test_jwt_issuer_email_domain(void) {
static void test_claims_success(void) {
grpc_jwt_claims* claims;
grpc_slice s = grpc_slice_from_copied_string(claims_without_time_constraint);
grpc_json* json = grpc_json_parse_string_with_len(
reinterpret_cast<char*> GRPC_SLICE_START_PTR(s), GRPC_SLICE_LENGTH(s));
GPR_ASSERT(json != nullptr);
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(claims_without_time_constraint, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
}
GPR_ASSERT(error == GRPC_ERROR_NONE);
GPR_ASSERT(json.type() == Json::Type::OBJECT);
grpc_core::ExecCtx exec_ctx;
claims = grpc_jwt_claims_from_json(json, s);
claims = grpc_jwt_claims_from_json(json);
GPR_ASSERT(claims != nullptr);
GPR_ASSERT(grpc_jwt_claims_json(claims) == json);
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);
@ -224,17 +229,20 @@ static void test_claims_success(void) {
static void test_expired_claims_failure(void) {
grpc_jwt_claims* claims;
grpc_slice s = grpc_slice_from_copied_string(expired_claims);
grpc_json* json = grpc_json_parse_string_with_len(
reinterpret_cast<char*> GRPC_SLICE_START_PTR(s), GRPC_SLICE_LENGTH(s));
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(expired_claims, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
}
GPR_ASSERT(error == GRPC_ERROR_NONE);
GPR_ASSERT(json.type() == Json::Type::OBJECT);
gpr_timespec exp_iat = {100, 0, GPR_CLOCK_REALTIME};
gpr_timespec exp_exp = {120, 0, GPR_CLOCK_REALTIME};
gpr_timespec exp_nbf = {60, 0, GPR_CLOCK_REALTIME};
GPR_ASSERT(json != nullptr);
grpc_core::ExecCtx exec_ctx;
claims = grpc_jwt_claims_from_json(json, s);
claims = grpc_jwt_claims_from_json(json);
GPR_ASSERT(claims != nullptr);
GPR_ASSERT(grpc_jwt_claims_json(claims) == json);
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);
@ -249,21 +257,28 @@ static void test_expired_claims_failure(void) {
}
static void test_invalid_claims_failure(void) {
grpc_slice s = grpc_slice_from_copied_string(invalid_claims);
grpc_json* json = grpc_json_parse_string_with_len(
reinterpret_cast<char*> GRPC_SLICE_START_PTR(s), GRPC_SLICE_LENGTH(s));
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(invalid_claims, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
}
GPR_ASSERT(error == GRPC_ERROR_NONE);
GPR_ASSERT(json.type() == Json::Type::OBJECT);
grpc_core::ExecCtx exec_ctx;
GPR_ASSERT(grpc_jwt_claims_from_json(json, s) == nullptr);
GPR_ASSERT(grpc_jwt_claims_from_json(json) == nullptr);
}
static void test_bad_audience_claims_failure(void) {
grpc_jwt_claims* claims;
grpc_slice s = grpc_slice_from_copied_string(claims_without_time_constraint);
grpc_json* json = grpc_json_parse_string_with_len(
reinterpret_cast<char*> GRPC_SLICE_START_PTR(s), GRPC_SLICE_LENGTH(s));
GPR_ASSERT(json != nullptr);
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(claims_without_time_constraint, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
}
GPR_ASSERT(error == GRPC_ERROR_NONE);
GPR_ASSERT(json.type() == Json::Type::OBJECT);
grpc_core::ExecCtx exec_ctx;
claims = grpc_jwt_claims_from_json(json, s);
claims = grpc_jwt_claims_from_json(json);
GPR_ASSERT(claims != nullptr);
GPR_ASSERT(grpc_jwt_claims_check(claims, "https://bar.com") ==
GRPC_JWT_VERIFIER_BAD_AUDIENCE);
@ -272,12 +287,15 @@ static void test_bad_audience_claims_failure(void) {
static void test_bad_subject_claims_failure(void) {
grpc_jwt_claims* claims;
grpc_slice s = grpc_slice_from_copied_string(claims_with_bad_subject);
grpc_json* json = grpc_json_parse_string_with_len(
reinterpret_cast<char*> GRPC_SLICE_START_PTR(s), GRPC_SLICE_LENGTH(s));
GPR_ASSERT(json != nullptr);
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(claims_with_bad_subject, &error);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "JSON parse error: %s", grpc_error_string(error));
}
GPR_ASSERT(error == GRPC_ERROR_NONE);
GPR_ASSERT(json.type() == Json::Type::OBJECT);
grpc_core::ExecCtx exec_ctx;
claims = grpc_jwt_claims_from_json(json, s);
claims = grpc_jwt_claims_from_json(json);
GPR_ASSERT(claims != nullptr);
GPR_ASSERT(grpc_jwt_claims_check(claims, "https://foo.com") ==
GRPC_JWT_VERIFIER_BAD_SUBJECT);

@ -52,12 +52,9 @@ static void on_jwt_verification_done(void* user_data,
sync->success = (status == GRPC_JWT_VERIFIER_OK);
if (sync->success) {
char* claims_str;
GPR_ASSERT(claims != nullptr);
claims_str = grpc_json_dump_to_string(
const_cast<grpc_json*>(grpc_jwt_claims_json(claims)), 2);
printf("Claims: \n\n%s\n", claims_str);
gpr_free(claims_str);
std::string claims_str = grpc_jwt_claims_json(claims)->Dump(/*indent=*/2);
printf("Claims: \n\n%s\n", claims_str.c_str());
grpc_jwt_claims_destroy(claims);
} else {
GPR_ASSERT(claims == nullptr);

Loading…
Cancel
Save