openbadgeslib 3.6.0__tar.gz → 3.7.0__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 (126) hide show
  1. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/Changelog.txt +68 -0
  2. {openbadgeslib-3.6.0/openbadgeslib.egg-info → openbadgeslib-3.7.0}/PKG-INFO +1 -5
  3. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/README.md +0 -2
  4. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/__init__.py +32 -11
  5. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/badge.py +9 -0
  6. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/keys.py +115 -100
  7. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/mail.py +1 -1
  8. openbadgeslib-3.7.0/openbadgeslib/ob1/__init__.py +61 -0
  9. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob1/badge.py +8 -29
  10. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/did.py +58 -5
  11. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/verifier.py +56 -8
  12. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/openbadges_keygenerator.py +24 -7
  13. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/openbadges_publish.py +234 -14
  14. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/openbadges_signer.py +70 -35
  15. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/openbadges_verifier.py +8 -1
  16. openbadgeslib-3.7.0/openbadgeslib/signer.py +13 -0
  17. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/util.py +48 -2
  18. openbadgeslib-3.7.0/openbadgeslib/verifier.py +13 -0
  19. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0/openbadgeslib.egg-info}/PKG-INFO +1 -5
  20. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib.egg-info/SOURCES.txt +1 -1
  21. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib.egg-info/requires.txt +0 -2
  22. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/pyproject.toml +0 -2
  23. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/conftest.py +2 -2
  24. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_cli_smoke.py +33 -0
  25. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_key_operation.py +64 -69
  26. openbadgeslib-3.7.0/tests/test_ob1_deprecation.py +158 -0
  27. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_did.py +105 -0
  28. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_publish_cli.py +219 -0
  29. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_util.py +47 -1
  30. openbadgeslib-3.6.0/openbadgeslib/ob1/__init__.py +0 -36
  31. openbadgeslib-3.6.0/openbadgeslib/signer.py +0 -4
  32. openbadgeslib-3.6.0/openbadgeslib/verifier.py +0 -4
  33. openbadgeslib-3.6.0/tests/runtests.sh +0 -3
  34. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/LICENSE.txt +0 -0
  35. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/MANIFEST.in +0 -0
  36. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/SECURITY.md +0 -0
  37. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/docs/README.md +0 -0
  38. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/_jws/__init__.py +0 -0
  39. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/_jws/exceptions.py +0 -0
  40. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/_jws/utils.py +0 -0
  41. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/baking.py +0 -0
  42. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/config.ini.example +0 -0
  43. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/confparser.py +0 -0
  44. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/errors.py +0 -0
  45. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/logs.py +0 -0
  46. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob1/signer.py +0 -0
  47. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob1/verifier.py +0 -0
  48. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob2/__init__.py +0 -0
  49. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob2/models.py +0 -0
  50. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob2/signer.py +0 -0
  51. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob2/verifier.py +0 -0
  52. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/__init__.py +0 -0
  53. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/contexts/__init__.py +0 -0
  54. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
  55. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
  56. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
  57. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
  58. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
  59. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
  60. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/credential.py +0 -0
  61. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/eudi.py +0 -0
  62. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/ldp.py +0 -0
  63. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/signer.py +0 -0
  64. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/status.py +0 -0
  65. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/status_list.py +0 -0
  66. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/ob3/status_registry.py +0 -0
  67. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/openbadges_init.py +0 -0
  68. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib/py.typed +0 -0
  69. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib.egg-info/dependency_links.txt +0 -0
  70. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib.egg-info/entry_points.txt +0 -0
  71. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/openbadgeslib.egg-info/top_level.txt +0 -0
  72. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/setup.cfg +0 -0
  73. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/config1.ini +0 -0
  74. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/conformance/Dockerfile.ob3 +0 -0
  75. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/conformance/README.md +0 -0
  76. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/conformance/conftest.py +0 -0
  77. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/conformance/docker-compose.yml +0 -0
  78. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/conformance/test_official_validators.py +0 -0
  79. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/ob_schemas/README.md +0 -0
  80. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/ob_schemas/ob_v3p0_achievementcredential_schema.json +0 -0
  81. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json +0 -0
  82. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/ob_schemas/refresh.sh +0 -0
  83. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/vc_di_eddsa/README.md +0 -0
  84. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
  85. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
  86. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
  87. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
  88. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
  89. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/images/sample1.png +0 -0
  90. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/images/sample1.svg +0 -0
  91. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/images/userimage01.svg +0 -0
  92. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/ldp_helpers.py +0 -0
  93. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/logo Python Espan/314/203a.svg" +0 -0
  94. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/logo Python Espa/303/261a.svg" +0 -0
  95. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_cli_json.py +0 -0
  96. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_confparser.py +0 -0
  97. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_docs.py +0 -0
  98. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_eddsa.py +0 -0
  99. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_jws.py +0 -0
  100. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob1_badge_io.py +0 -0
  101. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob1_signer.py +0 -0
  102. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob1_verifier.py +0 -0
  103. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob2_cli.py +0 -0
  104. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob2_models.py +0 -0
  105. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob2_signer.py +0 -0
  106. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob2_verifier.py +0 -0
  107. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_conformance_schema.py +0 -0
  108. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_contexts.py +0 -0
  109. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_credential.py +0 -0
  110. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_did_doc.py +0 -0
  111. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_eudi_sd_jwt.py +0 -0
  112. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_ldp.py +0 -0
  113. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_ldp_cli.py +0 -0
  114. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_ldp_sign.py +0 -0
  115. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_signer.py +0 -0
  116. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_signer_cli_ldp.py +0 -0
  117. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_status.py +0 -0
  118. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_status_list.py +0 -0
  119. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_status_registry.py +0 -0
  120. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_ob3_verifier.py +0 -0
  121. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_sign_ecc.pem +0 -0
  122. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_sign_rsa.pem +0 -0
  123. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_verify_ecc.pem +0 -0
  124. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/test_verify_rsa.pem +0 -0
  125. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/withoutxmlheader.svg +0 -0
  126. {openbadgeslib-3.6.0 → openbadgeslib-3.7.0}/tests/withxmlheader.svg +0 -0
@@ -4,6 +4,74 @@ OpenBadgesLib - Changelog
4
4
  Newest first. Dates are ISO 8601 (YYYY-MM-DD).
5
5
 
6
6
 
7
+ * v3.7.0 - 2026-07-08
8
+
9
+ - security(deps): `keys.py` (and the OB1 key-loading path) are ported to
10
+ `cryptography`, and both `pycryptodome` and `python-ecdsa` are dropped as
11
+ dependencies. They only generated and parsed key material here — signing
12
+ always went through PyJWT+`cryptography` — all of which `cryptography`
13
+ (already a hard dependency via `PyJWT[crypto]`) covers. This removes
14
+ python-ecdsa's permanent pip-audit flag (CVE-2024-23342 "Minerva", wontfix
15
+ upstream), moves ECC key generation onto constant-time code, and shrinks
16
+ the install to a single crypto backend. PEM output is byte-compatible
17
+ (PKCS#1 `RSA PRIVATE KEY`, SEC1 `EC PRIVATE KEY`, SubjectPublicKeyInfo
18
+ public keys), so existing key files round-trip. `key_to_pem` still accepts
19
+ a live pycryptodome/python-ecdsa key object via a soft import, for a caller
20
+ predating the port. (#167)
21
+
22
+ - feat(cli): `--json` machine-readable output and a documented exit-code
23
+ contract for `openbadges-signer`, `openbadges-publish` and
24
+ `openbadges-keygenerator` (the verifier already had one), so issuance can
25
+ be automated without scraping human text. Each prints exactly one JSON
26
+ object to stdout — the signer `{ob_version, badge_file, jti, status_index,
27
+ proof_format}`, the keygenerator `{key_type, private_key, public_key}`,
28
+ publish `{did, files_written, status_operation, skipped}`, and
29
+ `--list`/`--status` the queried records — under a shared exit contract:
30
+ `0` success, `2` partial (some badges skipped), `1` any error (reported as
31
+ `{"error": "..."}`). Human output and its historical (inconsistent) exit
32
+ codes are untouched — unifying those is reserved for 4.0.0. `publish
33
+ --json` is OpenBadges 3.0 only. (#166)
34
+
35
+ - feat(ob3): JWT-VC verification now honours the token's `kid` header for a
36
+ multi-key did:web issuer. `OB3Verifier.for_issuer_did` fetches the DID
37
+ document once and selects the exact `verificationMethod` the kid names (an
38
+ absolute `did:web:…#badge_N` URL, or a bare `#badge_N` resolved against the
39
+ issuer DID), so issuers that rotate keys or publish one key per badge now
40
+ verify without pinning `-k`/`-l` — closing the gap where only the first
41
+ listed key worked. Fails closed when the kid names a method of a *different*
42
+ DID (the header is attacker-controlled) or one absent from the document; a
43
+ token with no kid still falls back to the first method. (#163)
44
+
45
+ - feat(ob1): OpenBadges 1.0 is now deprecated (removal scheduled for the
46
+ grouped 4.0.0 release, #170). Reaching the legacy import surface — the
47
+ `openbadgeslib.ob1` package, the top-level `openbadgeslib.badge` /
48
+ `openbadgeslib.signer` / `openbadgeslib.verifier` shims, and the
49
+ unprefixed `openbadgeslib.Signer` / `Verifier` / `Badge` / … re-exports —
50
+ now emits a `DeprecationWarning`, and the `-V 1` CLI paths (signer,
51
+ verifier, publish) print a `[!]` notice (suppressed under the verifier's
52
+ `--json`, so machine output stays clean). OB1 stays fully functional; a
53
+ bare `import openbadgeslib` and the OB2/OB3 APIs stay silent. OpenBadges
54
+ 2.0 is **not** affected — it remains fully supported. The lifecycle policy
55
+ and the rejected alternatives (a `[legacy]` extra, a separate PyPI
56
+ package) are recorded on the "Python API OB1" wiki page. (#159)
57
+
58
+ - feat(publish): `openbadges-publish -V 3` gains two read-only registry
59
+ query commands, closing the credential lifecycle from the CLI
60
+ (issue → revoke → audit). `--list [-b badge]` tabulates every issued
61
+ credential — jti, recipient, issue date and state (active / REVOKED /
62
+ SUSPENDED); `--status <jti|email>` prints the full record of the matching
63
+ credential(s), including the revocation or suspension date and reason.
64
+ Both read only the private status registries, so neither needs an output
65
+ directory (`-o`). (#157)
66
+
67
+ - refactor(cli): `openbadges-keygenerator` no longer accepts the decorative
68
+ `-V/--ob-version` flag. Key material never depended on the OpenBadges
69
+ version (the algorithm comes from the badge's `key_type`), so the flag only
70
+ changed a log line. Internal tidy-ups alongside it: a dead `if args.badge`
71
+ guard in the signer (`-b` is required), the redundant `tests/runtests.sh`
72
+ wrapper, and an inaccurate conftest comment. (#156)
73
+
74
+
7
75
  * v3.6.0 - 2026-07-08
8
76
 
9
77
  - feat(ob3): the verifier now exposes the raw, already-validated VC document
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openbadgeslib
3
- Version: 3.6.0
3
+ Version: 3.7.0
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
@@ -29,8 +29,6 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
29
29
  Requires-Python: >=3.10
30
30
  Description-Content-Type: text/markdown
31
31
  License-File: LICENSE.txt
32
- Requires-Dist: pycryptodome>=3.20
33
- Requires-Dist: ecdsa>=0.19
34
32
  Requires-Dist: pypng>=0.20220715.0
35
33
  Requires-Dist: PyJWT[crypto]>=2.8
36
34
  Requires-Dist: cryptography>=42
@@ -136,8 +134,6 @@ all three OB versions.
136
134
  ## Requirements
137
135
 
138
136
  - Python >= 3.10 (tested on 3.10–3.14)
139
- - [pycryptodome](https://pypi.org/project/pycryptodome/) >= 3.20
140
- - [ecdsa](https://pypi.org/project/ecdsa/) >= 0.19
141
137
  - [pypng](https://pypi.org/project/pypng/) >= 0.20220715.0
142
138
  - [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.8
143
139
  - [cryptography](https://pypi.org/project/cryptography/) >= 42
@@ -85,8 +85,6 @@ all three OB versions.
85
85
  ## Requirements
86
86
 
87
87
  - Python >= 3.10 (tested on 3.10–3.14)
88
- - [pycryptodome](https://pypi.org/project/pycryptodome/) >= 3.20
89
- - [ecdsa](https://pypi.org/project/ecdsa/) >= 0.19
90
88
  - [pypng](https://pypi.org/project/pypng/) >= 0.20220715.0
91
89
  - [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.8
92
90
  - [cryptography](https://pypi.org/project/cryptography/) >= 42
@@ -21,17 +21,7 @@
21
21
  License along with this library.
22
22
  """
23
23
 
24
- # ── OpenBadges 1.0 (legacy) ─────────────────────────────────────────────────────
25
- # The classes formerly exposed as "OpenBadges 2.0" are the pre-2.0 wire format
26
- # (no @context/type, uid, verify{}, Unix timestamps). They are re-exported here
27
- # unchanged for backward compatibility; the strict OB 2.0 implementation lives
28
- # in openbadgeslib.ob2 (OB2Signer/OB2Verifier).
29
- from .ob1 import ( # noqa: F401
30
- Signer, Verifier, VerifyInfo,
31
- Badge, BadgeSigned, Assertion,
32
- BadgeStatus, BadgeImgType, BadgeType,
33
- extract_svg_assertion, extract_png_assertion,
34
- )
24
+ from typing import Any
35
25
 
36
26
  # ── OpenBadges 2.0 (strict) ──────────────────────────────────────────────────
37
27
  from .ob2 import ( # noqa: F401
@@ -47,3 +37,34 @@ from .ob3 import ( # noqa: F401
47
37
  # ── Shared utilities ────────────────────────────────────────────────────────────
48
38
  from .keys import KeyFactory, KeyRSA, KeyECC # noqa: F401
49
39
  from .util import __version__ # noqa: F401
40
+
41
+
42
+ # ── OpenBadges 1.0 (legacy) ──────────────────────────────────────────────────
43
+ # The unprefixed OB1 names (Signer, Verifier, Badge, …) stay importable from
44
+ # the top-level package for backward compatibility, but they are the legacy
45
+ # OpenBadges 1.0 surface: accessing one now emits a DeprecationWarning
46
+ # (removal in 4.0.0). Modern code uses openbadgeslib.ob2 / openbadgeslib.ob3.
47
+ # They are resolved lazily from the ob1 leaf modules (PEP 562), so a bare
48
+ # `import openbadgeslib` neither warns nor drags in the ob1 package.
49
+ _OB1_API = {
50
+ 'Signer': 'signer', 'Verifier': 'verifier', 'VerifyInfo': 'verifier',
51
+ 'Badge': 'badge', 'BadgeSigned': 'badge', 'Assertion': 'badge',
52
+ 'BadgeStatus': 'badge', 'BadgeImgType': 'badge', 'BadgeType': 'badge',
53
+ 'extract_svg_assertion': 'badge', 'extract_png_assertion': 'badge',
54
+ }
55
+
56
+
57
+ def __getattr__(name: str) -> Any:
58
+ module = _OB1_API.get(name)
59
+ if module is None:
60
+ raise AttributeError(
61
+ 'module %r has no attribute %r' % (__name__, name))
62
+ import importlib
63
+ import warnings
64
+ warnings.warn(
65
+ 'openbadgeslib.%s is the legacy OpenBadges 1.0 API and is deprecated; '
66
+ 'it will be removed in openbadgeslib 4.0.0. Use openbadgeslib.ob2 '
67
+ '(strict OB 2.0) or openbadgeslib.ob3 (OB 3.0). See the "OpenBadges '
68
+ '1.0 lifecycle" wiki page.' % name,
69
+ DeprecationWarning, stacklevel=2)
70
+ return getattr(importlib.import_module('.ob1.' + module, __name__), name)
@@ -1,10 +1,19 @@
1
1
  """OpenBadges 1.0 (legacy) badge objects — compatibility shim, re-exports from openbadgeslib.ob1."""
2
+ import warnings
3
+
2
4
  from .ob1.badge import (
3
5
  BadgeStatus, BadgeImgType, BadgeType,
4
6
  Assertion, Badge, BadgeSigned,
5
7
  extract_svg_assertion, extract_png_assertion,
6
8
  )
7
9
 
10
+ warnings.warn(
11
+ 'openbadgeslib.badge is a legacy OpenBadges 1.0 compatibility shim and is '
12
+ 'deprecated; it will be removed in openbadgeslib 4.0.0. Import from '
13
+ 'openbadgeslib.ob2 (strict OB 2.0) or openbadgeslib.ob3 (OB 3.0) instead. '
14
+ 'See the "OpenBadges 1.0 lifecycle" wiki page.',
15
+ DeprecationWarning, stacklevel=2)
16
+
8
17
  __all__ = [
9
18
  'BadgeStatus', 'BadgeImgType', 'BadgeType',
10
19
  'Assertion', 'Badge', 'BadgeSigned',
@@ -21,11 +21,17 @@
21
21
  License along with this library.
22
22
  """
23
23
 
24
+ # Key material generation and parsing. All backed by ``cryptography`` (already
25
+ # a hard dependency through PyJWT[crypto]); pycryptodome and python-ecdsa were
26
+ # dropped in the 3.7 port (#167) — they only ever generated/parsed keys here,
27
+ # never signed, and python-ecdsa carried a permanent pip-audit CVE flag
28
+ # (CVE-2024-23342, Minerva). ``key_to_pem`` still accepts a live pycryptodome /
29
+ # python-ecdsa key object via a soft import, for a caller predating the port.
30
+
24
31
  from typing import Any, Optional, Tuple, Union, cast
25
32
  from .errors import PublicKeyReadError, UnknownKeyType
26
- from ecdsa import SigningKey, VerifyingKey, NIST256p
27
- from Crypto.PublicKey import RSA
28
33
  from cryptography.hazmat.primitives import serialization as _crypto_serialization
34
+ from cryptography.hazmat.primitives.asymmetric import ec, rsa
29
35
  from cryptography.hazmat.primitives.asymmetric.ed25519 import (
30
36
  Ed25519PrivateKey, Ed25519PublicKey)
31
37
  from enum import Enum
@@ -52,6 +58,27 @@ def KeyFactory(key_type: 'KeyType' = KeyType.RSA) -> 'Union[KeyRSA, KeyECC, KeyE
52
58
  raise UnknownKeyType()
53
59
 
54
60
 
61
+ def _pem_bytes(key_pem: Union[str, bytes]) -> bytes:
62
+ return key_pem.encode('utf-8') if isinstance(key_pem, str) else key_pem
63
+
64
+
65
+ def _private_pem(key: Any) -> bytes:
66
+ """Serialise an RSA/EC private key to a TraditionalOpenSSL PEM — PKCS#1
67
+ ``RSA PRIVATE KEY`` / SEC1 ``EC PRIVATE KEY``, exactly what pycryptodome and
68
+ python-ecdsa emitted, so existing key files round-trip unchanged."""
69
+ return cast(bytes, key.private_bytes(
70
+ _crypto_serialization.Encoding.PEM,
71
+ _crypto_serialization.PrivateFormat.TraditionalOpenSSL,
72
+ _crypto_serialization.NoEncryption()))
73
+
74
+
75
+ def _public_pem(key: Any) -> bytes:
76
+ """Serialise a public key to a SubjectPublicKeyInfo ``PUBLIC KEY`` PEM."""
77
+ return cast(bytes, key.public_bytes(
78
+ _crypto_serialization.Encoding.PEM,
79
+ _crypto_serialization.PublicFormat.SubjectPublicKeyInfo))
80
+
81
+
55
82
  class KeyBase():
56
83
  def __init__(self) -> None:
57
84
  self.priv_key: Any = None # crypto Object
@@ -73,67 +100,56 @@ class KeyRSA(KeyBase):
73
100
 
74
101
  def generate_keypair(self) -> Tuple[bytes, bytes]:
75
102
  """ Generate a RSA Key, returning in PEM Format """
76
-
77
- # RSA Key Generation
78
- self.priv_key = RSA.generate(self._key_size)
79
- priv_key_pem = self.priv_key.export_key('PEM')
80
- self.pub_key = self.priv_key.publickey()
81
- pub_key_pem = self.pub_key.export_key('PEM')
82
-
83
- return priv_key_pem, pub_key_pem
103
+ self.priv_key = rsa.generate_private_key(
104
+ public_exponent=65537, key_size=self._key_size)
105
+ self.pub_key = self.priv_key.public_key()
106
+ return self.get_priv_key_pem(), self.get_pub_key_pem()
84
107
 
85
108
  def read_private_key(self, key_pem: Any = None) -> None:
86
109
  """ Read the private key from param in PEM format """
87
- self.priv_key = RSA.import_key(key_pem)
110
+ self.priv_key = _crypto_serialization.load_pem_private_key(
111
+ _pem_bytes(key_pem), password=None)
88
112
 
89
113
  def read_public_key(self, key_pem: Any = None) -> None:
90
114
  """ Read the public key from param in PEM format """
91
- self.pub_key = RSA.import_key(key_pem)
115
+ self.pub_key = _crypto_serialization.load_pem_public_key(
116
+ _pem_bytes(key_pem))
92
117
 
93
118
  def get_priv_key_pem(self) -> bytes:
94
- return cast(bytes, self.priv_key.export_key('PEM'))
119
+ return _private_pem(self.priv_key)
95
120
 
96
121
  def get_pub_key_pem(self) -> bytes:
97
- return cast(bytes, self.pub_key.export_key('PEM'))
122
+ return _public_pem(self.pub_key)
98
123
 
99
124
 
100
125
  class KeyECC(KeyBase):
101
- """ Elliptic Curve Cryptography Factory class """
126
+ """ Elliptic Curve Cryptography Factory class (NIST P-256 / ES256) """
102
127
 
103
- def __init__(self, key_curve: Any = NIST256p) -> None:
104
- self._key_curve = key_curve
128
+ def __init__(self, key_curve: Any = None) -> None:
129
+ self._key_curve = key_curve or ec.SECP256R1()
105
130
  super().__init__()
106
131
 
107
132
  def generate_keypair(self) -> Tuple[bytes, bytes]:
108
- """ Generate a ECDSA keypair """
109
-
110
- # Private key generation
111
- self.priv_key = SigningKey.generate(curve=self._key_curve)
112
- priv_key_pem = self.priv_key.to_pem()
113
-
114
- # Derive the public (verifying) key from the private key.
115
- self.pub_key = self.priv_key.get_verifying_key()
116
- pub_key_pem = self.pub_key.to_pem()
117
-
118
- return priv_key_pem, pub_key_pem
133
+ """ Generate an ECDSA keypair """
134
+ self.priv_key = ec.generate_private_key(self._key_curve)
135
+ self.pub_key = self.priv_key.public_key()
136
+ return self.get_priv_key_pem(), self.get_pub_key_pem()
119
137
 
120
- def read_private_key(self, key_pem: Optional[Union[str, bytes]] = None) -> None:
138
+ def read_private_key(self, key_pem: Any = None) -> None:
121
139
  """ Read the private key from param in PEM format """
122
- self.priv_key = SigningKey.from_pem(key_pem)
140
+ self.priv_key = _crypto_serialization.load_pem_private_key(
141
+ _pem_bytes(key_pem), password=None)
123
142
 
124
- def read_public_key(self, key_pem: Optional[Union[str, bytes]] = None) -> None:
143
+ def read_public_key(self, key_pem: Any = None) -> None:
125
144
  """ Read the public key from param in PEM format """
126
- self.pub_key = VerifyingKey.from_pem(key_pem)
145
+ self.pub_key = _crypto_serialization.load_pem_public_key(
146
+ _pem_bytes(key_pem))
127
147
 
128
148
  def get_priv_key_pem(self) -> bytes:
129
- return cast(bytes, self.priv_key.to_pem())
149
+ return _private_pem(self.priv_key)
130
150
 
131
151
  def get_pub_key_pem(self) -> bytes:
132
- return cast(bytes, self.pub_key.to_pem())
133
-
134
-
135
- def _pem_bytes(key_pem: Union[str, bytes]) -> bytes:
136
- return key_pem.encode('utf-8') if isinstance(key_pem, str) else key_pem
152
+ return _public_pem(self.pub_key)
137
153
 
138
154
 
139
155
  def _load_ed25519_private_key(key_pem: Optional[Union[str, bytes]]) -> Ed25519PrivateKey:
@@ -158,8 +174,8 @@ class KeyEd25519(KeyBase):
158
174
  """Edwards-curve Ed25519 (EdDSA) key class, backed by ``cryptography``.
159
175
 
160
176
  Kept separate from KeyECC: Ed25519 is an EdDSA curve, not an ECDSA NIST
161
- curve. It maps to the JWS ``EdDSA`` algorithm, and the ``ecdsa`` library
162
- used for the NIST curves would misread its PEM (see detect_key_type).
177
+ curve. It maps to the JWS ``EdDSA`` algorithm, and its PEM is PKCS#8
178
+ (Ed25519 has no TraditionalOpenSSL encoding).
163
179
  """
164
180
 
165
181
  def generate_keypair(self) -> Tuple[bytes, bytes]:
@@ -183,9 +199,7 @@ class KeyEd25519(KeyBase):
183
199
  _crypto_serialization.NoEncryption()))
184
200
 
185
201
  def get_pub_key_pem(self) -> bytes:
186
- return cast(bytes, self.pub_key.public_bytes(
187
- _crypto_serialization.Encoding.PEM,
188
- _crypto_serialization.PublicFormat.SubjectPublicKeyInfo))
202
+ return _public_pem(self.pub_key)
189
203
 
190
204
 
191
205
  def alg_for_key_type(key_type: 'KeyType') -> str:
@@ -199,28 +213,54 @@ def alg_for_key_type(key_type: 'KeyType') -> str:
199
213
  raise UnknownKeyType('No signing algorithm for key type: %r' % (key_type,))
200
214
 
201
215
 
216
+ def _legacy_key_to_pem(key: Any) -> Optional[Union[str, bytes]]:
217
+ """Best-effort PEM for a live pycryptodome RSA or python-ecdsa key object.
218
+
219
+ Kept for backward compatibility with a caller still holding one from before
220
+ the cryptography port (#167). Both libraries were dropped as dependencies,
221
+ so this soft-imports them and returns ``None`` when they are absent (or the
222
+ object is neither) — the caller then raises UnknownKeyType.
223
+ """
224
+ try:
225
+ from Crypto.PublicKey import RSA as _RSA
226
+ if isinstance(key, _RSA.RsaKey):
227
+ return cast(bytes, key.export_key('PEM'))
228
+ except ImportError:
229
+ pass
230
+ try:
231
+ from ecdsa import SigningKey as _SigningKey, VerifyingKey as _VerifyingKey
232
+ if isinstance(key, (_SigningKey, _VerifyingKey)):
233
+ return cast(Union[str, bytes], key.to_pem())
234
+ except ImportError:
235
+ pass
236
+ return None
237
+
238
+
202
239
  def key_to_pem(key: Any) -> Union[str, bytes]:
203
- """Convert a pycryptodome RSA or ecdsa key object to PEM bytes.
240
+ """Convert a key object to PEM bytes.
204
241
 
205
- Bytes/str are passed through unchanged. Centralised here so the OB2 JWS
206
- layer and both OB3 signer/verifier share one implementation instead of
207
- three hand-maintained copies.
242
+ Handles ``cryptography`` key objects (what keys.py and ob1.badge now
243
+ produce) and passes bytes/str through unchanged; a live pycryptodome /
244
+ python-ecdsa object is still accepted via :func:`_legacy_key_to_pem`.
245
+ Centralised here so the OB1/OB2 JWS layer and both OB3 signer/verifier
246
+ share one implementation instead of hand-maintained copies.
208
247
  """
209
- if isinstance(key, RSA.RsaKey):
210
- return key.export_key('PEM')
211
- if isinstance(key, (SigningKey, VerifyingKey)):
212
- return cast(Union[str, bytes], key.to_pem())
248
+ if isinstance(key, (bytes, str)):
249
+ return key
250
+ if isinstance(key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey)):
251
+ return _private_pem(key)
252
+ if isinstance(key, (rsa.RSAPublicKey, ec.EllipticCurvePublicKey)):
253
+ return _public_pem(key)
213
254
  if isinstance(key, Ed25519PrivateKey):
214
255
  return key.private_bytes(
215
256
  _crypto_serialization.Encoding.PEM,
216
257
  _crypto_serialization.PrivateFormat.PKCS8,
217
258
  _crypto_serialization.NoEncryption())
218
259
  if isinstance(key, Ed25519PublicKey):
219
- return key.public_bytes(
220
- _crypto_serialization.Encoding.PEM,
221
- _crypto_serialization.PublicFormat.SubjectPublicKeyInfo)
222
- if isinstance(key, (bytes, str)):
223
- return key
260
+ return _public_pem(key)
261
+ legacy = _legacy_key_to_pem(key)
262
+ if legacy is not None:
263
+ return legacy
224
264
  raise UnknownKeyType('Unsupported key object type: %r' % type(key))
225
265
 
226
266
 
@@ -232,7 +272,6 @@ def public_jwk_from_pem(pubkey_pem: Union[str, bytes]) -> dict[str, Any]:
232
272
  verification keys in a did:web DID document.
233
273
  """
234
274
  import json
235
- from cryptography.hazmat.primitives.asymmetric import ec, rsa
236
275
  from jwt.algorithms import ECAlgorithm, OKPAlgorithm, RSAAlgorithm
237
276
  try:
238
277
  pub = _crypto_serialization.load_pem_public_key(_pem_bytes(pubkey_pem))
@@ -262,7 +301,6 @@ def ec_curve_from_pem(pem_data: Union[str, bytes]) -> Optional[str]:
262
301
  uses this to pick the ES256 vs ES384 signing backend — the ECDSA curve
263
302
  fixes the JOSE algorithm.
264
303
  """
265
- from cryptography.hazmat.primitives.asymmetric import ec
266
304
  data = _pem_bytes(pem_data)
267
305
  for load in (
268
306
  lambda: _crypto_serialization.load_pem_public_key(data),
@@ -279,53 +317,30 @@ def ec_curve_from_pem(pem_data: Union[str, bytes]) -> Optional[str]:
279
317
  return None
280
318
 
281
319
 
282
- def _is_ed25519_pem(pem_data: Union[str, bytes]) -> bool:
283
- """True if pem_data is an Ed25519 public or private key.
320
+ def detect_key_type(pem_data: Union[str, bytes]) -> 'KeyType':
321
+ """Positive key-type detection via ``cryptography``'s PEM loaders.
284
322
 
285
- Probed before the RSA/ECC checks in detect_key_type: the ``ecdsa`` library's
286
- VerifyingKey/SigningKey.from_pem *accept* an Ed25519 PEM and would otherwise
287
- misclassify it as KeyType.ECC, pinning the wrong (ES*) algorithm family.
288
- ``cryptography``'s loaders are unambiguous about the key type.
323
+ Accepts a public or private PEM and distinguishes Ed25519/RSA/EC by the
324
+ loaded key's concrete type cryptography's loaders are unambiguous, unlike
325
+ the old python-ecdsa loader which accepted an Ed25519 PEM as an EC key and
326
+ forced the wrong (ES*) algorithm family.
289
327
  """
290
328
  data = _pem_bytes(pem_data)
291
- try:
292
- if isinstance(_crypto_serialization.load_pem_public_key(data), Ed25519PublicKey):
293
- return True
294
- except Exception:
295
- pass
296
- try:
297
- if isinstance(_crypto_serialization.load_pem_private_key(data, password=None),
298
- Ed25519PrivateKey):
299
- return True
300
- except Exception:
301
- pass
302
- return False
303
-
304
-
305
- def detect_key_type(pem_data: Union[str, bytes]) -> 'KeyType':
306
- """ Positive Key type detection """
329
+ key: Any = None
330
+ for load in (
331
+ lambda: _crypto_serialization.load_pem_public_key(data),
332
+ lambda: _crypto_serialization.load_pem_private_key(data, password=None),
333
+ ):
334
+ try:
335
+ key = load()
336
+ break
337
+ except Exception:
338
+ continue
307
339
 
308
- # Ed25519 first: see _is_ed25519_pem — the ecdsa library would otherwise
309
- # claim an Ed25519 PEM as ECC below.
310
- if _is_ed25519_pem(pem_data):
340
+ if isinstance(key, (Ed25519PrivateKey, Ed25519PublicKey)):
311
341
  return KeyType.ED25519
312
-
313
- try:
314
- RSA.import_key(pem_data)
342
+ if isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)):
315
343
  return KeyType.RSA
316
- except Exception:
317
- pass
318
-
319
- try:
320
- VerifyingKey.from_pem(pem_data)
321
- return KeyType.ECC
322
- except Exception:
323
- pass
324
-
325
- try:
326
- SigningKey.from_pem(pem_data)
344
+ if isinstance(key, (ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey)):
327
345
  return KeyType.ECC
328
- except Exception:
329
- pass
330
-
331
346
  raise UnknownKeyType('Unable to guess Key type')
@@ -29,7 +29,7 @@ from email.mime.multipart import MIMEMultipart
29
29
  from email.mime.text import MIMEText
30
30
  from email.utils import formatdate
31
31
  from email.header import Header
32
- from .ob1 import BadgeImgType
32
+ from .ob1.badge import BadgeImgType # leaf import: avoids the ob1 deprecation warning
33
33
  from .errors import BadgeImgFormatUnsupported
34
34
 
35
35
 
@@ -0,0 +1,61 @@
1
+ """
2
+ OpenBadges Library
3
+
4
+ Copyright (c) 2014-2026, Luis González Fernández, luisgf@luisgf.es
5
+ Copyright (c) 2014-2026, Jesús Cea Avión, jcea@jcea.es
6
+
7
+ All rights reserved.
8
+
9
+ This library is free software; you can redistribute it and/or
10
+ modify it under the terms of the GNU Lesser General Public
11
+ License as published by the Free Software Foundation; either
12
+ version 3.0 of the License, or (at your option) any later version.
13
+
14
+ This library is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
+ Lesser General Public License for more details.
18
+
19
+ You should have received a copy of the GNU Lesser General Public
20
+ License along with this library.
21
+ """
22
+
23
+ # OpenBadges 1.0 is a dead format (Mozilla Backpack shut down in 2019) and is
24
+ # deprecated: it will be removed in openbadgeslib 4.0.0 (see the "OpenBadges
25
+ # 1.0 lifecycle" wiki page). Reaching the OB1 API *through this package* —
26
+ # `from openbadgeslib.ob1 import Signer`, `openbadgeslib.ob1.Badge` — emits a
27
+ # DeprecationWarning. Migrate to openbadgeslib.ob2 (strict OB 2.0) or
28
+ # openbadgeslib.ob3 (OB 3.0).
29
+ #
30
+ # The symbols are resolved lazily (PEP 562) from their leaf modules, so the
31
+ # warning fires on the deprecated *package* surface only. Library internals
32
+ # that still need the shared, config-driven badge model import it straight
33
+ # from openbadgeslib.ob1.badge (BadgeImgType, Badge) and stay silent.
34
+ import importlib
35
+ import warnings
36
+ from typing import Any
37
+
38
+ #: Public OB1 name -> the leaf submodule that defines it.
39
+ _OB1_API = {
40
+ 'BadgeStatus': 'badge', 'BadgeImgType': 'badge', 'BadgeType': 'badge',
41
+ 'Assertion': 'badge', 'Badge': 'badge', 'BadgeSigned': 'badge',
42
+ 'extract_svg_assertion': 'badge', 'extract_png_assertion': 'badge',
43
+ 'Signer': 'signer', 'Verifier': 'verifier', 'VerifyInfo': 'verifier',
44
+ }
45
+
46
+ __all__ = list(_OB1_API)
47
+
48
+ _OB1_DEPRECATION = (
49
+ 'openbadgeslib.ob1 (OpenBadges 1.0) is deprecated and will be removed in '
50
+ 'openbadgeslib 4.0.0; migrate to openbadgeslib.ob2 (strict OB 2.0) or '
51
+ 'openbadgeslib.ob3 (OB 3.0). See the "OpenBadges 1.0 lifecycle" wiki page.'
52
+ )
53
+
54
+
55
+ def __getattr__(name: str) -> Any:
56
+ module = _OB1_API.get(name)
57
+ if module is None:
58
+ raise AttributeError(
59
+ 'module %r has no attribute %r' % (__name__, name))
60
+ warnings.warn(_OB1_DEPRECATION, DeprecationWarning, stacklevel=2)
61
+ return getattr(importlib.import_module('.' + module, __name__), name)
@@ -25,9 +25,6 @@ import os
25
25
  from enum import Enum
26
26
  from typing import Any, Optional, Union, cast
27
27
 
28
- from Crypto.PublicKey import RSA
29
- from ecdsa import SigningKey, VerifyingKey
30
-
31
28
  from ..keys import KeyType, detect_key_type
32
29
  from ..errors import (BadgeImgFormatUnsupported, AssertionFormatIncorrect,
33
30
  ErrorParsingFile, UnknownKeyType, PrivateKeyReadError,
@@ -122,32 +119,14 @@ class Badge():
122
119
  self.priv_key: Any = None
123
120
  self.pub_key: Any = None
124
121
 
125
- # Initialize an Key Object
126
- if self.key_type is KeyType.RSA:
127
- if self.pubkey_pem:
128
- try:
129
- self.pub_key = RSA.import_key(self.pubkey_pem)
130
- except Exception as exc:
131
- raise PublicKeyReadError('Unable to read RSA public key: %s' % exc) from exc
132
- if self.privkey_pem:
133
- try:
134
- self.priv_key = RSA.import_key(self.privkey_pem)
135
- except Exception as exc:
136
- raise PrivateKeyReadError('Unable to read RSA private key: %s' % exc) from exc
137
- elif self.key_type is KeyType.ECC:
138
- if self.pubkey_pem:
139
- try:
140
- self.pub_key = VerifyingKey.from_pem(self.pubkey_pem)
141
- except Exception as exc:
142
- raise PublicKeyReadError('Unable to read ECC public key: %s' % exc) from exc
143
- if self.privkey_pem:
144
- try:
145
- self.priv_key = SigningKey.from_pem(self.privkey_pem)
146
- except Exception as exc:
147
- raise PrivateKeyReadError('Unable to read ECC private key: %s' % exc) from exc
148
- elif self.key_type is KeyType.ED25519:
122
+ # Load whatever key material was supplied. RSA/ECC used to go through
123
+ # pycryptodome/python-ecdsa; all three families now share cryptography's
124
+ # unambiguous PEM loaders (the #167 port), producing cryptography key
125
+ # objects that the JWS layer serialises via keys.key_to_pem.
126
+ if self.key_type in (KeyType.RSA, KeyType.ECC, KeyType.ED25519):
149
127
  from cryptography.hazmat.primitives.serialization import (
150
128
  load_pem_private_key, load_pem_public_key)
129
+ label = self.key_type.name
151
130
  if self.pubkey_pem:
152
131
  try:
153
132
  pub_pem = self.pubkey_pem.encode('utf-8') \
@@ -155,7 +134,7 @@ class Badge():
155
134
  self.pub_key = load_pem_public_key(pub_pem)
156
135
  except Exception as exc:
157
136
  raise PublicKeyReadError(
158
- 'Unable to read Ed25519 public key: %s' % exc) from exc
137
+ 'Unable to read %s public key: %s' % (label, exc)) from exc
159
138
  if self.privkey_pem:
160
139
  try:
161
140
  priv_pem = self.privkey_pem.encode('utf-8') \
@@ -163,7 +142,7 @@ class Badge():
163
142
  self.priv_key = load_pem_private_key(priv_pem, password=None)
164
143
  except Exception as exc:
165
144
  raise PrivateKeyReadError(
166
- 'Unable to read Ed25519 private key: %s' % exc) from exc
145
+ 'Unable to read %s private key: %s' % (label, exc)) from exc
167
146
  elif self.key_type is not None:
168
147
  # key_type=None is a valid "no key material yet" state; any other
169
148
  # value is an unsupported key type and must fail loudly.