diff --git a/CHANGELOG.md b/CHANGELOG.md index 468491091311..29e703bf6258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Upgraded QuickJS from 2024-01-13 to 2025-09-13 (#7849). - On a joiner's first attempt, the primary now requires the joiner's startup seqno to be at least as recent as the primary's latest committed snapshot on disk, preventing snapshot-less joiners from replaying the entire ledger (#7844). +- JSON parsing now can reject inputs whose object/array nesting depth exceeds a certain value, defaulting to 64 levels and overridable per call site via `ccf::parse_json_safe`'s `max_depth` parameter (#7896). ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bb473609b36..3c3e797bd550 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -559,6 +559,11 @@ if(BUILD_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/json_schema.cpp ) + add_unit_test( + parse_json_safe_test + ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/parse_json_safe.cpp + ) + add_unit_test( logger_test ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/logger.cpp diff --git a/include/ccf/ds/json.h b/include/ccf/ds/json.h index a7630b8bb9f6..4f0f2e27aca9 100644 --- a/include/ccf/ds/json.h +++ b/include/ccf/ds/json.h @@ -43,6 +43,55 @@ namespace ccf return fmt::format("At {}: {}", pointer(), what()); } }; + + inline constexpr size_t MAX_JSON_NESTING_DEPTH = 64; + + class JsonTooDeep : public ccf::JsonParseError + { + public: + explicit JsonTooDeep(size_t max_depth) : + ccf::JsonParseError(fmt::format( + "JSON object/array nesting exceeds maximum depth of {}", max_depth)) + {} + }; + + inline nlohmann::json::parser_callback_t make_depth_limit_callback( + size_t max_depth) + { + return [max_depth]( + int depth, + nlohmann::json::parse_event_t event, + nlohmann::json& /*parsed*/) { + using E = nlohmann::json::parse_event_t; + if ( + (event == E::object_start || event == E::array_start) && + static_cast(depth) >= max_depth) + { + throw JsonTooDeep{max_depth}; + } + return true; + }; + } + + // Depth-bounded alternative to nlohmann::json::parse, for inputs whose + // depth has not been validated upstream. max_depth defaults to + // MAX_JSON_NESTING_DEPTH but can be overridden at the call site for + // applications with stricter or more permissive requirements. + template + nlohmann::json parse_json_safe( + Bytes&& bytes, size_t max_depth = MAX_JSON_NESTING_DEPTH) + { + return nlohmann::json::parse( + std::forward(bytes), make_depth_limit_callback(max_depth)); + } + + template + nlohmann::json parse_json_safe( + Iter first, Iter last, size_t max_depth = MAX_JSON_NESTING_DEPTH) + { + return nlohmann::json::parse( + first, last, make_depth_limit_callback(max_depth)); + } } // NOLINTBEGIN(cert-dcl58-cpp) diff --git a/include/ccf/json_handler.h b/include/ccf/json_handler.h index ae97b7aa1f84..c202f5e9fe7d 100644 --- a/include/ccf/json_handler.h +++ b/include/ccf/json_handler.h @@ -19,7 +19,7 @@ namespace ccf * nlohmann::json params; * if () * { - * params = nlohmann::json::parse(ctx.rpc_ctx->get_request_body()); + * params = ccf::parse_json_safe(ctx.rpc_ctx->get_request_body()); * } * else * { diff --git a/include/ccf/kv/serialisers/json_serialiser.h b/include/ccf/kv/serialisers/json_serialiser.h index 3119bbf9b051..5f12e2528a82 100644 --- a/include/ccf/kv/serialisers/json_serialiser.h +++ b/include/ccf/kv/serialisers/json_serialiser.h @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "ccf/ds/json.h" #include "ccf/kv/serialisers/serialised_entry.h" #include @@ -29,7 +30,7 @@ namespace ccf::kv::serialisers static T from_serialised(const SerialisedEntry& rep) { - const auto j = nlohmann::json::parse(rep.begin(), rep.end()); + const auto j = ccf::parse_json_safe(rep.begin(), rep.end()); return j.get(); } }; diff --git a/samples/apps/basic/basic.cpp b/samples/apps/basic/basic.cpp index d5eedd8f5c64..0ff8c31a2508 100644 --- a/samples/apps/basic/basic.cpp +++ b/samples/apps/basic/basic.cpp @@ -126,7 +126,7 @@ namespace basicapp auto post = [](ccf::endpoints::EndpointContext& ctx) { const nlohmann::json body = - nlohmann::json::parse(ctx.rpc_ctx->get_request_body()); + ccf::parse_json_safe(ctx.rpc_ctx->get_request_body()); const auto records = body.get>(); diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 0f7ed3860f32..5b049e6ac622 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -1281,7 +1281,7 @@ namespace loggingapp ctx.template get_caller(); const nlohmann::json body_j = - nlohmann::json::parse(ctx.rpc_ctx->get_request_body()); + ccf::parse_json_safe(ctx.rpc_ctx->get_request_body()); const auto in = body_j.get(); if (in.msg.empty()) diff --git a/samples/apps/programmability/programmability.cpp b/samples/apps/programmability/programmability.cpp index b8d9199ea4db..0b3cde194134 100644 --- a/samples/apps/programmability/programmability.cpp +++ b/samples/apps/programmability/programmability.cpp @@ -351,7 +351,7 @@ namespace programmabilityapp auto post = [](ccf::endpoints::EndpointContext& ctx) { const nlohmann::json body = - nlohmann::json::parse(ctx.rpc_ctx->get_request_body()); + ccf::parse_json_safe(ctx.rpc_ctx->get_request_body()); const auto records = body.get>(); @@ -439,7 +439,7 @@ namespace programmabilityapp const auto [format, content, created_at] = get_action_content(ctx); const auto parsed_content = - nlohmann::json::parse(content.begin(), content.end()); + ccf::parse_json_safe(content.begin(), content.end()); const auto parsed_bundle = parsed_content.get(); // Make operation auditable @@ -627,7 +627,7 @@ namespace programmabilityapp const auto [format, content, created_at] = get_action_content(ctx); // - Parse content as JSON options const auto arg_content = - nlohmann::json::parse(content.begin(), content.end()); + ccf::parse_json_safe(content.begin(), content.end()); // - Merge, to overwrite current options with anything from body. Note // that nulls mean deletions, which results in resetting to a default diff --git a/src/ds/test/parse_json_safe.cpp b/src/ds/test/parse_json_safe.cpp new file mode 100644 index 000000000000..79222805faba --- /dev/null +++ b/src/ds/test/parse_json_safe.cpp @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +// +// Tests for ccf::parse_json_safe - the depth-bounded JSON parse chokepoint. +// parse_json_safe wraps nlohmann::json::parse with the library's +// parser_callback_t and aborts the parse before any DOM node is materialised +// once an object_start or array_start is reached at the configured maximum +// depth (default: ccf::MAX_JSON_NESTING_DEPTH, overridable per call site). + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "ccf/ds/json.h" + +#include +#include + +namespace +{ + // Builds {"a":{"a":...{"a":null}...}} of the requested nesting depth. + // Examples: nest_obj(1) -> {"a":null} + // nest_obj(3) -> {"a":{"a":{"a":null}}} + std::string nest_obj(size_t depth) + { + std::string s; + s.reserve(depth * 6 + 4); + for (size_t i = 0; i < depth; ++i) + { + s.append("{\"a\":"); + } + s.append("null"); + for (size_t i = 0; i < depth; ++i) + { + s.push_back('}'); + } + return s; + } + + // Builds [[...[null]...]] of the requested nesting depth. + // Examples: nest_arr(1) -> [null] + // nest_arr(3) -> [[[null]]] + std::string nest_arr(size_t depth) + { + std::string s; + s.reserve(depth * 2 + 4); + for (size_t i = 0; i < depth; ++i) + { + s.push_back('['); + } + s.append("null"); + for (size_t i = 0; i < depth; ++i) + { + s.push_back(']'); + } + return s; + } + + // Far above any plausible legitimate payload. + constexpr size_t kAttackDepth = 200'000; + constexpr size_t kAtLimit = ccf::MAX_JSON_NESTING_DEPTH; +} + +TEST_CASE("parse_json_safe rejects deeply nested objects far past the limit") +{ + // kAttackDepth (200 000) is well past MAX_JSON_NESTING_DEPTH and so must + // be rejected long before any DOM is built. + const auto body = nest_obj(kAttackDepth); + CHECK_THROWS_AS(ccf::parse_json_safe(body), ccf::JsonTooDeep); +} + +TEST_CASE("parse_json_safe rejects deeply nested arrays far past the limit") +{ + const auto body = nest_arr(kAttackDepth); + CHECK_THROWS_AS(ccf::parse_json_safe(body), ccf::JsonTooDeep); +} + +TEST_CASE("parse_json_safe rejects objects one level above the limit") +{ + // kAtLimit + 1 is the minimal rejection case: proves the boundary is + // strictly "<= MAX_JSON_NESTING_DEPTH accepted, > rejected". + const auto body = nest_obj(kAtLimit + 1); + CHECK_THROWS_AS(ccf::parse_json_safe(body), ccf::JsonTooDeep); +} + +TEST_CASE("parse_json_safe rejects arrays one level above the limit") +{ + const auto body = nest_arr(kAtLimit + 1); + CHECK_THROWS_AS(ccf::parse_json_safe(body), ccf::JsonTooDeep); +} + +TEST_CASE("parse_json_safe accepts objects exactly at the limit") +{ + // Exactly MAX_JSON_NESTING_DEPTH must round-trip cleanly: the bound + // is inclusive so the maximum legitimate payload is not collateral damage. + const auto body = nest_obj(kAtLimit); + nlohmann::json j; + CHECK_NOTHROW(j = ccf::parse_json_safe(body)); + CHECK(j.is_object()); +} + +TEST_CASE("parse_json_safe accepts arrays exactly at the limit") +{ + const auto body = nest_arr(kAtLimit); + nlohmann::json j; + CHECK_NOTHROW(j = ccf::parse_json_safe(body)); + CHECK(j.is_array()); +} + +TEST_CASE("parse_json_safe leaves shallow well-formed input unchanged") +{ + const auto body = std::string(R"({"k":[1,2,3],"v":{"x":true}})"); + nlohmann::json j; + CHECK_NOTHROW(j = ccf::parse_json_safe(body)); + CHECK(j["k"].size() == 3); + CHECK(j["v"]["x"] == true); +} + +TEST_CASE("parse_json_safe propagates ordinary syntax errors as parse_error") +{ + // The callback only adds a depth check; ordinary parse errors still + // surface as nlohmann::json::parse_error, NOT as JsonTooDeep. + const auto body = std::string("{not json"); + CHECK_THROWS_AS(ccf::parse_json_safe(body), nlohmann::json::parse_error); +} + +TEST_CASE("parse_json_safe iterator overload enforces the same limit") +{ + const auto body = nest_obj(kAttackDepth); + CHECK_THROWS_AS( + ccf::parse_json_safe(body.begin(), body.end()), ccf::JsonTooDeep); + + const auto shallow = std::string(R"({"a":1})"); + nlohmann::json j; + CHECK_NOTHROW(j = ccf::parse_json_safe(shallow.begin(), shallow.end())); + CHECK(j["a"] == 1); +} + +TEST_CASE("JsonTooDeep is catchable as ccf::JsonParseError") +{ + // Frontend top-level catch in src/node/rpc/frontend.h relies on this + // hierarchy to convert the failure into HTTP 400 InvalidInput. + const auto body = nest_obj(kAttackDepth); + CHECK_THROWS_AS(ccf::parse_json_safe(body), ccf::JsonParseError); +} + +TEST_CASE("parse_json_safe honours a caller-supplied max_depth override") +{ + // A caller-supplied limit must take precedence over the default, both for + // tightening (reject something the default would accept) and loosening + // (accept something the default would reject) the bound. + constexpr size_t kCustomLimit = 8; + + // Tighten: depth above kCustomLimit but well within the default must now + // be rejected when the caller passes kCustomLimit. + const auto tight_reject = nest_obj(kCustomLimit + 1); + CHECK_THROWS_AS( + ccf::parse_json_safe(tight_reject, kCustomLimit), ccf::JsonTooDeep); + + // Tighten boundary: exactly kCustomLimit must still be accepted when the + // caller passes kCustomLimit, mirroring the inclusive default boundary. + const auto tight_accept = nest_obj(kCustomLimit); + nlohmann::json j; + CHECK_NOTHROW(j = ccf::parse_json_safe(tight_accept, kCustomLimit)); + CHECK(j.is_object()); + + // Loosen: depth above the default but within an explicit larger limit must + // be accepted, proving the override is not just an upper bound on the + // default. + constexpr size_t kLooseLimit = ccf::MAX_JSON_NESTING_DEPTH + 16; + const auto loose_accept = nest_obj(ccf::MAX_JSON_NESTING_DEPTH + 8); + CHECK_NOTHROW(j = ccf::parse_json_safe(loose_accept, kLooseLimit)); + CHECK(j.is_object()); +} + +TEST_CASE("parse_json_safe iterator overload honours max_depth override") +{ + constexpr size_t kCustomLimit = 4; + const auto body = nest_arr(kCustomLimit + 1); + CHECK_THROWS_AS( + ccf::parse_json_safe(body.begin(), body.end(), kCustomLimit), + ccf::JsonTooDeep); + + const auto shallow = nest_arr(kCustomLimit); + nlohmann::json j; + CHECK_NOTHROW( + j = ccf::parse_json_safe(shallow.begin(), shallow.end(), kCustomLimit)); + CHECK(j.is_array()); +} diff --git a/src/endpoints/json_handler.cpp b/src/endpoints/json_handler.cpp index 9b7e9cd1ec61..bd3a54e55bd5 100644 --- a/src/endpoints/json_handler.cpp +++ b/src/endpoints/json_handler.cpp @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. #include "ccf/json_handler.h" +#include "ccf/ds/json.h" #include "ccf/http_accept.h" #include "ccf/http_consts.h" #include "ccf/odata_error.h" @@ -24,7 +25,7 @@ namespace ccf // Body of GET is ignored && ctx->get_request_verb() != HTTP_GET) { - params = nlohmann::json::parse(ctx->get_request_body()); + params = ccf::parse_json_safe(ctx->get_request_body()); } else { diff --git a/src/http/http_jwt.h b/src/http/http_jwt.h index 070ea2867630..8f138b690d0e 100644 --- a/src/http/http_jwt.h +++ b/src/http/http_jwt.h @@ -4,6 +4,7 @@ #include "ccf/crypto/base64.h" #include "ccf/crypto/verifier.h" +#include "ccf/ds/json.h" #include "ccf/http_consts.h" #include "http_parser.h" @@ -152,8 +153,15 @@ namespace http nlohmann::json payload; try { - header = nlohmann::json::parse(header_raw); - payload = nlohmann::json::parse(payload_raw); + header = ccf::parse_json_safe(header_raw); + payload = ccf::parse_json_safe(payload_raw); + } + catch (const ccf::JsonParseError& e) + { + error_reason = fmt::format( + "JWT header or payload exceeds permitted JSON nesting depth: {}", + e.what()); + return std::nullopt; } catch (const nlohmann::json::parse_error& e) { diff --git a/src/js/extensions/ccf/crypto.cpp b/src/js/extensions/ccf/crypto.cpp index 06ae11024353..c5f860405d6a 100644 --- a/src/js/extensions/ccf/crypto.cpp +++ b/src/js/extensions/ccf/crypto.cpp @@ -12,6 +12,7 @@ #include "ccf/crypto/rsa_key_pair.h" #include "ccf/crypto/sha256.h" #include "ccf/crypto/verifier.h" +#include "ccf/ds/json.h" #include "ccf/js/core/context.h" #include "ds/internal_logger.h" #include "js/checks.h" @@ -502,7 +503,7 @@ namespace ccf::js::extensions try { - T jwk = nlohmann::json::parse(jwk_str.value()); + T jwk = ccf::parse_json_safe(jwk_str.value()); if constexpr (std::is_same_v) { diff --git a/src/js/extensions/ccf/gov_effects.cpp b/src/js/extensions/ccf/gov_effects.cpp index 59ce1015d9eb..4233fae87282 100644 --- a/src/js/extensions/ccf/gov_effects.cpp +++ b/src/js/extensions/ccf/gov_effects.cpp @@ -6,6 +6,7 @@ #include "ccf/js/extensions/ccf/gov_effects.h" +#include "ccf/ds/json.h" #include "ccf/js/core/context.h" #include "ccf/version.h" #include "js/checks.h" @@ -161,8 +162,8 @@ namespace ccf::js::extensions try { auto metadata = - nlohmann::json::parse(*metadata_json).get(); - auto jwks = nlohmann::json::parse(*jwks_json).get(); + ccf::parse_json_safe(*metadata_json).get(); + auto jwks = ccf::parse_json_safe(*jwks_json).get(); auto success = ccf::set_jwt_public_signing_keys(tx, "", *issuer, metadata, jwks); if (!success) diff --git a/src/node/gov/handlers/acks.h b/src/node/gov/handlers/acks.h index 4614d8990357..2facd62b8b1b 100644 --- a/src/node/gov/handlers/acks.h +++ b/src/node/gov/handlers/acks.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/base_endpoint_registry.h" +#include "ccf/ds/json.h" #include "node/gov/api_version.h" #include "node/gov/handlers/helpers.h" #include "node/history.h" @@ -219,7 +220,7 @@ namespace ccf::gov::endpoints // Check signed digest matches expected digest in KV const auto expected_digest = ack->state_digest; - const auto signed_body = nlohmann::json::parse(cose_ident.content); + const auto signed_body = ccf::parse_json_safe(cose_ident.content); const auto actual_digest = signed_body["stateDigest"].template get(); if (expected_digest != actual_digest) diff --git a/src/node/gov/handlers/proposals.h b/src/node/gov/handlers/proposals.h index 23a203f86cad..69ac2aa35baa 100644 --- a/src/node/gov/handlers/proposals.h +++ b/src/node/gov/handlers/proposals.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/base_endpoint_registry.h" +#include "ccf/ds/json.h" #include "ccf/js/common_context.h" #include "ccf/js/extensions/ccf/gov_effects.h" #include "js/checks.h" @@ -969,7 +970,7 @@ namespace ccf::gov::endpoints } // Parse and validate incoming ballot - const auto params = nlohmann::json::parse(cose_ident.content); + const auto params = ccf::parse_json_safe(cose_ident.content); const auto ballot_it = params.find("ballot"); if (ballot_it == params.end() || !ballot_it.value().is_string()) { diff --git a/src/node/gov/handlers/recovery.h b/src/node/gov/handlers/recovery.h index eb633c3a15d6..3e4a658c72f6 100644 --- a/src/node/gov/handlers/recovery.h +++ b/src/node/gov/handlers/recovery.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/base_endpoint_registry.h" +#include "ccf/ds/json.h" #include "node/gov/api_version.h" #include "node/gov/handlers/helpers.h" #include "node/share_manager.h" @@ -111,7 +112,7 @@ namespace ccf::gov::endpoints const auto& cose_ident = ctx.template get_caller(); - auto params = nlohmann::json::parse(cose_ident.content); + auto params = ccf::parse_json_safe(cose_ident.content); if (cose_ident.member_id != member_id) { detail::set_gov_error( diff --git a/src/node/gov/handlers/service_state.h b/src/node/gov/handlers/service_state.h index 7e3c8ea0174f..85fabdb2a4c0 100644 --- a/src/node/gov/handlers/service_state.h +++ b/src/node/gov/handlers/service_state.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/base_endpoint_registry.h" +#include "ccf/ds/json.h" #include "node/gov/api_version.h" namespace ccf::gov::endpoints @@ -110,8 +111,7 @@ namespace ccf::gov::endpoints quote_info["rawQuote"] = node_info.quote_info.quote; { - const auto details = - nlohmann::json::parse(node_info.quote_info.quote); + const auto details = ccf::parse_json_safe(node_info.quote_info.quote); auto j_details = nlohmann::json::object(); j_details["measurement"] = details["measurement"]; j_details["reportData"] = details["report_data"]; @@ -362,7 +362,7 @@ namespace ccf::gov::endpoints } const auto& raw_value = raw_value_opt.value(); operation = - nlohmann::json::parse(raw_value.begin(), raw_value.end()); + ccf::parse_json_safe(raw_value.begin(), raw_value.end()); } else { diff --git a/src/node/jwt_key_auto_refresh.h b/src/node/jwt_key_auto_refresh.h index dec0224831ce..9738c36d70db 100644 --- a/src/node/jwt_key_auto_refresh.h +++ b/src/node/jwt_key_auto_refresh.h @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "ccf/ds/json.h" #include "ccf/service/tables/jwt.h" #include "http/http_builder.h" #include "http/http_rpc_context.h" @@ -153,7 +154,7 @@ namespace ccf JsonWebKeySet jwks; try { - jwks = nlohmann::json::parse(data).get(); + jwks = ccf::parse_json_safe(data).get(); } catch (const std::exception& e) { @@ -212,7 +213,7 @@ namespace ccf nlohmann::json metadata; try { - metadata = nlohmann::json::parse(data); + metadata = ccf::parse_json_safe(data); jwks_url_str = metadata.at("jwks_uri").get(); } catch (const std::exception& e) diff --git a/src/node/node_state.h b/src/node/node_state.h index 10d2038fba21..23a316e7e490 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -837,7 +837,7 @@ namespace ccf const auto raw_data = ccf::crypto::raw_from_b64( config.attestation.environment.snp_endorsements.value()); - const auto j = nlohmann::json::parse(raw_data); + const auto j = ccf::parse_json_safe(raw_data); const auto aci_endorsements = j.get(); @@ -1087,9 +1087,17 @@ namespace ccf try { - auto j = nlohmann::json::parse(data); + auto j = ccf::parse_json_safe(data); error_response = j.get(); } + catch (const ccf::JsonParseError& e) + { + LOG_FAIL_FMT( + "Join request returned {}, body exceeds permitted JSON nesting " + "depth: {}", + status, + e.what()); + } catch (const nlohmann::json::exception& e) { // Leave error_response == nullopt @@ -1172,7 +1180,7 @@ namespace ccf JoinNetworkNodeToNode::Out resp; try { - auto j = nlohmann::json::parse(data); + auto j = ccf::parse_json_safe(data); resp = j.get(); } catch (const std::exception& e) @@ -2629,7 +2637,7 @@ namespace ccf return false; } - const auto body = nlohmann::json::parse(raw_body); + const auto body = ccf::parse_json_safe(raw_body); if (!body.is_boolean()) { LOG_FAIL_FMT("Expected boolean body in create response"); diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 4db0b68cac53..b683369d12db 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -4,6 +4,7 @@ #include "ccf/node/quote.h" #include "ccf/crypto/cose.h" +#include "ccf/ds/json.h" #include "ccf/historical_queries_utils.h" #include "ccf/pal/attestation.h" #include "ccf/pal/attestation_sev_snp.h" @@ -179,7 +180,7 @@ namespace ccf { case QuoteFormat::insecure_virtual: { - auto j = nlohmann::json::parse(quote_info.quote); + auto j = ccf::parse_json_safe(quote_info.quote); auto it = j.find("host_data"); if (it != j.end()) @@ -328,8 +329,8 @@ namespace ccf pem_chain.emplace_back(ccf::crypto::cert_der_to_pem(c).str()); } - auto jwk = nlohmann::json::parse( - didx509::resolve_jwk(pem_chain, issuer_did, true)); + auto jwk = + ccf::parse_json_safe(didx509::resolve_jwk(pem_chain, issuer_did, true)); auto generic_jwk = jwk.get(); if (generic_jwk.kty != ccf::crypto::JsonWebKeyType::EC) diff --git a/src/node/quote_endorsements_client.h b/src/node/quote_endorsements_client.h index 491669908c33..f93614bd88ea 100644 --- a/src/node/quote_endorsements_client.h +++ b/src/node/quote_endorsements_client.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/crypto/verifier.h" +#include "ccf/ds/json.h" #include "ccf/http_consts.h" #include "ccf/pal/attestation.h" #include "ccf/pal/attestation_sev_snp_endorsements.h" @@ -87,7 +88,7 @@ namespace ccf } else if (endpoint.response_is_thim_json) { - auto j = nlohmann::json::parse(data); + auto j = ccf::parse_json_safe(data); auto vcekCert = j.at("vcekCert").get(); auto certificateChain = j.at("certificateChain").get(); endorsements_pem.insert( diff --git a/src/node/snapshot_serdes.h b/src/node/snapshot_serdes.h index e5f870374ab3..0ac29955eab4 100644 --- a/src/node/snapshot_serdes.h +++ b/src/node/snapshot_serdes.h @@ -5,6 +5,7 @@ #include "ccf/crypto/cose.h" #include "ccf/crypto/cose_verifier.h" #include "ccf/crypto/pem.h" +#include "ccf/ds/json.h" #include "ccf/historical_queries_adapter.h" #include "ccf/service/tables/nodes.h" #include "crypto/cose.h" @@ -117,7 +118,7 @@ namespace ccf const std::optional>& prev_service_identity) { auto j = - nlohmann::json::parse(segments.receipt.begin(), segments.receipt.end()); + ccf::parse_json_safe(segments.receipt.begin(), segments.receipt.end()); auto receipt_p = j.get(); auto receipt = std::dynamic_pointer_cast(receipt_p); if (receipt == nullptr) diff --git a/src/node/uvm_endorsements.cpp b/src/node/uvm_endorsements.cpp index a8199c78ad0e..72e001fe64a5 100644 --- a/src/node/uvm_endorsements.cpp +++ b/src/node/uvm_endorsements.cpp @@ -3,6 +3,7 @@ #include "node/uvm_endorsements.h" +#include "ccf/ds/json.h" #include "crypto/cbor.h" #include "crypto/cose_utils.h" #include "ds/internal_logger.h" @@ -266,7 +267,7 @@ namespace ccf const auto& did = phdr.iss; ccf::crypto::Pem pubk; - const auto jwk = nlohmann::json::parse( + const auto jwk = ccf::parse_json_safe( didx509::resolve_jwk(pem_chain, did, true /* ignore time */)); const auto generic_jwk = jwk.get(); switch (generic_jwk.kty) @@ -304,7 +305,7 @@ namespace ccf cose::value::CT_JSON)); } - auto payload = nlohmann::json::parse(raw_payload); + auto payload = ccf::parse_json_safe(raw_payload); sevsnpvm_launch_measurement = payload["x-ms-sevsnpvm-launchmeasurement"].get(); auto sevsnpvm_guest_svn_obj = payload["x-ms-sevsnpvm-guestsvn"]; diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index e89a7dd03b10..182ae49d5138 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -6,6 +6,7 @@ #include "ccf/crypto/ecdsa.h" #include "ccf/crypto/openssl/openssl_wrappers.h" #include "ccf/crypto/verifier.h" +#include "ccf/ds/json.h" #include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/sev_snp_cpuid.h" #include "ds/internal_logger.h" @@ -24,7 +25,7 @@ namespace ccf::pal PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data) { - auto j = nlohmann::json::parse(quote_info.quote); + auto j = ccf::parse_json_safe(quote_info.quote); const auto s_measurement = j["measurement"].get(); measurement.data =