)]}'
{
  "commit": "c948840051b34f2e93cec340f2ecd901a8c07979",
  "tree": "8f3cbc155845f3c9e43e968c1ac9711848dfcf25",
  "parents": [
    "8a7a9bb9e5dbd2c5ed5351ecdd8216effd3327f4"
  ],
  "author": {
    "name": "Core",
    "email": "core@artgins.com",
    "time": "Sun Jun 28 16:16:55 2026 +0200"
  },
  "committer": {
    "name": "Core",
    "email": "core@artgins.com",
    "time": "Sun Jun 28 16:21:56 2026 +0200"
  },
  "message": "jwt: pin the exact algorithm in both verify paths (RFC 8725 3.1)\n\nOn the pinned path of __verify_config_post() (Compact) -- both config-\u003ealg and\nconfig-\u003ekey-\u003ealg set and equal -- the alg-vs-alg checks compare config and key\nto each OTHER, but none of them compares the token\u0027s own alg (jwt-\u003ealg) against\nthe pinned alg. The jwt_alg_required_kty() backstop that follows is only\nfamily-granular (every RSn/PSn alg maps to JWK_KEY_TYPE_RSA, every ESn to EC).\nThe effect: a token presenting \"RS512\" verifies against a key the caller pinned\nas \"RS256\" -- same family, valid signature -- so a pinned verifier accepts an\nalgorithm it never pinned.\n\nThe JSON Serialization path has the same gap in try_candidate(): it gates the\ncandidate key by jwt_alg_required_kty(s-\u003ealg) (family-granular) but, for the\nexplicit-key and \"kid\"-matched branches, never checks the key\u0027s own declared\nalg against the signature alg. (The keyring-scan branch already skips on\n\"kalg !\u003d s-\u003ealg\"; the single-key and kid branches did not.)\n\nThis is not a forgery (the signature must still validate under the key), but it\nis a policy/conformance gap: RFC 8725 3.1 calls for using algorithm\nidentifiers, not just key types, in the verification decision.\n\n  - Compact: require jwt-\u003ealg to equal whichever alg the caller pinned\n    (config-\u003ealg, or config-\u003ekey-\u003ealg when only the key carries one), right\n    before the kty backstop.\n  - JSON: in try_candidate() -- the single choke point for all three key-\n    selection branches -- skip a candidate whose declared \"alg\" differs from\n    the signature alg, mirroring the scan branch\u0027s existing guard.\n\nBoth are purely additive: no legitimately-pinned token is newly rejected.\n\ntests/jwt_alg_downgrade.c (every crypto backend, both Compact and Flattened\nJSON serializations): an RS512 token verified against an RS256-pinned key is\nnow rejected on both paths; an RS256 token under an RS256 pin, and an RS512\ntoken under a legitimate RS512 pin, both still verify. Adds\ntests/keys/rsa_key_2048_no_alg.json (the existing 2048-bit RSA modulus with no\n\"alg\" hint, so the builder can sign any RSA alg on it). Full suite 40/40\nlocally (OpenSSL + Jansson).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Core \u003ccore@artgins.com\u003e\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "7bd3a6fd7f0ca352b1553ff4908d50ca78b9f660",
      "old_mode": 33188,
      "old_path": "CMakeLists.txt",
      "new_id": "fc7646abf0a4ea185ff4698c70c74dd249543cba",
      "new_mode": 33188,
      "new_path": "CMakeLists.txt"
    },
    {
      "type": "modify",
      "old_id": "c3c4e721266a8f989b15c522105673ac630e2cc1",
      "old_mode": 33188,
      "old_path": "libjwt/jwt-verify.c",
      "new_id": "6db7b50e70889b29352a8cba6a2ed19687ee738f",
      "new_mode": 33188,
      "new_path": "libjwt/jwt-verify.c"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "95d5891e1d0cfb8c2d2764b89c5d706ee5b1ef4e",
      "new_mode": 33188,
      "new_path": "tests/jwt_alg_downgrade.c"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "24f11987f0251faf2068db6e0dce096da4e50168",
      "new_mode": 33188,
      "new_path": "tests/keys/rsa_key_2048_no_alg.json"
    }
  ]
}
