diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.cc b/src/core/lib/security/credentials/google_default/google_default_credentials.cc index 3da49619835..58e550ed694 100644 --- a/src/core/lib/security/credentials/google_default/google_default_credentials.cc +++ b/src/core/lib/security/credentials/google_default/google_default_credentials.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* creds) { - grpc_json* json = nullptr; grpc_auth_json_key key; grpc_auth_refresh_token token; grpc_core::RefCountedPtr 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 GRPC_SLICE_START_PTR(creds_data), + if (error != GRPC_ERROR_NONE) goto end; + str = grpc_core::StringView( + reinterpret_cast(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; } diff --git a/src/core/lib/security/credentials/jwt/json_token.cc b/src/core/lib/security/credentials/jwt/json_token.cc index 12b7c5368da..58dcc89e682 100644 --- a/src/core/lib/security/credentials/jwt/json_token.cc +++ b/src/core/lib/security/credentials/jwt/json_token.cc @@ -39,6 +39,8 @@ extern "C" { #include } +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) { diff --git a/src/core/lib/security/credentials/jwt/json_token.h b/src/core/lib/security/credentials/jwt/json_token.h index 20390f3096a..cee3a3670af 100644 --- a/src/core/lib/security/credentials/jwt/json_token.h +++ b/src/core/lib/security/credentials/jwt/json_token.h @@ -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); diff --git a/src/core/lib/security/credentials/jwt/jwt_credentials.cc b/src/core/lib/security/credentials/jwt/jwt_credentials.cc index 09c8c32e447..bed594e5d92 100644 --- a/src/core/lib/security/credentials/jwt/jwt_credentials.cc +++ b/src/core/lib/security/credentials/jwt/jwt_credentials.cc @@ -32,6 +32,8 @@ #include #include +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(""); } - const char* redacted = ""; - grpc_json* current = json->child; - while (current) { - if (current->type == GRPC_JSON_STRING && - strcmp(current->key, "private_key") == 0) { - current->value = const_cast(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"] = ""; + return gpr_strdup(json.Dump(/*indent=*/2).c_str()); } grpc_call_credentials* grpc_service_account_jwt_access_credentials_create( diff --git a/src/core/lib/security/credentials/jwt/jwt_verifier.cc b/src/core/lib/security/credentials/jwt/jwt_verifier.cc index 9f3c24c8458..30f7f410bfa 100644 --- a/src/core/lib/security/credentials/jwt/jwt_verifier.cc +++ b/src/core/lib/security/credentials/jwt/jwt_verifier.cc @@ -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 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(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; } 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(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; }; 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(gpr_malloc(sizeof(grpc_jwt_claims))); - memset(claims, 0, sizeof(grpc_jwt_claims)); - claims->json = json; - claims->buffer = buffer; + static_cast(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: { : , : , ... } */ - 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(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(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(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(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(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(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(dot - jwt); diff --git a/src/core/lib/security/credentials/jwt/jwt_verifier.h b/src/core/lib/security/credentials/jwt/jwt_verifier.h index 3f69ada98d5..d44838668d3 100644 --- a/src/core/lib/security/credentials/jwt/jwt_verifier.h +++ b/src/core/lib/security/credentials/jwt/jwt_verifier.h @@ -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); diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc index ce846cfb7ff..3a6d19c11ec 100644 --- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc +++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc @@ -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; } diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.h b/src/core/lib/security/credentials/oauth2/oauth2_credentials.h index ba07ee04ee7..97579522950 100644 --- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.h +++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.h @@ -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); diff --git a/src/core/lib/security/util/json_util.cc b/src/core/lib/security/util/json_util.cc index 8a1db636139..458f805bc06 100644 --- a/src/core/lib/security/util/json_util.cc +++ b/src/core/lib/security/util/json_util.cc @@ -26,34 +26,41 @@ #include #include -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; diff --git a/src/core/lib/security/util/json_util.h b/src/core/lib/security/util/json_util.h index 44d9eccd545..42f7005e00e 100644 --- a/src/core/lib/security/util/json_util.h +++ b/src/core/lib/security/util/json_util.h @@ -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 */ diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc index 0e3d6d24f66..33f56d3c340 100644 --- a/src/cpp/client/secure_credentials.cc +++ b/src/cpp/client/secure_credentials.cc @@ -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 scratchpad(json_string.c_str(), - json_string.c_str() + json_string.size() + 1); - std::unique_ptr 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(); diff --git a/test/core/security/json_token_test.cc b/test/core/security/json_token_test.cc index a3ae18e6abd..d93be88aba5 100644 --- a/test/core/security/json_token_test.cc +++ b/test/core/security/json_token_test.cc @@ -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(gpr_malloc(len + 1)); + char* b64 = static_cast(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(gpr_malloc(GRPC_SLICE_LENGTH(slice) + 1)); - strncpy(decoded, reinterpret_cast 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(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(dot - jwt), - &scratchpad); - GPR_ASSERT(parsed_header != nullptr); + Json parsed_header = + parse_json_part_from_jwt(jwt, static_cast(dot - jwt)); + GPR_ASSERT(parsed_header.type() == Json::Type::OBJECT); check_jwt_header(parsed_header); offset = static_cast(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(dot - (jwt + offset)), &scratchpad); - GPR_ASSERT(parsed_claim != nullptr); + Json parsed_claim = parse_json_part_from_jwt( + jwt + offset, static_cast(dot - (jwt + offset))); + GPR_ASSERT(parsed_claim.type() == Json::Type::OBJECT); check_jwt_claim_func(parsed_claim); offset = static_cast(dot - jwt) + 1; - grpc_json_destroy(parsed_claim); - gpr_free(scratchpad); dot = strchr(jwt + offset, '.'); GPR_ASSERT(dot == nullptr); /* no more part. */ diff --git a/test/core/security/jwt_verifier_test.cc b/test/core/security/jwt_verifier_test.cc index 1e33b45f3c5..a60d874b6eb 100644 --- a/test/core/security/jwt_verifier_test.cc +++ b/test/core/security/jwt_verifier_test.cc @@ -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 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 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 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 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 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); diff --git a/test/core/security/verify_jwt.cc b/test/core/security/verify_jwt.cc index 61dde0e7d9a..cfe285f53e6 100644 --- a/test/core/security/verify_jwt.cc +++ b/test/core/security/verify_jwt.cc @@ -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_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);