Skip to content

Escape DID when building DID document to prevent JSON injection#33

Open
achamayou wants to merge 2 commits into
mainfrom
fix_did_document_injection
Open

Escape DID when building DID document to prevent JSON injection#33
achamayou wants to merge 2 commits into
mainfrom
fix_did_document_injection

Conversation

@achamayou

Copy link
Copy Markdown
Member

Summary

create_did_document spliced the attacker-influenced did into the output JSON using std::regex_replace. This is unsafe for two reasons:

  1. No JSON escaping. The did is placed inside JSON string values (id, verificationMethod[].id, controller, assertionMethod, keyAgreement) without escaping. A did containing ", \, or control characters could break out of the JSON string and corrupt or inject structure into the resulting document.
  2. Regex replacement metacharacters. std::regex_replace interprets $ in the replacement text ($&, $1, $`, $$), and the sequential _DID_ / _LEAF_JWK_ substitutions could interfere with one another.

Changes

  • Add a json_escape_string helper that escapes ", \, the standard short escapes, and control characters (\u00XX).
  • Rebuild the DID document with plain string concatenation, JSON-escaping the did everywhere it is embedded.
  • The leaf JWK is generated internally (base64url-encoded values + a fixed set of keys), so it is inserted verbatim as a JSON object.
  • Drop the now-unused <regex> include.

Testing

  • cmake --build build && ctest — all tests pass. The existing tests parse the resolved document with nlohmann::json, so malformed output would be caught.

Found during a security review of the codebase.

create_did_document spliced the attacker-influenced DID into the output
JSON using std::regex_replace. That had two problems:

- The DID was inserted into JSON string values without escaping, so a DID
  containing '"', '\\' or control characters could break out of the
  string and corrupt or inject structure into the document.
- std::regex_replace interprets '$' in the replacement ($&, $1, etc.),
  and the sequential _DID_/_LEAF_JWK_ substitutions could interfere with
  one another.

Build the document with plain concatenation and JSON-escape the DID. The
leaf JWK is generated internally (base64url values + fixed keys) and is
inserted verbatim. Drop the now-unused <regex> include.
@achamayou achamayou requested a review from a team as a code owner June 12, 2026 21:58
@achamayou achamayou requested a review from Copilot June 12, 2026 22:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens DID document generation against JSON injection by ensuring attacker-influenced did values are JSON-escaped before being embedded into the resolved DID document, and by removing the prior std::regex_replace-based templating approach.

Changes:

  • Added a json_escape_string helper to escape quotes, backslashes, standard short escapes, and control characters for safe JSON string embedding.
  • Reworked create_did_document to build JSON via string concatenation, inserting the escaped DID everywhere it is used.
  • Removed the unused <regex> include after eliminating std::regex_replace.
Comments suppressed due to low confidence (1)

didx509cpp.h:1583

  • The new JSON escaping behavior (json_escape_string + embedding did_json) doesn’t appear to be covered by tests. Existing tests parse the DID document JSON, but they only use normal DID inputs and won’t catch regressions where quotes/backslashes/control characters aren’t escaped correctly (the security scenario this PR addresses). Add a unit test that uses a DID containing characters like ", \\, and a control character (e.g. \n via a literal newline) and assert nlohmann::json::parse(resolve(...)) succeeds and that doc["id"] / doc["verificationMethod"][0]["id"] match the original DID value (plus #key-1 where applicable).

    inline std::pair<bool, bool> is_agreed_signature_key(const UqX509& cert)
    {
      const bool include_assertion_method =
        !cert.has_key_usage() || cert.has_key_usage_digital_signature();
      const bool include_key_agreement =

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants