openbadgeslib 4.6.0__tar.gz → 4.6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/CONTRIBUTING.md +1 -1
  2. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/Changelog.txt +264 -0
  3. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/PKG-INFO +9 -8
  4. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/README.md +2 -2
  5. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/_python_support.py +7 -6
  6. openbadgeslib-4.6.2/openbadgeslib/ob3/_context_validation.py +881 -0
  7. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/credential.py +20 -3
  8. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/did.py +33 -1
  9. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/eudi.py +186 -10
  10. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/ldp.py +317 -25
  11. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/status.py +8 -1
  12. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/verifier.py +4 -1
  13. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/handler.py +7 -7
  14. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/metadata.py +4 -4
  15. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/offer.py +3 -3
  16. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/util.py +1 -1
  17. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/verify.py +3 -1
  18. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib.egg-info/PKG-INFO +9 -8
  19. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib.egg-info/SOURCES.txt +2 -0
  20. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib.egg-info/requires.txt +6 -5
  21. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/pyproject.toml +83 -17
  22. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_docs.py +25 -3
  23. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_credential.py +16 -0
  24. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_did.py +32 -0
  25. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_eudi_sd_jwt.py +316 -0
  26. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_eudi_type_metadata.py +13 -0
  27. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_eudi_x5c.py +82 -2
  28. openbadgeslib-4.6.2/tests/test_ob3_ldp_context_validation.py +1729 -0
  29. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_ldp_ecdsa_sd.py +84 -10
  30. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_python_support.py +1 -1
  31. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/GOVERNANCE.md +0 -0
  32. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/LICENSE.txt +0 -0
  33. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/MANIFEST.in +0 -0
  34. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/SECURITY.md +0 -0
  35. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/docs/README.md +0 -0
  36. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/__init__.py +0 -0
  37. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/_jsonmodel.py +0 -0
  38. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/_jws/__init__.py +0 -0
  39. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/_jws/exceptions.py +0 -0
  40. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/_jws/utils.py +0 -0
  41. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/_ob1_api.py +0 -0
  42. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/badge_model.py +0 -0
  43. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/baking.py +0 -0
  44. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/cli_common.py +0 -0
  45. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/config.ini.example +0 -0
  46. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/confparser.py +0 -0
  47. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/errors.py +0 -0
  48. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/issue.py +0 -0
  49. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/keys.py +0 -0
  50. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/logs.py +0 -0
  51. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/mail.py +0 -0
  52. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob1/__init__.py +0 -0
  53. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob1/badge.py +0 -0
  54. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob1/signer.py +0 -0
  55. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob1/verifier.py +0 -0
  56. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob2/__init__.py +0 -0
  57. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob2/models.py +0 -0
  58. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob2/signer.py +0 -0
  59. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob2/verifier.py +0 -0
  60. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/__init__.py +0 -0
  61. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/clr.py +0 -0
  62. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/__init__.py +0 -0
  63. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/clr_context_2.0.1.json +0 -0
  64. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
  65. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
  66. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
  67. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
  68. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/ob-v3p0-extensions.json +0 -0
  69. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
  70. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
  71. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/publish.py +0 -0
  72. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/signer.py +0 -0
  73. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/status_list.py +0 -0
  74. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/ob3/status_registry.py +0 -0
  75. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/__init__.py +0 -0
  76. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/codes.py +0 -0
  77. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/errors.py +0 -0
  78. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/formats.py +0 -0
  79. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/memory_store.py +0 -0
  80. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/nonce.py +0 -0
  81. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/reconcile.py +0 -0
  82. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/sqlite_store.py +0 -0
  83. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/oid4vci/store.py +0 -0
  84. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/openbadges_cli.py +0 -0
  85. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/openbadges_init.py +0 -0
  86. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/openbadges_keygenerator.py +0 -0
  87. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/openbadges_publish.py +0 -0
  88. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/openbadges_signer.py +0 -0
  89. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/openbadges_status.py +0 -0
  90. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/openbadges_verifier.py +0 -0
  91. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib/py.typed +0 -0
  92. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib.egg-info/dependency_links.txt +0 -0
  93. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib.egg-info/entry_points.txt +0 -0
  94. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/openbadgeslib.egg-info/top_level.txt +0 -0
  95. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/setup.cfg +0 -0
  96. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/config1.ini +0 -0
  97. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/conformance/Dockerfile.ob3 +0 -0
  98. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/conformance/README.md +0 -0
  99. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/conformance/conftest.py +0 -0
  100. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/conformance/docker-compose.yml +0 -0
  101. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/conformance/relax-danubetech-checksums.awk +0 -0
  102. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/conformance/test_official_validators.py +0 -0
  103. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/conftest.py +0 -0
  104. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/ob_schemas/README.md +0 -0
  105. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/ob_schemas/ob_v3p0_achievementcredential_schema.json +0 -0
  106. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json +0 -0
  107. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/ob_schemas/refresh.sh +0 -0
  108. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/vc_di_eddsa/README.md +0 -0
  109. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
  110. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
  111. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
  112. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
  113. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
  114. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/images/sample1.png +0 -0
  115. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/images/sample1.svg +0 -0
  116. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/images/userimage01.svg +0 -0
  117. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/ldp_helpers.py +0 -0
  118. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/logo Python Espan/314/203a.svg" +0 -0
  119. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/logo Python Espa/303/261a.svg" +0 -0
  120. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_b64url.py +0 -0
  121. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_baking.py +0 -0
  122. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_cli_json.py +0 -0
  123. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_cli_smoke.py +0 -0
  124. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_confparser.py +0 -0
  125. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_eddsa.py +0 -0
  126. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_errors.py +0 -0
  127. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_examples.py +0 -0
  128. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_issue.py +0 -0
  129. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_issue_credential.py +0 -0
  130. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_jsonmodel.py +0 -0
  131. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_jws.py +0 -0
  132. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_key_operation.py +0 -0
  133. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_keys.py +0 -0
  134. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_mail.py +0 -0
  135. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob1_badge_io.py +0 -0
  136. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob1_deprecation.py +0 -0
  137. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob1_signer.py +0 -0
  138. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob1_verifier.py +0 -0
  139. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob2_cli.py +0 -0
  140. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob2_error_branches.py +0 -0
  141. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob2_models.py +0 -0
  142. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob2_signer.py +0 -0
  143. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob2_verifier.py +0 -0
  144. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_clr.py +0 -0
  145. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_conformance_schema.py +0 -0
  146. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_contexts.py +0 -0
  147. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_did_doc.py +0 -0
  148. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_endorsement.py +0 -0
  149. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_eudi_oid4vci.py +0 -0
  150. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_ldp.py +0 -0
  151. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_ldp_cli.py +0 -0
  152. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_ldp_errors.py +0 -0
  153. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_ldp_sign.py +0 -0
  154. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_publish_cli.py +0 -0
  155. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_signer.py +0 -0
  156. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_signer_cli_ldp.py +0 -0
  157. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_status.py +0 -0
  158. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_status_list.py +0 -0
  159. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_status_registry.py +0 -0
  160. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_ob3_verifier.py +0 -0
  161. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_oid4vci_config.py +0 -0
  162. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_oid4vci_protocol.py +0 -0
  163. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_oid4vci_reservations.py +0 -0
  164. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_oid4vci_store.py +0 -0
  165. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_openbadges_cli.py +0 -0
  166. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_public_api.py +0 -0
  167. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_sign_ecc.pem +0 -0
  168. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_sign_rsa.pem +0 -0
  169. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_signer_batch.py +0 -0
  170. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_util.py +0 -0
  171. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_verifier_cli_coverage.py +0 -0
  172. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_verify.py +0 -0
  173. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_verify_ecc.pem +0 -0
  174. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/test_verify_rsa.pem +0 -0
  175. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/withoutxmlheader.svg +0 -0
  176. {openbadgeslib-4.6.0 → openbadgeslib-4.6.2}/tests/withxmlheader.svg +0 -0
@@ -85,7 +85,7 @@ policy is deliberately simple and predictable:
85
85
  - A new CPython minor is **added** to the CI matrix and the classifiers once it
86
86
  has a stable (non-pre) release, so downstreams can adopt it early.
87
87
  - The current matrix is **3.10–3.15**, and `requires-python = ">=3.10"`.
88
- Python **3.10 is deprecated** since 4.6.0 (EOL 2026-10-31) and is dropped in
88
+ Python **3.10 is deprecated** since 4.6.0 (EOL 2026-10-01) and is dropped in
89
89
  5.0, which will require Python 3.11+
90
90
  ([#242](https://github.com/luisgf/openbadgeslib/issues/242)).
91
91
 
@@ -4,6 +4,270 @@ OpenBadgesLib - Changelog
4
4
  Newest first. Dates are ISO 8601 (YYYY-MM-DD).
5
5
 
6
6
 
7
+ * v4.6.2 - 2026-10-16
8
+
9
+ - security(deps): the openvc-core floor of `[eudi]`, `[ldp-sd]` and
10
+ `[oid4vci]` rises to 1.35.0. Two behaviours below it reach this
11
+ library's public API. An SD-JWT VC whose issuer JWT carries `aud`
12
+ verified for any relying party, so a badge its issuer restricted to one
13
+ verifier was honoured by all (RFC 7519 §4.1.3); 1.35.0 enforces it, and
14
+ `verify_badge_sd_jwt(audience=…)` now also gates it. And
15
+ `status_list_token_resolver(leeway_s=…)` took any number, so an infinite
16
+ leeway made an expired status-list token verify as current; 1.35.0
17
+ accepts whole seconds from 0 to 86400 only. Neither is an advisory in
18
+ openvc-core. The run-time guard of the ecdsa-sd-2023 verify path stays at
19
+ 1.33.2, the security minimum of that path. `[ldp]` and `[ldp-sd]` also cap
20
+ pyld below 4: pyld 4.0 (unreleased, breaking) drops statements from the
21
+ signed dataset that 3.x keeps, an IRI with two `#` or a literal with an
22
+ invalid `@language`, which context validation does not refuse, so the
23
+ credential would still show what the proof no longer covers.
24
+
25
+ - security(eudi): `verify_badge_sd_jwt` enforces the badge's own
26
+ `validFrom` and `validUntil` on both trust paths (pinned key and X.509
27
+ trust anchors), and `issue_badge_sd_jwt` writes the credential's
28
+ `validUntil` as the issuer JWT's `exp`, and a `validFrom` still ahead as
29
+ `nbf`. A badge is a flat IETF SD-JWT VC, and openvc-core reads
30
+ `validFrom` / `validUntil` as a validity window only for a W3C VCDM
31
+ payload, so a badge past its `validUntil` verified as unexpired unless
32
+ `expires_in_s` happened to be passed; badges issued by other
33
+ implementations with only a `validUntil` were affected the same way. The
34
+ earlier of `validUntil` and `expires_in_s` wins, a window value that is
35
+ not an ISO 8601 timestamp with an offset (as `datetime.fromisoformat`
36
+ reads it, which is stricter before Python 3.11) fails closed, and the
37
+ leeway is the 60 seconds the other OB 3.0 verifiers use.
38
+ `issue_badge_sd_jwt` now refuses, with `EudiError`, to make `name`,
39
+ `achievement`, `validFrom` or `validUntil` selectively disclosable: a
40
+ holder could withhold the disclosure and present an expired badge as one
41
+ that never expires, and the stripped presentation still verified. Only
42
+ the recipient (`credentialSubject`) is meant to be withheld. A bare string
43
+ for `disclosable=` is refused too (it was read one character at a time,
44
+ which left the recipient in the clear), and `expires_in_s` must be a
45
+ number within about 317 years of now. Nothing can detect a withheld
46
+ disclosure on a badge that already exists, and an existing badge has no
47
+ `exp`/`nbf` for other verifiers: re-issue badges issued with a
48
+ `disclosable=` that named one of those claims, and any badge that must
49
+ stop verifying at its `validUntil` everywhere. The Type Metadata document
50
+ is unchanged, byte for byte (a change would break the `vct#integrity` pin
51
+ of every badge already issued), and a test now pins its hash.
52
+ With `x5c_trust_anchors`, `verify_badge_sd_jwt` no longer accepts a plain
53
+ JWT (a token with no `~`): openvc-core's `verify_credential` takes one
54
+ and then ignored `expected_vct` and `require_key_binding` and returned
55
+ another type, so a JWT signed by an anchored issuer passed as a badge;
56
+ the pinned-key path always refused it. `status_list_token_resolver`
57
+ validates `leeway_s` when it is built, so a bad value fails there rather
58
+ than on the first status check of a later badge. A hostile issuer JWT
59
+ header nested deeper than the JSON parser recurses now fails as
60
+ `EudiError` instead of escaping `verify_badge_sd_jwt(x5c_trust_anchors=…)`
61
+ as a bare `RecursionError`, and a window value too long to be a timestamp
62
+ is not echoed back in the error.
63
+
64
+ - fix(did): a `did:web` document nested deeper than the JSON parser
65
+ recurses, or deeper than 64 levels (a DID document is a handful), is
66
+ reported as the usual `OB3VerificationError` instead of escaping
67
+ `resolve_did`, `fetch_did_web_document` and the verifiers that resolve an
68
+ issuer's DID as a bare `RecursionError`. CPython 3.14 and later parse
69
+ nesting far past the recursion limit, so the parser alone no longer bounds
70
+ what the later `repr()` and `json.dumps` of the document survive.
71
+
72
+ * v4.6.1 - 2026-10-14
73
+
74
+ - security(ldp): `OB3LdpVerifier` reads only JSON its Data Integrity proof
75
+ actually covers (GHSA-rp5q-966c-5592; affects 3.2.0–4.6.0, the native
76
+ eddsa-rdfc-2022 path). The proof signs the RDF dataset the credential
77
+ expands to, while the returned credential and every policy check
78
+ (validity window, `credentialStatus`, recipient, issuer binding,
79
+ `proof.expires`) read the JSON, and nothing tied the two together. Many
80
+ JSON documents expand to the same dataset, so an expired or revoked
81
+ badge, or one showing forged content, verified with its genuine
82
+ signature, including on the DID-resolved, issuer-bound path. `verify`
83
+ now applies VC Data Integrity §2.4.1 context validation before it builds
84
+ the credential or applies any policy, for both cryptosuites and ahead of
85
+ the ecdsa-sd-2023 delegation: `@context` starts with the VC 2.0 URL and
86
+ names only bundled contexts, each context document once and at most
87
+ eight entries in all, so a padded `@context` cannot buy seconds of
88
+ JSON-LD processing before any key is resolved, and no inline context
89
+ object, not even a bare `@vocab` (OB 3.0 credentials name only
90
+ published contexts; a holder-chosen vocabulary would only add spellings
91
+ of signed members to refuse). Those bounds hold for every `@context` the
92
+ JSON-LD processor applies and are checked before it runs: below the top
93
+ level only an embedded endorsement credential may carry one, held to
94
+ the same rules, and any other is refused, even inside an `@json`
95
+ literal such as `_sd`; and since each embedded credential is validated
96
+ again on its own, at most 64 may be embedded, 8 deep, with at most
97
+ 256 KiB of their JSON in all (a nested one counted once per credential
98
+ holding it). The document and its proof configuration must each be the
99
+ compacted form of their own expansion, with every member (and `type`
100
+ value) a bundled context defines spelled by its term; and nothing may
101
+ be dropped on the way to RDF ("safe mode"). That
102
+ refuses a member re-keyed to its IRI or aliased by an inline context, a
103
+ value its term cannot carry (an untyped `validUntil`/`validFrom` or
104
+ proof `expires`, or a literal `credentialStatus`: compaction itself
105
+ keeps such a value under the full IRI, out of sight of the policy,
106
+ which reads the term), `@included`, `@reverse`, `@index`, `@direction`, a
107
+ top-level `@graph`, a second description of the credential's own node,
108
+ an array nested inside an array (expansion flattens `[[S]]` into the
109
+ same signed dataset as `S`, while the status checker reads only objects,
110
+ so a wrapped `credentialStatus` skipped the revocation check), and any
111
+ term, type or node id that is undefined or not an absolute IRI. Every
112
+ `credentialStatus` entry must also be a JSON object: a bare status
113
+ reference, which no checker can read, is refused rather than treated as
114
+ no status. Embedded endorsement credentials are validated against their
115
+ own `@context`. `OB3LdpSigner.sign` checks what it produced and raises
116
+ `ErrorSigningFile` rather than emit a document its proof does not fully
117
+ cover (e.g. a recipient id without a scheme), or one whose
118
+ `sign_to_json` text exceeds the 256 KiB verifiers accept. Now refused
119
+ because part of them was never signed: credentials with an undefined
120
+ term or type, or a relative or malformed id (pyld >= 3.1 signed an
121
+ undefined type or a relative id as `http://example.org/base/...`),
122
+ including the shape openbadgeslib 3.6.0 to 3.14.0 issued, a
123
+ `credentialSchema` typed `1EdTechJsonSchemaValidator2019` without the
124
+ extensions context (#239): re-issue such badges. The size limit is still
125
+ applied once, to the document as received. The low-level
126
+ `verify_data_integrity_proof` / `add_data_integrity_proof` are unchanged
127
+ for eddsa-rdfc-2022 (CLR and the W3C vectors use them): they verify or
128
+ sign the RDF dataset only, so do not read validity, status, subject or
129
+ issuer from the JSON after `verify_data_integrity_proof` alone; verify
130
+ OB 3.0 credentials with `OB3LdpVerifier`.
131
+
132
+ - security(ldp): `OB3LdpVerifier` refuses a member the policy reads
133
+ spelled by its full IRI even where no bundled context defines a term
134
+ for it. OB 3.0 was first published on VCDM 1.1, so issuers signed
135
+ eddsa-rdfc-2022 badges carrying `issuanceDate`/`expirationDate` (and
136
+ perhaps a `StatusList2021Entry`); those are refused as issued, since the
137
+ VC 1.1 context is not bundled, but a holder could re-compact one onto
138
+ `[VC 2.0, OB 3.0]` with the dataset and the signature unchanged:
139
+ compaction then spelled those members by full IRI, and the model saw no
140
+ expiry, no not-before and a status entry type it did not know. Every IRI
141
+ the policy reads (the validity window of both VC data models,
142
+ `proof.expires`, `credentialStatus`, `credentialSchema`,
143
+ `credentialSubject`, `issuer`, and the Bitstring and StatusList2021
144
+ entry types and members) must now be spelled by the one key the policy
145
+ reads it under, from a fixed table rather than from the bundled
146
+ contexts, so an alias a future context gave one is refused too. VC 1.1
147
+ is still not bundled: such badges stay unverifiable; re-issue them on
148
+ VC 2.0.
149
+
150
+ - security(ldp): `OB3LdpVerifier` refuses a `credentialStatus` entry that
151
+ is also described outside `credentialStatus`. RDF lets a node referenced
152
+ from two properties be described under either, so when the issuer also
153
+ named its status entry from another property (`relatedResource`, a
154
+ nested node's or an embedded endorsement's own `credentialStatus`), a
155
+ holder could move a signed member such as `statusSize: 2` there with the
156
+ dataset and the signature unchanged, and the status check read the wrong
157
+ bit and reported a revoked badge as valid. A node directly under the
158
+ top-level `credentialStatus` whose id is described anywhere else in the
159
+ expanded document is now refused, unless that description is an exact
160
+ copy of the entry (as when an embedded endorsement repeats the outer
161
+ credential's `credentialStatus` verbatim). An issuer-signed credential
162
+ that describes its status entry under a second property with more than
163
+ its id is refused too: re-issue it naming the entry by id only.
164
+
165
+ - security(ldp): an ecdsa-sd-2023 presentation passes `OB3LdpVerifier`
166
+ only if every `validUntil` and `credentialStatus` statement it discloses
167
+ is mandatory. The #328 check saw only that the two keys were there, so
168
+ with status left selective a holder could disclose one status entry of
169
+ two (the clear one), or an entry without its `statusSize`, and pass the
170
+ status check. A verifier cannot count what a holder withheld, only rely
171
+ on the mandatory statements the issuer's base signature covers as a
172
+ whole, so issuers must list `/validUntil` and `/credentialStatus` as
173
+ mandatory pointers; presentations of credentials that left them
174
+ selective are now refused even when fully disclosed. The limit no
175
+ verifier can lift: what an issuer leaves selective stays hideable, so a
176
+ mandatory `/credentialStatus/0` alone leaves `/credentialStatus/1`
177
+ hideable; mark `/credentialStatus` whole. The check indexes the
178
+ disclosed statements with openvc-core's own ecdsa-sd-2023 helpers and
179
+ fails closed if they are missing. `verify_data_integrity_proof` still
180
+ checks the proof alone.
181
+ - `validFrom` is held to the same rule as `validUntil` and
182
+ `credentialStatus`: a presentation that omits it, or whose issuer
183
+ left `/validFrom` selective, is refused. Otherwise the holder of a
184
+ badge that is not valid yet could withhold its `validFrom`, which the
185
+ model then took as "now", and present it as valid. Issuers list
186
+ `/validFrom`, `/validUntil` and `/credentialStatus` as mandatory
187
+ pointers.
188
+
189
+ - fix(ob3): a `credentialStatus` entry whose `statusPurpose` or `type`,
190
+ or a credential whose `type`, is not a string (a holder can respell a
191
+ signed `"revocation"` as `["revocation"]` in a Data Integrity credential
192
+ without changing what is signed; an issuer can sign a language-tagged
193
+ value) fails with `OB3VerificationError` instead of a bare `TypeError`,
194
+ and before the status list is fetched.
195
+
196
+ - security(deps): raise the `[ldp]` pyld floor to `>=3.3.0`, the release
197
+ the suite is tested against, and declare the same floor in `[ldp-sd]`
198
+ (openvc-core's own `[data-integrity]` extra admits pyld 2.x, and
199
+ `OB3LdpVerifier` validates with pyld before it delegates). Context
200
+ validation compares the JSON with the compacted form pyld derives, and
201
+ pyld 2.x compacts some terms differently (an `Address.geo` comes back as
202
+ `GeoCoordinates`), so genuine credentials such as 1EdTech's "Complete
203
+ OpenBadgeCredential" example would be refused. pyld before 3.3 also
204
+ canonicalizes an integral float (`3.0`) as `"3.0E0"`, where 3.3 and
205
+ jsonld.js give `"3"`, so a Data Integrity proof made on one did not
206
+ verify on the other.
207
+
208
+ - security(deps): raise the openvc-core floors to `>=1.33.2` in the
209
+ `[eudi]`, `[ldp-sd]` and `[oid4vci]` extras, the release the suite is
210
+ tested against. openvc-core 1.30.2 closes the same JSON-vs-dataset gap
211
+ in the ecdsa-sd-2023 suite `[ldp-sd]` delegates to
212
+ (GHSA-vrxg-57v4-85mc), and 1.33.2 its follow-ups (GHSA-h9rq-wxcw-4rjc:
213
+ nested arrays, re-spelled policy terms, a derived proof that merges
214
+ blank nodes). The ecdsa-sd-2023 verify (`OB3LdpVerifier`
215
+ and `verify_data_integrity_proof` alike) now also refuses, at run time,
216
+ an installed openvc-core older than 1.33.2 or with no readable version
217
+ (`OB3VerificationError`), so a stale environment fails closed. Behaviour
218
+ the new floor brings: `verify_badge_sd_jwt` on a presentation carrying a
219
+ Key Binding JWT, called with only `audience` or only `nonce` (or one of
220
+ them empty), raises `EudiError` instead of reporting `key_bound=True`
221
+ for a KB-JWT never bound to this verifier (openvc-core 1.30.0); and an
222
+ ecdsa-sd-2023 document with an undefined term or an inline `@context`
223
+ that defines terms is rejected. CI's `eudi` job now runs the `[eudi]`
224
+ and `[ldp-sd]` suites at that exact floor and fails if the extras'
225
+ floors disagree.
226
+
227
+ - security(deps): raise the PyJWT[crypto] floor to `>=2.15.1`. The OB3
228
+ JWT-VC verifier hands an untrusted token to
229
+ `jwt.get_unverified_header()`, to an unverified `jwt.decode()` peek that
230
+ reads the issuer before its key is resolved, and to the
231
+ signature-verifying `jwt.decode()`, so these PyJWT advisories are on a
232
+ reachable path: a deeply nested header (2.14.0, GHSA-8wjv-2p76-3863,
233
+ which affects the old 2.13 floor) or payload (2.15.0,
234
+ GHSA-42vr-xj54-vc7v, through the unverified peek) escaped as a bare
235
+ `RecursionError` instead of an `OB3VerificationError`; and characters
236
+ outside the base64url alphabet were accepted in a segment, so one
237
+ signature verified under many spellings of a token (2.14.0,
238
+ GHSA-hxm8-2xgr-2p9m; its revocation bypass needs a consumer that indexes
239
+ revocation by token text, which openbadgeslib does not do). 2.15.1
240
+ rather than 2.15.0 because 2.14.0 and 2.15.0 also refused canonical `=`
241
+ padding (PyJWT #1209). The HMAC key-confusion, options-dict and
242
+ `PyJWKClient` advisories are not reachable: no HS* algorithm is ever
243
+ allowed, every call passes a fresh options dict, and `PyJWKClient` is
244
+ not used. `environment.yml`, the README and the Installation page
245
+ follow, and a docs test now pins their PyJWT floor to `pyproject.toml`.
246
+
247
+ - fix: the Python 3.10 deprecation gives the real CPython end-of-life
248
+ date, 2026-10-01 (PEP 619); v4.6.0 said 2026-10-31. The
249
+ `DeprecationWarning` now reads "(CPython end of life: 2026-10-01)", and
250
+ the README, Installation, Upgrading and contributing docs follow. Its
251
+ prefix is unchanged, so the documented
252
+ `ignore:Python 3.10 is deprecated in openbadgeslib:DeprecationWarning`
253
+ filter still matches, and 3.10 stays supported through 4.x.
254
+
255
+ - fix(ob3): integral `creditsAvailable` / `creditsEarned` values are
256
+ emitted as JSON integers (`3`, not `3.0`). Both are `xsd:float` terms,
257
+ and JSON-LD gives an integral number the integer lexical form (`"3"`),
258
+ which jsonld.js and pyld 3.3 sign; pyld before 3.3 signed the float
259
+ `3.0` as `"3.0E0"`, so a Data Integrity badge issued with integral float
260
+ credits verified only on the pyld version it was signed with. New badges
261
+ now canonicalize the same everywhere. Data Integrity badges already
262
+ issued with integral float credits on pyld <= 3.1 do not verify on pyld
263
+ 3.3 or jsonld.js-based verifiers; re-issue them. The model still reads
264
+ the values back as floats.
265
+
266
+ - fix(ldp): a deeply nested Data Integrity document fails with
267
+ `OB3VerificationError` instead of escaping `OB3LdpVerifier.verify` (and
268
+ the issuer-DID peek in `verify_badge`) as a bare `RecursionError`.
269
+
270
+
7
271
  * v4.6.0 - 2026-10-13
8
272
 
9
273
  - feat: Python 3.15 is officially supported (#243). It joins the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openbadgeslib
3
- Version: 4.6.0
3
+ Version: 4.6.2
4
4
  Summary: Production-ready Python library & CLI for Open Badges 3.0 (W3C Verifiable Credentials): issue, verify, and revoke JWT-VC credentials with Bitstring Status Lists and did:web — plus strict OB 2.0 and legacy OB 1.0.
5
5
  Author-email: Luis González Fernández <luisgf@luisgf.es>, Jesús Cea Avión <jcea@jcea.es>
6
6
  License: LGPLv3
@@ -31,17 +31,18 @@ Requires-Python: >=3.10
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE.txt
33
33
  Requires-Dist: pypng>=0.20220715.0
34
- Requires-Dist: PyJWT[crypto]>=2.13
34
+ Requires-Dist: PyJWT[crypto]>=2.15.1
35
35
  Requires-Dist: cryptography>=45
36
36
  Requires-Dist: defusedxml>=0.7
37
37
  Provides-Extra: ldp
38
- Requires-Dist: pyld>=2.0.4; extra == "ldp"
38
+ Requires-Dist: pyld<4,>=3.3.0; extra == "ldp"
39
39
  Provides-Extra: eudi
40
- Requires-Dist: openvc-core<2,>=1.26; extra == "eudi"
40
+ Requires-Dist: openvc-core<2,>=1.35.0; extra == "eudi"
41
41
  Provides-Extra: ldp-sd
42
- Requires-Dist: openvc-core[data-integrity]<2,>=1.26; extra == "ldp-sd"
42
+ Requires-Dist: openvc-core[data-integrity]<2,>=1.35.0; extra == "ldp-sd"
43
+ Requires-Dist: pyld<4,>=3.3.0; extra == "ldp-sd"
43
44
  Provides-Extra: oid4vci
44
- Requires-Dist: openvc-core<2,>=1.26; extra == "oid4vci"
45
+ Requires-Dist: openvc-core<2,>=1.35.0; extra == "oid4vci"
45
46
  Provides-Extra: dev
46
47
  Requires-Dist: pytest>=8.0; extra == "dev"
47
48
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
@@ -69,7 +70,7 @@ with W3C Bitstring Status Lists and `did:web`. It also ships strict
69
70
  **OpenBadges 2.0** (JWS / hosted assertions) and a frozen **OpenBadges 1.0**
70
71
  legacy format, selected with `-V {1,2,3}` (default `3`).
71
72
 
72
- > **4.0.0 is released** (2026-07-22). It groups the breaking changes so upgrades stay rare: every CLI unifies on one `0/1/2` exit-code contract (a valid-but-untrusted badge now exits `2`, and the 255s are gone), and the legacy pycryptodome/python-ecdsa compat shim is removed — a live key **object** from either is no longer accepted, though key *files* are plain PEM and round-trip unchanged. OpenBadges 1.0 and the `openbadges-<command>` scripts stay, and a new unified `openbadges <command>` front-end joins them. **Python 3.10 stays supported throughout 4.x** (`requires-python >= 3.10`; CI tests 3.10–3.15) but is deprecated since 4.6.0, ahead of its 2026-10-31 end of life; 5.0 drops it and requires Python 3.11+. See **[Upgrading to 4.0](https://github.com/luisgf/openbadgeslib/wiki/Upgrading-to-4.0)** before pinning a major.
73
+ > **4.0.0 is released** (2026-07-22). It groups the breaking changes so upgrades stay rare: every CLI unifies on one `0/1/2` exit-code contract (a valid-but-untrusted badge now exits `2`, and the 255s are gone), and the legacy pycryptodome/python-ecdsa compat shim is removed — a live key **object** from either is no longer accepted, though key *files* are plain PEM and round-trip unchanged. OpenBadges 1.0 and the `openbadges-<command>` scripts stay, and a new unified `openbadges <command>` front-end joins them. **Python 3.10 stays supported throughout 4.x** (`requires-python >= 3.10`; CI tests 3.10–3.15) but is deprecated since 4.6.0 (it reached end of life on 2026-10-01); 5.0 drops it and requires Python 3.11+. See **[Upgrading to 4.0](https://github.com/luisgf/openbadgeslib/wiki/Upgrading-to-4.0)** before pinning a major.
73
74
 
74
75
  ## Features
75
76
 
@@ -147,7 +148,7 @@ all three OB versions.
147
148
  openbadgeslib 5.0 will require Python 3.11 or newer
148
149
  ([#242](https://github.com/luisgf/openbadgeslib/issues/242)).
149
150
  - [pypng](https://pypi.org/project/pypng/) >= 0.20220715.0
150
- - [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.13
151
+ - [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.15.1
151
152
  - [cryptography](https://pypi.org/project/cryptography/) >= 45
152
153
  - [defusedxml](https://pypi.org/project/defusedxml/) >= 0.7
153
154
 
@@ -13,7 +13,7 @@ with W3C Bitstring Status Lists and `did:web`. It also ships strict
13
13
  **OpenBadges 2.0** (JWS / hosted assertions) and a frozen **OpenBadges 1.0**
14
14
  legacy format, selected with `-V {1,2,3}` (default `3`).
15
15
 
16
- > **4.0.0 is released** (2026-07-22). It groups the breaking changes so upgrades stay rare: every CLI unifies on one `0/1/2` exit-code contract (a valid-but-untrusted badge now exits `2`, and the 255s are gone), and the legacy pycryptodome/python-ecdsa compat shim is removed — a live key **object** from either is no longer accepted, though key *files* are plain PEM and round-trip unchanged. OpenBadges 1.0 and the `openbadges-<command>` scripts stay, and a new unified `openbadges <command>` front-end joins them. **Python 3.10 stays supported throughout 4.x** (`requires-python >= 3.10`; CI tests 3.10–3.15) but is deprecated since 4.6.0, ahead of its 2026-10-31 end of life; 5.0 drops it and requires Python 3.11+. See **[Upgrading to 4.0](https://github.com/luisgf/openbadgeslib/wiki/Upgrading-to-4.0)** before pinning a major.
16
+ > **4.0.0 is released** (2026-07-22). It groups the breaking changes so upgrades stay rare: every CLI unifies on one `0/1/2` exit-code contract (a valid-but-untrusted badge now exits `2`, and the 255s are gone), and the legacy pycryptodome/python-ecdsa compat shim is removed — a live key **object** from either is no longer accepted, though key *files* are plain PEM and round-trip unchanged. OpenBadges 1.0 and the `openbadges-<command>` scripts stay, and a new unified `openbadges <command>` front-end joins them. **Python 3.10 stays supported throughout 4.x** (`requires-python >= 3.10`; CI tests 3.10–3.15) but is deprecated since 4.6.0 (it reached end of life on 2026-10-01); 5.0 drops it and requires Python 3.11+. See **[Upgrading to 4.0](https://github.com/luisgf/openbadgeslib/wiki/Upgrading-to-4.0)** before pinning a major.
17
17
 
18
18
  ## Features
19
19
 
@@ -91,7 +91,7 @@ all three OB versions.
91
91
  openbadgeslib 5.0 will require Python 3.11 or newer
92
92
  ([#242](https://github.com/luisgf/openbadgeslib/issues/242)).
93
93
  - [pypng](https://pypi.org/project/pypng/) >= 0.20220715.0
94
- - [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.13
94
+ - [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.15.1
95
95
  - [cryptography](https://pypi.org/project/cryptography/) >= 45
96
96
  - [defusedxml](https://pypi.org/project/defusedxml/) >= 0.7
97
97
 
@@ -20,12 +20,13 @@
20
20
  """
21
21
 
22
22
  # The runtime side of the "Supported Python versions" policy (CONTRIBUTING.md):
23
- # a CPython that is still supported but nearing (or past) end of life is
24
- # deprecated — importing openbadgeslib on it emits a DeprecationWarning — and
25
- # is dropped only at the next major release.
23
+ # a CPython that is still supported but past (or in its final months before)
24
+ # end of life is deprecated — importing openbadgeslib on it emits a
25
+ # DeprecationWarning — and is dropped only at the next major release.
26
26
  #
27
- # Python 3.10 is deprecated since 4.6.0 and dropped in 5.0 (#242). That drop
28
- # raises requires-python, the mypy python_version and DEPRECATED_BELOW together,
27
+ # Python 3.10 reached CPython end of life on 2026-10-01 (PEP 619); it is
28
+ # deprecated since 4.6.0 and dropped in 5.0 (#242). That drop raises
29
+ # requires-python, the mypy python_version and DEPRECATED_BELOW together,
29
30
  # rewording DEPRECATION_MESSAGE for the next deprecated version — or deletes
30
31
  # this module (and its call in __init__) if none is pending removal.
31
32
 
@@ -38,7 +39,7 @@ DEPRECATED_BELOW = (3, 11)
38
39
 
39
40
  DEPRECATION_MESSAGE = (
40
41
  'Python 3.10 is deprecated in openbadgeslib (CPython end of life: '
41
- '2026-10-31); openbadgeslib 5.0 will require Python 3.11 or newer.')
42
+ '2026-10-01); openbadgeslib 5.0 will require Python 3.11 or newer.')
42
43
 
43
44
 
44
45
  def warn_if_deprecated(version_info: Tuple[object, ...] = sys.version_info) -> None: