Merge branch 'master' of https://github.com/grpc/grpc into tls-credentials-1

pull/21852/head
Matthew Stevenson 5 years ago
commit 25601302e4
  1. 12
      doc/PROTOCOL-HTTP2.md
  2. 73
      src/core/ext/filters/client_channel/xds/xds_api.cc
  3. 475
      src/core/ext/filters/client_channel/xds/xds_bootstrap.cc
  4. 57
      src/core/ext/filters/client_channel/xds/xds_bootstrap.h
  5. 4
      src/core/ext/filters/client_channel/xds/xds_channel.cc
  6. 12
      src/core/ext/filters/client_channel/xds/xds_channel_secure.cc
  7. 2
      src/core/ext/filters/client_channel/xds/xds_client.cc
  8. 1
      src/core/lib/iomgr/load_file.cc
  9. 1
      src/core/lib/json/json.h
  10. 18
      src/core/lib/security/credentials/google_default/google_default_credentials.cc
  11. 82
      src/core/lib/security/credentials/jwt/json_token.cc
  12. 3
      src/core/lib/security/credentials/jwt/json_token.h
  13. 26
      src/core/lib/security/credentials/jwt/jwt_credentials.cc
  14. 308
      src/core/lib/security/credentials/jwt/jwt_verifier.cc
  15. 5
      src/core/lib/security/credentials/jwt/jwt_verifier.h
  16. 71
      src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
  17. 2
      src/core/lib/security/credentials/oauth2/oauth2_credentials.h
  18. 3
      src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h
  19. 23
      src/core/lib/security/security_connector/tls/tls_security_connector.cc
  20. 6
      src/core/lib/security/security_connector/tls/tls_security_connector.h
  21. 37
      src/core/lib/security/util/json_util.cc
  22. 4
      src/core/lib/security/util/json_util.h
  23. 2
      src/cpp/README.md
  24. 36
      src/cpp/client/secure_credentials.cc
  25. 6
      src/objective-c/tests/build_one_example.sh
  26. 2
      src/objective-c/tests/build_tests.sh
  27. 1
      src/objective-c/tests/run_one_test.sh
  28. 20
      src/objective-c/tests/verbose_time.sh
  29. 286
      test/core/client_channel/xds_bootstrap_test.cc
  30. 11
      test/core/end2end/fixtures/h2_tls.cc
  31. 2
      test/core/security/BUILD
  32. 173
      test/core/security/json_token_test.cc
  33. 70
      test/core/security/jwt_verifier_test.cc
  34. 36
      test/core/security/tls_security_connector_test.cc
  35. 7
      test/core/security/verify_jwt.cc
  36. 47
      test/cpp/end2end/test_service_impl.cc
  37. 1
      test/cpp/end2end/test_service_impl.h
  38. 12
      test/cpp/qps/server_async.cc

@ -31,12 +31,12 @@ Request-Headers are delivered as HTTP2 headers in HEADERS + CONTINUATION frames.
* **Timeout** → "grpc-timeout" TimeoutValue TimeoutUnit
* **TimeoutValue** → {_positive integer as ASCII string of at most 8 digits_}
* **TimeoutUnit** → Hour / Minute / Second / Millisecond / Microsecond / Nanosecond
* **Hour** → "H"
* **Minute** → "M"
* **Second** → "S"
* **Millisecond** → "m"
* **Microsecond** → "u"
* **Nanosecond** → "n"
* **Hour** → "H"
* **Minute** → "M"
* **Second** → "S"
* **Millisecond** → "m"
* **Microsecond** → "u"
* **Nanosecond** → "n"
* **Content-Type** → "content-type" "application/grpc" [("+proto" / "+json" / {_custom_})]
* **Content-Coding** → "identity" / "gzip" / "deflate" / "snappy" / {_custom_}
* <a name="message-encoding"></a>**Message-Encoding** → "grpc-encoding" Content-Coding

@ -20,6 +20,7 @@
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <grpc/impl/codegen/log.h>
#include <grpc/support/alloc.h>
@ -106,10 +107,10 @@ bool XdsDropConfig::ShouldDrop(const std::string** category_name) const {
namespace {
void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb,
const XdsBootstrap::MetadataValue& value);
const Json& value);
void PopulateListValue(upb_arena* arena, google_protobuf_ListValue* list_value,
const std::vector<XdsBootstrap::MetadataValue>& values) {
const Json::Array& values) {
for (const auto& value : values) {
auto* value_pb = google_protobuf_ListValue_add_values(list_value, arena);
PopulateMetadataValue(arena, value_pb, value);
@ -117,13 +118,12 @@ void PopulateListValue(upb_arena* arena, google_protobuf_ListValue* list_value,
}
void PopulateMetadata(upb_arena* arena, google_protobuf_Struct* metadata_pb,
const std::map<const char*, XdsBootstrap::MetadataValue,
StringLess>& metadata) {
const Json::Object& metadata) {
for (const auto& p : metadata) {
google_protobuf_Struct_FieldsEntry* field =
google_protobuf_Struct_add_fields(metadata_pb, arena);
google_protobuf_Struct_FieldsEntry_set_key(field,
upb_strview_makez(p.first));
google_protobuf_Struct_FieldsEntry_set_key(
field, upb_strview_makez(p.first.c_str()));
google_protobuf_Value* value =
google_protobuf_Struct_FieldsEntry_mutable_value(field, arena);
PopulateMetadataValue(arena, value, p.second);
@ -131,31 +131,35 @@ void PopulateMetadata(upb_arena* arena, google_protobuf_Struct* metadata_pb,
}
void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb,
const XdsBootstrap::MetadataValue& value) {
switch (value.type) {
case XdsBootstrap::MetadataValue::Type::MD_NULL:
const Json& value) {
switch (value.type()) {
case Json::Type::JSON_NULL:
google_protobuf_Value_set_null_value(value_pb, 0);
break;
case XdsBootstrap::MetadataValue::Type::DOUBLE:
google_protobuf_Value_set_number_value(value_pb, value.double_value);
case Json::Type::NUMBER:
google_protobuf_Value_set_number_value(
value_pb, strtod(value.string_value().c_str(), nullptr));
break;
case XdsBootstrap::MetadataValue::Type::STRING:
case Json::Type::STRING:
google_protobuf_Value_set_string_value(
value_pb, upb_strview_makez(value.string_value));
value_pb, upb_strview_makez(value.string_value().c_str()));
break;
case XdsBootstrap::MetadataValue::Type::BOOL:
google_protobuf_Value_set_bool_value(value_pb, value.bool_value);
case Json::Type::JSON_TRUE:
google_protobuf_Value_set_bool_value(value_pb, true);
break;
case XdsBootstrap::MetadataValue::Type::STRUCT: {
case Json::Type::JSON_FALSE:
google_protobuf_Value_set_bool_value(value_pb, false);
break;
case Json::Type::OBJECT: {
google_protobuf_Struct* struct_value =
google_protobuf_Value_mutable_struct_value(value_pb, arena);
PopulateMetadata(arena, struct_value, value.struct_value);
PopulateMetadata(arena, struct_value, value.object_value());
break;
}
case XdsBootstrap::MetadataValue::Type::LIST: {
case Json::Type::ARRAY: {
google_protobuf_ListValue* list_value =
google_protobuf_Value_mutable_list_value(value_pb, arena);
PopulateListValue(arena, list_value, value.list_value);
PopulateListValue(arena, list_value, value.array_value());
break;
}
}
@ -164,33 +168,34 @@ void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb,
void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node,
const char* build_version, envoy_api_v2_core_Node* node_msg) {
if (node != nullptr) {
if (node->id != nullptr) {
envoy_api_v2_core_Node_set_id(node_msg, upb_strview_makez(node->id));
if (!node->id.empty()) {
envoy_api_v2_core_Node_set_id(node_msg,
upb_strview_makez(node->id.c_str()));
}
if (node->cluster != nullptr) {
envoy_api_v2_core_Node_set_cluster(node_msg,
upb_strview_makez(node->cluster));
if (!node->cluster.empty()) {
envoy_api_v2_core_Node_set_cluster(
node_msg, upb_strview_makez(node->cluster.c_str()));
}
if (!node->metadata.empty()) {
if (!node->metadata.object_value().empty()) {
google_protobuf_Struct* metadata =
envoy_api_v2_core_Node_mutable_metadata(node_msg, arena);
PopulateMetadata(arena, metadata, node->metadata);
PopulateMetadata(arena, metadata, node->metadata.object_value());
}
if (node->locality_region != nullptr || node->locality_zone != nullptr ||
node->locality_subzone != nullptr) {
if (!node->locality_region.empty() || !node->locality_zone.empty() ||
!node->locality_subzone.empty()) {
envoy_api_v2_core_Locality* locality =
envoy_api_v2_core_Node_mutable_locality(node_msg, arena);
if (node->locality_region != nullptr) {
if (!node->locality_region.empty()) {
envoy_api_v2_core_Locality_set_region(
locality, upb_strview_makez(node->locality_region));
locality, upb_strview_makez(node->locality_region.c_str()));
}
if (node->locality_zone != nullptr) {
if (!node->locality_zone.empty()) {
envoy_api_v2_core_Locality_set_zone(
locality, upb_strview_makez(node->locality_zone));
locality, upb_strview_makez(node->locality_zone.c_str()));
}
if (node->locality_subzone != nullptr) {
if (!node->locality_subzone.empty()) {
envoy_api_v2_core_Locality_set_sub_zone(
locality, upb_strview_makez(node->locality_subzone));
locality, upb_strview_makez(node->locality_subzone.c_str()));
}
}
}

@ -39,86 +39,54 @@ std::unique_ptr<XdsBootstrap> XdsBootstrap::ReadFromFile(grpc_error** error) {
grpc_slice contents;
*error = grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents);
if (*error != GRPC_ERROR_NONE) return nullptr;
return grpc_core::MakeUnique<XdsBootstrap>(contents, error);
Json json = Json::Parse(StringViewFromSlice(contents), error);
grpc_slice_unref_internal(contents);
if (*error != GRPC_ERROR_NONE) return nullptr;
return grpc_core::MakeUnique<XdsBootstrap>(std::move(json), error);
}
XdsBootstrap::XdsBootstrap(grpc_slice contents, grpc_error** error)
: contents_(contents) {
tree_ = grpc_json_parse_string_with_len(
reinterpret_cast<char*>(GPR_SLICE_START_PTR(contents_)),
GPR_SLICE_LENGTH(contents_));
if (tree_ == nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"failed to parse bootstrap file JSON");
return;
}
if (tree_->type != GRPC_JSON_OBJECT || tree_->key != nullptr) {
XdsBootstrap::XdsBootstrap(Json json, grpc_error** error) {
if (json.type() != Json::Type::OBJECT) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"malformed JSON in bootstrap file");
return;
}
InlinedVector<grpc_error*, 1> error_list;
bool seen_xds_servers = false;
bool seen_node = false;
for (grpc_json* child = tree_->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "xds_servers") == 0) {
if (child->type != GRPC_JSON_ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"xds_servers\" field is not an array"));
}
if (seen_xds_servers) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"xds_servers\" field"));
}
seen_xds_servers = true;
grpc_error* parse_error = ParseXdsServerList(child);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} else if (strcmp(child->key, "node") == 0) {
if (child->type != GRPC_JSON_OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"node\" field is not an object"));
}
if (seen_node) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"node\" field"));
}
seen_node = true;
grpc_error* parse_error = ParseNode(child);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
if (!seen_xds_servers) {
auto it = json.mutable_object()->find("xds_servers");
if (it == json.mutable_object()->end()) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"xds_servers\" field not present"));
} else if (it->second.type() != Json::Type::ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"xds_servers\" field is not an array"));
} else {
grpc_error* parse_error = ParseXdsServerList(&it->second);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
it = json.mutable_object()->find("node");
if (it != json.mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"node\" field is not an object"));
} else {
grpc_error* parse_error = ParseNode(&it->second);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
*error = GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing xds bootstrap file",
&error_list);
}
XdsBootstrap::~XdsBootstrap() {
grpc_json_destroy(tree_);
grpc_slice_unref_internal(contents_);
}
grpc_error* XdsBootstrap::ParseXdsServerList(grpc_json* json) {
grpc_error* XdsBootstrap::ParseXdsServerList(Json* json) {
InlinedVector<grpc_error*, 1> error_list;
size_t idx = 0;
for (grpc_json *child = json->child; child != nullptr;
child = child->next, ++idx) {
if (child->key != nullptr) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " key is not null", idx);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
}
if (child->type != GRPC_JSON_OBJECT) {
for (Json& child : *json->mutable_array()) {
if (child.type() != Json::Type::OBJECT) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", idx);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
} else {
grpc_error* parse_error = ParseXdsServer(child, idx);
grpc_error* parse_error = ParseXdsServer(&child, idx);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
@ -126,42 +94,29 @@ grpc_error* XdsBootstrap::ParseXdsServerList(grpc_json* json) {
&error_list);
}
grpc_error* XdsBootstrap::ParseXdsServer(grpc_json* json, size_t idx) {
grpc_error* XdsBootstrap::ParseXdsServer(Json* json, size_t idx) {
InlinedVector<grpc_error*, 1> error_list;
servers_.emplace_back();
XdsServer& server = servers_[servers_.size() - 1];
bool seen_channel_creds = false;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "server_uri") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"server_uri\" field is not a string"));
}
if (server.server_uri != nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"server_uri\" field"));
}
server.server_uri = child->value;
} else if (strcmp(child->key, "channel_creds") == 0) {
if (child->type != GRPC_JSON_ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"channel_creds\" field is not an array"));
}
if (seen_channel_creds) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"channel_creds\" field"));
}
seen_channel_creds = true;
grpc_error* parse_error = ParseChannelCredsArray(child, &server);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
if (server.server_uri == nullptr) {
auto it = json->mutable_object()->find("server_uri");
if (it == json->mutable_object()->end()) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"server_uri\" field not present"));
} else if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"server_uri\" field is not a string"));
} else {
server.server_uri = std::move(*it->second.mutable_string_value());
}
it = json->mutable_object()->find("channel_creds");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"channel_creds\" field is not an array"));
} else {
grpc_error* parse_error = ParseChannelCredsArray(&it->second, &server);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case.
@ -176,23 +131,17 @@ grpc_error* XdsBootstrap::ParseXdsServer(grpc_json* json, size_t idx) {
return error;
}
grpc_error* XdsBootstrap::ParseChannelCredsArray(grpc_json* json,
grpc_error* XdsBootstrap::ParseChannelCredsArray(Json* json,
XdsServer* server) {
InlinedVector<grpc_error*, 1> error_list;
size_t idx = 0;
for (grpc_json *child = json->child; child != nullptr;
child = child->next, ++idx) {
if (child->key != nullptr) {
for (size_t i = 0; i < json->mutable_array()->size(); ++i) {
Json& child = json->mutable_array()->at(i);
if (child.type() != Json::Type::OBJECT) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " key is not null", idx);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
}
if (child->type != GRPC_JSON_OBJECT) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", idx);
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", i);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
} else {
grpc_error* parse_error = ParseChannelCreds(child, idx, server);
grpc_error* parse_error = ParseChannelCreds(&child, i, server);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
@ -200,38 +149,31 @@ grpc_error* XdsBootstrap::ParseChannelCredsArray(grpc_json* json,
&error_list);
}
grpc_error* XdsBootstrap::ParseChannelCreds(grpc_json* json, size_t idx,
grpc_error* XdsBootstrap::ParseChannelCreds(Json* json, size_t idx,
XdsServer* server) {
InlinedVector<grpc_error*, 1> error_list;
ChannelCreds channel_creds;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "type") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"type\" field is not a string"));
}
if (channel_creds.type != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"type\" field"));
}
channel_creds.type = child->value;
} else if (strcmp(child->key, "config") == 0) {
if (child->type != GRPC_JSON_OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"config\" field is not an object"));
}
if (channel_creds.config != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"config\" field"));
}
channel_creds.config = child;
auto it = json->mutable_object()->find("type");
if (it == json->mutable_object()->end()) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field not present"));
} else if (it->second.type() != Json::Type::STRING) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field is not a string"));
} else {
channel_creds.type = std::move(*it->second.mutable_string_value());
}
it = json->mutable_object()->find("config");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"config\" field is not an object"));
} else {
channel_creds.config = std::move(it->second);
}
}
if (channel_creds.type != nullptr) {
server->channel_creds.push_back(channel_creds);
if (!channel_creds.type.empty()) {
server->channel_creds.emplace_back(std::move(channel_creds));
}
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case.
@ -246,242 +188,81 @@ grpc_error* XdsBootstrap::ParseChannelCreds(grpc_json* json, size_t idx,
return error;
}
grpc_error* XdsBootstrap::ParseNode(grpc_json* json) {
grpc_error* XdsBootstrap::ParseNode(Json* json) {
InlinedVector<grpc_error*, 1> error_list;
node_ = grpc_core::MakeUnique<Node>();
bool seen_metadata = false;
bool seen_locality = false;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
auto it = json->mutable_object()->find("id");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "id") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"id\" field is not a string"));
}
if (node_->id != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"id\" field"));
}
node_->id = child->value;
} else if (strcmp(child->key, "cluster") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"cluster\" field is not a string"));
}
if (node_->cluster != nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"cluster\" field"));
}
node_->cluster = child->value;
} else if (strcmp(child->key, "locality") == 0) {
if (child->type != GRPC_JSON_OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"locality\" field is not an object"));
}
if (seen_locality) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"locality\" field"));
}
seen_locality = true;
grpc_error* parse_error = ParseLocality(child);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} else if (strcmp(child->key, "metadata") == 0) {
if (child->type != GRPC_JSON_OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"metadata\" field is not an object"));
}
if (seen_metadata) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"metadata\" field"));
}
seen_metadata = true;
InlinedVector<grpc_error*, 1> parse_errors =
ParseMetadataStruct(child, &node_->metadata);
if (!parse_errors.empty()) {
grpc_error* parse_error = GRPC_ERROR_CREATE_FROM_VECTOR(
"errors parsing \"metadata\" object", &parse_errors);
error_list.push_back(parse_error);
}
GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"id\" field is not a string"));
} else {
node_->id = std::move(*it->second.mutable_string_value());
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"node\" object",
&error_list);
}
grpc_error* XdsBootstrap::ParseLocality(grpc_json* json) {
InlinedVector<grpc_error*, 1> error_list;
node_->locality_region = nullptr;
node_->locality_zone = nullptr;
node_->locality_subzone = nullptr;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "region") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"region\" field is not a string"));
}
if (node_->locality_region != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"region\" field"));
}
node_->locality_region = child->value;
} else if (strcmp(child->key, "zone") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"zone\" field is not a string"));
}
if (node_->locality_zone != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"zone\" field"));
}
node_->locality_zone = child->value;
} else if (strcmp(child->key, "subzone") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"subzone\" field is not a string"));
}
if (node_->locality_subzone != nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"subzone\" field"));
}
node_->locality_subzone = child->value;
it = json->mutable_object()->find("cluster");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"cluster\" field is not a string"));
} else {
node_->cluster = std::move(*it->second.mutable_string_value());
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"locality\" object",
&error_list);
}
InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataStruct(
grpc_json* json,
std::map<const char*, XdsBootstrap::MetadataValue, StringLess>* result) {
InlinedVector<grpc_error*, 1> error_list;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
continue;
it = json->mutable_object()->find("locality");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"locality\" field is not an object"));
} else {
grpc_error* parse_error = ParseLocality(&it->second);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
if (result->find(child->key) != result->end()) {
char* msg;
gpr_asprintf(&msg, "duplicate metadata key \"%s\"", child->key);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
gpr_free(msg);
}
it = json->mutable_object()->find("metadata");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"metadata\" field is not an object"));
} else {
node_->metadata = std::move(it->second);
}
MetadataValue& value = (*result)[child->key];
grpc_error* parse_error = ParseMetadataValue(child, 0, &value);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
return error_list;
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"node\" object",
&error_list);
}
InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataList(
grpc_json* json, std::vector<MetadataValue>* result) {
grpc_error* XdsBootstrap::ParseLocality(Json* json) {
InlinedVector<grpc_error*, 1> error_list;
size_t idx = 0;
for (grpc_json *child = json->child; child != nullptr;
child = child->next, ++idx) {
if (child->key != nullptr) {
char* msg;
gpr_asprintf(&msg, "JSON key is non-null for index %" PRIuPTR, idx);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
gpr_free(msg);
auto it = json->mutable_object()->find("region");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"region\" field is not a string"));
} else {
node_->locality_region = std::move(*it->second.mutable_string_value());
}
result->emplace_back();
grpc_error* parse_error = ParseMetadataValue(child, idx, &result->back());
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
return error_list;
}
grpc_error* XdsBootstrap::ParseMetadataValue(grpc_json* json, size_t idx,
MetadataValue* result) {
grpc_error* error = GRPC_ERROR_NONE;
auto context_func = [json, idx]() {
char* context;
if (json->key != nullptr) {
gpr_asprintf(&context, "key \"%s\"", json->key);
it = json->mutable_object()->find("zone");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"zone\" field is not a string"));
} else {
gpr_asprintf(&context, "index %" PRIuPTR, idx);
node_->locality_zone = std::move(*it->second.mutable_string_value());
}
return context;
};
switch (json->type) {
case GRPC_JSON_STRING:
result->type = MetadataValue::Type::STRING;
result->string_value = json->value;
break;
case GRPC_JSON_NUMBER:
result->type = MetadataValue::Type::DOUBLE;
errno = 0; // To distinguish error.
result->double_value = strtod(json->value, nullptr);
if (errno != 0) {
char* context = context_func();
char* msg;
gpr_asprintf(&msg, "error parsing numeric value for %s: \"%s\"",
context, json->value);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(context);
gpr_free(msg);
}
break;
case GRPC_JSON_TRUE:
result->type = MetadataValue::Type::BOOL;
result->bool_value = true;
break;
case GRPC_JSON_FALSE:
result->type = MetadataValue::Type::BOOL;
result->bool_value = false;
break;
case GRPC_JSON_NULL:
result->type = MetadataValue::Type::MD_NULL;
break;
case GRPC_JSON_ARRAY: {
result->type = MetadataValue::Type::LIST;
InlinedVector<grpc_error*, 1> error_list =
ParseMetadataList(json, &result->list_value);
if (!error_list.empty()) {
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case.
char* context = context_func();
char* msg;
gpr_asprintf(&msg, "errors parsing struct for %s", context);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(context);
gpr_free(msg);
for (size_t i = 0; i < error_list.size(); ++i) {
error = grpc_error_add_child(error, error_list[i]);
}
}
break;
}
case GRPC_JSON_OBJECT: {
result->type = MetadataValue::Type::STRUCT;
InlinedVector<grpc_error*, 1> error_list =
ParseMetadataStruct(json, &result->struct_value);
if (!error_list.empty()) {
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case.
char* context = context_func();
char* msg;
gpr_asprintf(&msg, "errors parsing struct for %s", context);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(context);
gpr_free(msg);
for (size_t i = 0; i < error_list.size(); ++i) {
error = grpc_error_add_child(error, error_list[i]);
GRPC_ERROR_UNREF(error_list[i]);
}
}
break;
}
it = json->mutable_object()->find("subzone");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"subzone\" field is not a string"));
} else {
node_->locality_subzone = std::move(*it->second.mutable_string_value());
}
default:
break;
}
return error;
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"locality\" object",
&error_list);
}
} // namespace grpc_core

@ -19,6 +19,8 @@
#include <grpc/support/port_platform.h>
#include <memory>
#include <string>
#include <vector>
#include <grpc/impl/codegen/slice.h>
@ -33,33 +35,22 @@ namespace grpc_core {
class XdsBootstrap {
public:
struct MetadataValue {
enum class Type { MD_NULL, DOUBLE, STRING, BOOL, STRUCT, LIST };
Type type = Type::MD_NULL;
// TODO(roth): Once we can use C++17, these can be in a std::variant.
double double_value;
const char* string_value;
bool bool_value;
std::map<const char*, MetadataValue, StringLess> struct_value;
std::vector<MetadataValue> list_value;
};
struct Node {
const char* id = nullptr;
const char* cluster = nullptr;
const char* locality_region = nullptr;
const char* locality_zone = nullptr;
const char* locality_subzone = nullptr;
std::map<const char*, MetadataValue, StringLess> metadata;
std::string id;
std::string cluster;
std::string locality_region;
std::string locality_zone;
std::string locality_subzone;
Json metadata;
};
struct ChannelCreds {
const char* type = nullptr;
grpc_json* config = nullptr;
std::string type;
Json config;
};
struct XdsServer {
const char* server_uri = nullptr;
std::string server_uri;
InlinedVector<ChannelCreds, 1> channel_creds;
};
@ -68,8 +59,7 @@ class XdsBootstrap {
static std::unique_ptr<XdsBootstrap> ReadFromFile(grpc_error** error);
// Do not instantiate directly -- use ReadFromFile() above instead.
XdsBootstrap(grpc_slice contents, grpc_error** error);
~XdsBootstrap();
XdsBootstrap(Json json, grpc_error** error);
// TODO(roth): We currently support only one server. Fix this when we
// add support for fallback for the xds channel.
@ -77,23 +67,12 @@ class XdsBootstrap {
const Node* node() const { return node_.get(); }
private:
grpc_error* ParseXdsServerList(grpc_json* json);
grpc_error* ParseXdsServer(grpc_json* json, size_t idx);
grpc_error* ParseChannelCredsArray(grpc_json* json, XdsServer* server);
grpc_error* ParseChannelCreds(grpc_json* json, size_t idx, XdsServer* server);
grpc_error* ParseNode(grpc_json* json);
grpc_error* ParseLocality(grpc_json* json);
InlinedVector<grpc_error*, 1> ParseMetadataStruct(
grpc_json* json,
std::map<const char*, MetadataValue, StringLess>* result);
InlinedVector<grpc_error*, 1> ParseMetadataList(
grpc_json* json, std::vector<MetadataValue>* result);
grpc_error* ParseMetadataValue(grpc_json* json, size_t idx,
MetadataValue* result);
grpc_slice contents_;
grpc_json* tree_ = nullptr;
grpc_error* ParseXdsServerList(Json* json);
grpc_error* ParseXdsServer(Json* json, size_t idx);
grpc_error* ParseChannelCredsArray(Json* json, XdsServer* server);
grpc_error* ParseChannelCreds(Json* json, size_t idx, XdsServer* server);
grpc_error* ParseNode(Json* json);
grpc_error* ParseLocality(Json* json);
InlinedVector<XdsServer, 1> servers_;
std::unique_ptr<Node> node_;

@ -31,8 +31,8 @@ grpc_channel_args* ModifyXdsChannelArgs(grpc_channel_args* args) {
grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap,
const grpc_channel_args& args) {
if (!bootstrap.server().channel_creds.empty()) return nullptr;
return grpc_insecure_channel_create(bootstrap.server().server_uri, &args,
nullptr);
return grpc_insecure_channel_create(bootstrap.server().server_uri.c_str(),
&args, nullptr);
}
} // namespace grpc_core

@ -69,12 +69,10 @@ grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap,
RefCountedPtr<grpc_channel_credentials> creds_to_unref;
if (!bootstrap.server().channel_creds.empty()) {
for (size_t i = 0; i < bootstrap.server().channel_creds.size(); ++i) {
if (strcmp(bootstrap.server().channel_creds[i].type, "google_default") ==
0) {
if (bootstrap.server().channel_creds[i].type == "google_default") {
creds = grpc_google_default_credentials_create();
break;
} else if (strcmp(bootstrap.server().channel_creds[i].type, "fake") ==
0) {
} else if (bootstrap.server().channel_creds[i].type == "fake") {
creds = grpc_fake_transport_security_credentials_create();
break;
}
@ -85,15 +83,15 @@ grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap,
creds = grpc_channel_credentials_find_in_args(&args);
if (creds == nullptr) {
// Built with security but parent channel is insecure.
return grpc_insecure_channel_create(bootstrap.server().server_uri, &args,
nullptr);
return grpc_insecure_channel_create(bootstrap.server().server_uri.c_str(),
&args, nullptr);
}
}
const char* arg_to_remove = GRPC_ARG_CHANNEL_CREDENTIALS;
grpc_channel_args* new_args =
grpc_channel_args_copy_and_remove(&args, &arg_to_remove, 1);
grpc_channel* channel = grpc_secure_channel_create(
creds, bootstrap.server().server_uri, new_args, nullptr);
creds, bootstrap.server().server_uri.c_str(), new_args, nullptr);
grpc_channel_args_destroy(new_args);
return channel;
}

@ -1735,7 +1735,7 @@ XdsClient::XdsClient(Combiner* combiner, grpc_pollset_set* interested_parties,
}
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
gpr_log(GPR_INFO, "[xds_client %p: creating channel to %s", this,
bootstrap_->server().server_uri);
bootstrap_->server().server_uri.c_str());
}
chand_ = MakeOrphanable<ChannelState>(
Ref(DEBUG_LOCATION, "XdsClient+ChannelState"), channel_args);

@ -53,6 +53,7 @@ grpc_error* grpc_load_file(const char* filename, int add_null_terminator,
gpr_malloc(contents_size + (add_null_terminator ? 1 : 0)));
bytes_read = fread(contents, 1, contents_size, file);
if (bytes_read < contents_size) {
gpr_free(contents);
error = GRPC_OS_ERROR(errno, "fread");
GPR_ASSERT(ferror(file));
goto end;

@ -163,6 +163,7 @@ class Json {
// Accessor methods.
Type type() const { return type_; }
const std::string& string_value() const { return string_value_; }
std::string* mutable_string_value() { return &string_value_; }
const Object& object_value() const { return object_value_; }
Object* mutable_object() { return &object_value_; }
const Array& array_value() const { return array_value_; }

@ -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);

@ -273,7 +273,8 @@ struct grpc_tls_credentials_options
private:
grpc_ssl_client_certificate_request_type cert_request_type_;
grpc_tls_server_verification_option server_verification_option_;
grpc_tls_server_verification_option server_verification_option_ =
GRPC_TLS_SERVER_VERIFICATION;
grpc_core::RefCountedPtr<grpc_tls_key_materials_config> key_materials_config_;
grpc_core::RefCountedPtr<grpc_tls_credential_reload_config>
credential_reload_config_;

@ -112,6 +112,18 @@ grpc_status_code TlsFetchKeyMaterials(
return status;
}
grpc_error* TlsCheckHostName(const char* peer_name, const tsi_peer* peer) {
/* Check the peer name if specified. */
if (peer_name != nullptr && !grpc_ssl_host_matches_name(peer, peer_name)) {
char* msg;
gpr_asprintf(&msg, "Peer name %s is not in peer certificate", peer_name);
grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(msg);
return error;
}
return GRPC_ERROR_NONE;
}
TlsChannelSecurityConnector::TlsChannelSecurityConnector(
grpc_core::RefCountedPtr<grpc_channel_credentials> channel_creds,
grpc_core::RefCountedPtr<grpc_call_credentials> request_metadata_creds,
@ -180,6 +192,17 @@ void TlsChannelSecurityConnector::check_peer(
grpc_ssl_peer_to_auth_context(&peer, GRPC_TLS_TRANSPORT_SECURITY_TYPE);
const TlsCredentials* creds =
static_cast<const TlsCredentials*>(channel_creds());
if (creds->options().server_verification_option() ==
GRPC_TLS_SERVER_VERIFICATION) {
/* Do the default host name check if specifying the target name. */
error = TlsCheckHostName(target_name, &peer);
if (error != GRPC_ERROR_NONE) {
grpc_core::ExecCtx::Run(DEBUG_LOCATION, on_peer_checked, error);
tsi_peer_destruct(&peer);
return;
}
}
/* Do the custom server authorization check, if specified by the user. */
const grpc_tls_server_authorization_check_config* config =
creds->options().server_authorization_check_config();
/* If server authorization config is not null, use it to perform

@ -144,13 +144,17 @@ class TlsServerSecurityConnector final : public grpc_server_security_connector {
grpc_core::RefCountedPtr<grpc_tls_key_materials_config> key_materials_config_;
};
// Exposed for testing only.
// ---- Functions below are exposed for testing only -----------------------
grpc_status_code TlsFetchKeyMaterials(
const grpc_core::RefCountedPtr<grpc_tls_key_materials_config>&
key_materials_config,
const grpc_tls_credentials_options& options, bool server_config,
grpc_ssl_certificate_config_reload_status* status);
// TlsCheckHostName checks if |peer_name| matches the identity information
// contained in |peer|. This is AKA hostname check.
grpc_error* TlsCheckHostName(const char* peer_name, const tsi_peer* peer);
} // namespace grpc_core
#endif /* GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_TLS_TLS_SECURITY_CONNECTOR_H \

@ -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 */

@ -16,7 +16,7 @@ provides fast builds and it easily handles dependencies that support bazel.
To add gRPC as a dependency in bazel:
1. determine commit SHA for the grpc release you want to use
2. Use the [http_archive](https://docs.bazel.build/versions/master/be/workspace.html#http_archive) bazel rule to include gRPC source
2. Use the [http_archive](https://docs.bazel.build/versions/master/repo/http.html#http_archive) bazel rule to include gRPC source
```
http_archive(
name = "com_github_grpc_grpc",

@ -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();

@ -25,6 +25,8 @@ set -ev
# CocoaPods requires the terminal to be using UTF-8 encoding.
export LANG=en_US.UTF-8
TEST_PATH=$(cd "$(dirname $0)" > /dev/null ; pwd)
cd `dirname $0`/../../..
cd $EXAMPLE_PATH
@ -34,7 +36,7 @@ rm -rf Pods
rm -rf *.xcworkspace
rm -f Podfile.lock
pod install
pod install | $TEST_PATH/verbose_time.sh
set -o pipefail
XCODEBUILD_FILTER='(^CompileC |^Ld |^.*clang |^ *cd |^ *export |^Libtool |^.*libtool |^CpHeader |^ *builtin-copy )'
@ -47,6 +49,7 @@ if [ "$SCHEME" == "tvOS-sample" ]; then
-derivedDataPath Build/Build \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
| $TEST_PATH/verbose_time.sh \
| egrep -v "$XCODEBUILD_FILTER" \
| egrep -v "^$" -
else
@ -58,6 +61,7 @@ else
-derivedDataPath Build/Build \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
| $TEST_PATH/verbose_time.sh \
| egrep -v "$XCODEBUILD_FILTER" \
| egrep -v "^$" -
fi

@ -36,5 +36,5 @@ rm -f Podfile.lock
rm -f RemoteTestClient/*.{h,m}
echo "TIME: $(date)"
pod install
pod install | ./verbose_time.sh

@ -86,6 +86,7 @@ xcodebuild \
HOST_PORT_LOCAL=localhost:$PLAIN_PORT \
HOST_PORT_REMOTE=grpc-test.sandbox.googleapis.com \
test \
| ./verbose_time.sh \
| egrep -v "$XCODEBUILD_FILTER" \
| egrep -v '^$' \
| egrep -v "(GPBDictionary|GPBArray)" -

@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2019 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/bin/bash
while IFS= read -r line; do
echo "$(date) - $line"
done

@ -36,7 +36,7 @@ void VerifyRegexMatch(grpc_error* error, const std::regex& e) {
}
TEST(XdsBootstrapTest, Basic) {
const char* json =
const char* json_str =
"{"
" \"xds_servers\": ["
" {"
@ -70,99 +70,46 @@ TEST(XdsBootstrapTest, Basic) {
" \"ignore\": {}"
" },"
" \"metadata\": {"
" \"null\": null,"
" \"string\": \"quux\","
" \"double\": 123.4,"
" \"bool\": true,"
" \"struct\": {"
" \"whee\": 0"
" },"
" \"list\": [1, 2, 3]"
" \"foo\": 1,"
" \"bar\": 2"
" },"
" \"ignore\": \"whee\""
" },"
" \"ignore\": {}"
"}";
grpc_slice slice = grpc_slice_from_copied_string(json);
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
EXPECT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
EXPECT_STREQ(bootstrap.server().server_uri, "fake:///lb");
EXPECT_EQ(bootstrap.server().server_uri, "fake:///lb");
ASSERT_EQ(bootstrap.server().channel_creds.size(), 1);
EXPECT_STREQ(bootstrap.server().channel_creds[0].type, "fake");
EXPECT_EQ(bootstrap.server().channel_creds[0].config, nullptr);
EXPECT_EQ(bootstrap.server().channel_creds[0].type, "fake");
EXPECT_EQ(bootstrap.server().channel_creds[0].config.type(),
Json::Type::JSON_NULL);
ASSERT_NE(bootstrap.node(), nullptr);
EXPECT_STREQ(bootstrap.node()->id, "foo");
EXPECT_STREQ(bootstrap.node()->cluster, "bar");
EXPECT_STREQ(bootstrap.node()->locality_region, "milky_way");
EXPECT_STREQ(bootstrap.node()->locality_zone, "sol_system");
EXPECT_STREQ(bootstrap.node()->locality_subzone, "earth");
EXPECT_THAT(
bootstrap.node()->metadata,
::testing::ElementsAre(
::testing::Pair(
::testing::StrEq("bool"),
::testing::AllOf(
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::BOOL),
::testing::Field(&XdsBootstrap::MetadataValue::bool_value,
true))),
::testing::Pair(
::testing::StrEq("double"),
::testing::AllOf(
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::DOUBLE),
::testing::Field(&XdsBootstrap::MetadataValue::double_value,
123.4))),
::testing::Pair(
::testing::StrEq("list"),
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::LIST)),
::testing::Pair(::testing::StrEq("null"),
::testing::AllOf(::testing::Field(
&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::MD_NULL))),
::testing::Pair(
::testing::StrEq("string"),
::testing::AllOf(
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::STRING),
::testing::Field(&XdsBootstrap::MetadataValue::string_value,
::testing::StrEq("quux")))),
::testing::Pair(
::testing::StrEq("struct"),
::testing::AllOf(
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::STRUCT),
::testing::Field(
&XdsBootstrap::MetadataValue::struct_value,
::testing::ElementsAre(::testing::Pair(
::testing::StrEq("whee"),
::testing::AllOf(
::testing::Field(
&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::DOUBLE),
::testing::Field(
&XdsBootstrap::MetadataValue::double_value,
0)))))))));
// TODO(roth): Once our InlinedVector<> implementation supports
// iteration, replace this by using ElementsAre() in the statement above.
auto it = bootstrap.node()->metadata.find("list");
ASSERT_TRUE(it != bootstrap.node()->metadata.end());
ASSERT_EQ(it->second.list_value.size(), 3);
EXPECT_EQ(it->second.list_value[0].type,
XdsBootstrap::MetadataValue::Type::DOUBLE);
EXPECT_EQ(it->second.list_value[0].double_value, 1);
EXPECT_EQ(it->second.list_value[1].type,
XdsBootstrap::MetadataValue::Type::DOUBLE);
EXPECT_EQ(it->second.list_value[1].double_value, 2);
EXPECT_EQ(it->second.list_value[2].type,
XdsBootstrap::MetadataValue::Type::DOUBLE);
EXPECT_EQ(it->second.list_value[2].double_value, 3);
EXPECT_EQ(bootstrap.node()->id, "foo");
EXPECT_EQ(bootstrap.node()->cluster, "bar");
EXPECT_EQ(bootstrap.node()->locality_region, "milky_way");
EXPECT_EQ(bootstrap.node()->locality_zone, "sol_system");
EXPECT_EQ(bootstrap.node()->locality_subzone, "earth");
ASSERT_EQ(bootstrap.node()->metadata.type(), Json::Type::OBJECT);
EXPECT_THAT(bootstrap.node()->metadata.object_value(),
::testing::ElementsAre(
::testing::Pair(
::testing::Eq("bar"),
::testing::AllOf(
::testing::Property(&Json::type, Json::Type::NUMBER),
::testing::Property(&Json::string_value, "2"))),
::testing::Pair(
::testing::Eq("foo"),
::testing::AllOf(
::testing::Property(&Json::type, Json::Type::NUMBER),
::testing::Property(&Json::string_value, "1")))));
}
TEST(XdsBootstrapTest, ValidWithoutChannelCredsAndNode) {
const char* json =
const char* json_str =
"{"
" \"xds_servers\": ["
" {"
@ -170,93 +117,89 @@ TEST(XdsBootstrapTest, ValidWithoutChannelCredsAndNode) {
" }"
" ]"
"}";
grpc_slice slice = grpc_slice_from_copied_string(json);
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
EXPECT_EQ(error, GRPC_ERROR_NONE);
EXPECT_STREQ(bootstrap.server().server_uri, "fake:///lb");
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
EXPECT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
EXPECT_EQ(bootstrap.server().server_uri, "fake:///lb");
EXPECT_EQ(bootstrap.server().channel_creds.size(), 0);
EXPECT_EQ(bootstrap.node(), nullptr);
}
TEST(XdsBootstrapTest, InvalidJson) {
grpc_slice slice = grpc_slice_from_copied_string("");
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("failed to parse bootstrap file JSON"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, MalformedJson) {
grpc_slice slice = grpc_slice_from_copied_string("\"foo\"");
TEST(XdsBootstrapTest, MissingXdsServers) {
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
Json json = Json::Parse("{}", &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("malformed JSON in bootstrap file"));
std::regex e(std::string("\"xds_servers\" field not present"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, MissingXdsServers) {
grpc_slice slice = grpc_slice_from_copied_string("{}");
TEST(XdsBootstrapTest, TopFieldsWrongTypes) {
const char* json_str =
"{"
" \"xds_servers\":1,"
" \"node\":1"
"}";
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("\"xds_servers\" field not present"));
std::regex e(
std::string("\"xds_servers\" field is not an array(.*)"
"\"node\" field is not an object"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, BadXdsServers) {
grpc_slice slice = grpc_slice_from_copied_string(
TEST(XdsBootstrapTest, XdsServerMissingServerUri) {
const char* json_str =
"{"
" \"xds_servers\":1,"
" \"xds_servers\":[{}]"
"}");
"}";
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(
std::string("\"xds_servers\" field is not an array(.*)"
"duplicate \"xds_servers\" field(.*)"
"errors parsing \"xds_servers\" array(.*)"
std::string("errors parsing \"xds_servers\" array(.*)"
"errors parsing index 0(.*)"
"\"server_uri\" field not present"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, BadXdsServerContents) {
grpc_slice slice = grpc_slice_from_copied_string(
TEST(XdsBootstrapTest, XdsServerUriAndCredsWrongTypes) {
const char* json_str =
"{"
" \"xds_servers\":["
" {"
" \"server_uri\":1,"
" \"server_uri\":\"foo\","
" \"channel_creds\":1,"
" \"channel_creds\":{}"
" \"channel_creds\":1"
" }"
" ]"
"}");
"}";
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(
std::string("errors parsing \"xds_servers\" array(.*)"
"errors parsing index 0(.*)"
"\"server_uri\" field is not a string(.*)"
"duplicate \"server_uri\" field(.*)"
"\"channel_creds\" field is not an array(.*)"
"\"channel_creds\" field is not an array(.*)"
"duplicate \"channel_creds\" field(.*)"));
"\"channel_creds\" field is not an array"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, BadChannelCredsContents) {
grpc_slice slice = grpc_slice_from_copied_string(
TEST(XdsBootstrapTest, ChannelCredsFieldsWrongTypes) {
const char* json_str =
"{"
" \"xds_servers\":["
" {"
@ -264,16 +207,16 @@ TEST(XdsBootstrapTest, BadChannelCredsContents) {
" \"channel_creds\":["
" {"
" \"type\":0,"
" \"type\":\"fake\","
" \"config\":1,"
" \"config\":{}"
" \"config\":1"
" }"
" ]"
" }"
" ]"
"}");
"}";
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(
@ -282,80 +225,61 @@ TEST(XdsBootstrapTest, BadChannelCredsContents) {
"errors parsing \"channel_creds\" array(.*)"
"errors parsing index 0(.*)"
"\"type\" field is not a string(.*)"
"duplicate \"type\" field(.*)"
"\"config\" field is not an object(.*)"
"duplicate \"config\" field"));
"\"config\" field is not an object"));
VerifyRegexMatch(error, e);
}
// under TSAN, ASAN and UBSAN, bazel RBE can suffer from a std::regex
// stackoverflow bug if the analyzed string is too long (> ~2000 characters). As
// this test is only single-thread and deterministic, it is safe to just disable
// it under TSAN and ASAN until
// https://github.com/GoogleCloudPlatform/layer-definitions/issues/591
// is resolved. The risk for UBSAN problem also doesn't seem to be very high.
#ifndef GRPC_ASAN
#ifndef GRPC_TSAN
#ifndef GRPC_UBSAN
TEST(XdsBootstrapTest, BadNode) {
grpc_slice slice = grpc_slice_from_copied_string(
TEST(XdsBootstrapTest, NodeFieldsWrongTypes) {
const char* json_str =
"{"
" \"node\":1,"
" \"node\":{"
" \"id\":0,"
" \"id\":\"foo\","
" \"cluster\":0,"
" \"cluster\":\"foo\","
" \"locality\":0,"
" \"metadata\":0"
" }"
"}";
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(
std::string("errors parsing \"node\" object(.*)"
"\"id\" field is not a string(.*)"
"\"cluster\" field is not a string(.*)"
"\"locality\" field is not an object(.*)"
"\"metadata\" field is not an object"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, LocalityFieldsWrongType) {
const char* json_str =
"{"
" \"node\":{"
" \"locality\":{"
" \"region\":0,"
" \"region\":\"foo\","
" \"zone\":0,"
" \"zone\":\"foo\","
" \"subzone\":0,"
" \"subzone\":\"foo\""
" },"
" \"metadata\":0,"
" \"metadata\":{"
" \"foo\":0,"
" \"foo\":\"whee\","
" \"foo\":\"whee2\""
" \"subzone\":0"
" }"
" }"
"}");
"}";
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(
std::string("\"node\" field is not an object(.*)"
"duplicate \"node\" field(.*)"
"errors parsing \"node\" object(.*)"
"\"id\" field is not a string(.*)"
"duplicate \"id\" field(.*)"
"\"cluster\" field is not a string(.*)"
"duplicate \"cluster\" field(.*)"
"\"locality\" field is not an object(.*)"
"duplicate \"locality\" field(.*)"
std::string("errors parsing \"node\" object(.*)"
"errors parsing \"locality\" object(.*)"
"\"region\" field is not a string(.*)"
"duplicate \"region\" field(.*)"
"\"zone\" field is not a string(.*)"
"duplicate \"zone\" field(.*)"
"\"subzone\" field is not a string(.*)"
"duplicate \"subzone\" field(.*)"
"\"metadata\" field is not an object(.*)"
"duplicate \"metadata\" field(.*)"
"errors parsing \"metadata\" object(.*)"
"duplicate metadata key \"foo\""));
"\"subzone\" field is not a string"));
VerifyRegexMatch(error, e);
}
#endif
#endif
#endif
} // namespace testing
} // namespace grpc_core

@ -16,16 +16,13 @@
*
*/
#include "test/core/end2end/end2end_tests.h"
#include <stdio.h>
#include <string.h>
#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <stdio.h>
#include <string.h>
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/string.h"
@ -37,6 +34,7 @@
#include "src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h"
#include "src/core/lib/security/security_connector/ssl_utils_config.h"
#include "test/core/end2end/data/ssl_test_data.h"
#include "test/core/end2end/end2end_tests.h"
#include "test/core/util/port.h"
#include "test/core/util/test_config.h"
@ -193,6 +191,7 @@ static int server_cred_reload_sync(void* /*config_user_data*/,
static grpc_channel_credentials* create_tls_channel_credentials(
fullstack_secure_fixture_data* ffd) {
grpc_tls_credentials_options* options = grpc_tls_credentials_options_create();
options->set_server_verification_option(GRPC_TLS_SERVER_VERIFICATION);
/* Set credential reload config. */
grpc_tls_credential_reload_config* reload_config =
grpc_tls_credential_reload_config_create(nullptr, client_cred_reload_sync,

@ -269,6 +269,8 @@ grpc_cc_test(
"//:gpr",
"//:grpc",
"//:grpc_secure",
"//:tsi",
"//:tsi_interface",
"//test/core/end2end:ssl_test_data",
"//test/core/util:grpc_test_util",
],

@ -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);

@ -16,16 +16,17 @@
*
*/
#include <stdlib.h>
#include <string.h>
#include "src/core/lib/security/security_connector/tls/tls_security_connector.h"
#include <gmock/gmock.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <gtest/gtest.h>
#include <stdlib.h>
#include <string.h>
#include "src/core/lib/security/security_connector/tls/tls_security_connector.h"
#include "src/core/tsi/transport_security.h"
#include "test/core/end2end/data/ssl_test_data.h"
#include "test/core/util/test_config.h"
@ -254,6 +255,35 @@ TEST_F(TlsSecurityConnectorTest, CreateChannelSecurityConnectorFailInit) {
EXPECT_EQ(connector, nullptr);
}
TEST_F(TlsSecurityConnectorTest, TlsCheckHostNameSuccess) {
const char* target_name = "foo.test.google.fr";
tsi_peer peer;
GPR_ASSERT(tsi_construct_peer(1, &peer) == TSI_OK);
GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, target_name,
&peer.properties[0]) == TSI_OK);
grpc_error* error = grpc_core::TlsCheckHostName(target_name, &peer);
tsi_peer_destruct(&peer);
EXPECT_EQ(error, GRPC_ERROR_NONE);
GRPC_ERROR_UNREF(error);
options_->Unref();
}
TEST_F(TlsSecurityConnectorTest, TlsCheckHostNameFail) {
const char* target_name = "foo.test.google.fr";
const char* another_name = "bar.test.google.fr";
tsi_peer peer;
GPR_ASSERT(tsi_construct_peer(1, &peer) == TSI_OK);
GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, another_name,
&peer.properties[0]) == TSI_OK);
grpc_error* error = grpc_core::TlsCheckHostName(target_name, &peer);
tsi_peer_destruct(&peer);
EXPECT_NE(error, GRPC_ERROR_NONE);
GRPC_ERROR_UNREF(error);
options_->Unref();
}
TEST_F(TlsSecurityConnectorTest, CreateServerSecurityConnectorSuccess) {
SetOptions(SUCCESS);
auto cred = std::unique_ptr<grpc_server_credentials>(

@ -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);

@ -19,6 +19,7 @@
#include "test/cpp/end2end/test_service_impl.h"
#include <grpc/support/log.h>
#include <grpcpp/alarm.h>
#include <grpcpp/security/credentials.h>
#include <grpcpp/server_context.h>
#include <gtest/gtest.h>
@ -120,7 +121,8 @@ void ServerTryCancel(ServerContext* context) {
void ServerTryCancelNonblocking(experimental::CallbackServerContext* context) {
EXPECT_FALSE(context->IsCancelled());
context->TryCancel();
gpr_log(GPR_INFO, "Server called TryCancel() to cancel the request");
gpr_log(GPR_INFO,
"Server called TryCancelNonblocking() to cancel the request");
}
} // namespace
@ -451,9 +453,9 @@ experimental::ServerUnaryReactor* CallbackTestServiceImpl::Echo(
gpr_time_from_micros(req_->param().server_sleep_us(),
GPR_TIMESPAN)),
[this](bool ok) { NonDelayed(ok); });
} else {
NonDelayed(true);
return;
}
NonDelayed(true);
}
void OnSendInitialMetadataDone(bool ok) override {
EXPECT_TRUE(ok);
@ -462,10 +464,9 @@ experimental::ServerUnaryReactor* CallbackTestServiceImpl::Echo(
void OnCancel() override {
EXPECT_TRUE(started_);
EXPECT_TRUE(ctx_->IsCancelled());
// do the actual finish in the main handler only but use this as a chance
// to cancel any alarms.
alarm_.Cancel();
on_cancel_invoked_ = true;
std::lock_guard<std::mutex> l(cancel_mu_);
cancel_cv_.notify_one();
}
void OnDone() override {
if (req_->has_param() && req_->param().echo_metadata_initially()) {
@ -476,6 +477,9 @@ experimental::ServerUnaryReactor* CallbackTestServiceImpl::Echo(
if (rpc_wait_thread_.joinable()) {
rpc_wait_thread_.join();
}
if (finish_when_cancelled_.joinable()) {
finish_when_cancelled_.join();
}
delete this;
}
@ -506,7 +510,7 @@ experimental::ServerUnaryReactor* CallbackTestServiceImpl::Echo(
EXPECT_FALSE(ctx_->IsCancelled());
ctx_->TryCancel();
gpr_log(GPR_INFO, "Server called TryCancel() to cancel the request");
LoopUntilCancelled(1000);
FinishWhenCancelledAsync();
return;
}
gpr_log(GPR_DEBUG, "Request message was %s", req_->message().c_str());
@ -520,7 +524,7 @@ experimental::ServerUnaryReactor* CallbackTestServiceImpl::Echo(
std::unique_lock<std::mutex> lock(service_->mu_);
service_->signal_client_ = true;
}
LoopUntilCancelled(req_->param().client_cancel_after_us());
FinishWhenCancelledAsync();
return;
} else if (req_->has_param() && req_->param().server_cancel_after_us()) {
alarm_.experimental().Set(
@ -578,20 +582,12 @@ experimental::ServerUnaryReactor* CallbackTestServiceImpl::Echo(
}
Finish(Status::OK);
}
void LoopUntilCancelled(int loop_delay_us) {
if (!ctx_->IsCancelled()) {
alarm_.experimental().Set(
gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
gpr_time_from_micros(loop_delay_us, GPR_TIMESPAN)),
[this, loop_delay_us](bool ok) {
if (!ok) {
EXPECT_TRUE(ctx_->IsCancelled());
}
LoopUntilCancelled(loop_delay_us);
});
} else {
void FinishWhenCancelledAsync() {
finish_when_cancelled_ = std::thread([this] {
std::unique_lock<std::mutex> l(cancel_mu_);
cancel_cv_.wait(l, [this] { return ctx_->IsCancelled(); });
Finish(Status::CANCELLED);
}
});
}
CallbackTestServiceImpl* const service_;
@ -599,11 +595,14 @@ experimental::ServerUnaryReactor* CallbackTestServiceImpl::Echo(
const EchoRequest* const req_;
EchoResponse* const resp_;
Alarm alarm_;
bool initial_metadata_sent_{false};
bool started_{false};
bool on_cancel_invoked_{false};
std::mutex cancel_mu_;
std::condition_variable cancel_cv_;
bool initial_metadata_sent_ = false;
bool started_ = false;
bool on_cancel_invoked_ = false;
std::thread async_cancel_check_;
std::thread rpc_wait_thread_;
std::thread finish_when_cancelled_;
};
return new Reactor(this, context, request, response);

@ -23,7 +23,6 @@
#include <mutex>
#include <grpc/grpc.h>
#include <grpcpp/alarm.h>
#include <grpcpp/server_context.h>
#include "src/proto/grpc/testing/echo.grpc.pb.h"

@ -162,7 +162,10 @@ class AsyncQpsServerTest final : public grpc::testing::Server {
std::lock_guard<std::mutex> lock((*ss)->mutex);
(*ss)->shutdown = true;
}
std::thread shutdown_thread(&AsyncQpsServerTest::ShutdownThreadFunc, this);
// TODO(vjpai): Remove the following deadline and allow full proper
// shutdown.
server_->Shutdown(std::chrono::system_clock::now() +
std::chrono::seconds(3));
for (auto cq = srv_cqs_.begin(); cq != srv_cqs_.end(); ++cq) {
(*cq)->Shutdown();
}
@ -175,7 +178,6 @@ class AsyncQpsServerTest final : public grpc::testing::Server {
while ((*cq)->Next(&got_tag, &ok))
;
}
shutdown_thread.join();
}
int GetPollCount() override {
@ -192,12 +194,6 @@ class AsyncQpsServerTest final : public grpc::testing::Server {
}
private:
void ShutdownThreadFunc() {
// TODO (vpai): Remove this deadline and allow Shutdown to finish properly
auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(3);
server_->Shutdown(deadline);
}
void ThreadFunc(int thread_idx) {
// Wait until work is available or we are shutting down
bool ok;

Loading…
Cancel
Save