openbadgeslib 0.4.2__tar.gz → 1.1.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.
- openbadgeslib-1.1.2/Changelog.txt +256 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/MANIFEST.in +1 -1
- openbadgeslib-1.1.2/PKG-INFO +211 -0
- openbadgeslib-1.1.2/README.md +174 -0
- openbadgeslib-1.1.2/docs/authors.rst +21 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/docs/conf.py +14 -4
- openbadgeslib-1.1.2/docs/development.rst +165 -0
- openbadgeslib-1.1.2/docs/glossary.rst +147 -0
- openbadgeslib-1.1.2/docs/index.rst +30 -0
- openbadgeslib-1.1.2/docs/installation.rst +159 -0
- openbadgeslib-1.1.2/docs/intro.rst +190 -0
- openbadgeslib-1.1.2/docs/userguide.rst +367 -0
- openbadgeslib-1.1.2/openbadgeslib/__init__.py +40 -0
- openbadgeslib-1.1.2/openbadgeslib/_jws/__init__.py +102 -0
- openbadgeslib-1.1.2/openbadgeslib/_jws/exceptions.py +18 -0
- openbadgeslib-1.1.2/openbadgeslib/_jws/utils.py +30 -0
- openbadgeslib-1.1.2/openbadgeslib/badge.py +12 -0
- openbadgeslib-1.1.2/openbadgeslib/baking.py +166 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/config.ini.example +6 -1
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/confparser.py +30 -4
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/errors.py +18 -15
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/keys.py +58 -27
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/logs.py +20 -3
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/mail.py +17 -9
- openbadgeslib-1.1.2/openbadgeslib/ob2/__init__.py +36 -0
- {openbadgeslib-0.4.2/openbadgeslib → openbadgeslib-1.1.2/openbadgeslib/ob2}/badge.py +90 -104
- openbadgeslib-1.1.2/openbadgeslib/ob2/signer.py +152 -0
- openbadgeslib-1.1.2/openbadgeslib/ob2/verifier.py +178 -0
- {openbadgeslib-0.4.2/openbadgeslib → openbadgeslib-1.1.2/openbadgeslib/ob3}/__init__.py +13 -10
- openbadgeslib-1.1.2/openbadgeslib/ob3/credential.py +198 -0
- openbadgeslib-1.1.2/openbadgeslib/ob3/signer.py +79 -0
- openbadgeslib-1.1.2/openbadgeslib/ob3/verifier.py +196 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/openbadges_init.py +12 -8
- openbadgeslib-1.1.2/openbadgeslib/openbadges_keygenerator.py +117 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/openbadgeslib/openbadges_publish.py +51 -33
- openbadgeslib-1.1.2/openbadgeslib/openbadges_signer.py +216 -0
- openbadgeslib-1.1.2/openbadgeslib/openbadges_verifier.py +197 -0
- openbadgeslib-1.1.2/openbadgeslib/signer.py +4 -0
- openbadgeslib-1.1.2/openbadgeslib/util.py +111 -0
- openbadgeslib-1.1.2/openbadgeslib/verifier.py +4 -0
- openbadgeslib-1.1.2/openbadgeslib.egg-info/PKG-INFO +211 -0
- openbadgeslib-1.1.2/openbadgeslib.egg-info/SOURCES.txt +73 -0
- openbadgeslib-1.1.2/openbadgeslib.egg-info/dependency_links.txt +1 -0
- openbadgeslib-1.1.2/openbadgeslib.egg-info/entry_points.txt +6 -0
- openbadgeslib-1.1.2/openbadgeslib.egg-info/requires.txt +11 -0
- openbadgeslib-1.1.2/openbadgeslib.egg-info/top_level.txt +1 -0
- openbadgeslib-1.1.2/pyproject.toml +79 -0
- openbadgeslib-1.1.2/setup.cfg +11 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/config1.ini +4 -0
- openbadgeslib-1.1.2/tests/conftest.py +216 -0
- openbadgeslib-1.1.2/tests/logo Python Espa/303/261a.svg" +15 -0
- openbadgeslib-1.1.2/tests/runtests.sh +3 -0
- openbadgeslib-1.1.2/tests/test_badge_io.py +140 -0
- openbadgeslib-1.1.2/tests/test_cli_smoke.py +442 -0
- openbadgeslib-1.1.2/tests/test_docs.py +90 -0
- openbadgeslib-1.1.2/tests/test_jws.py +180 -0
- openbadgeslib-1.1.2/tests/test_key_operation.py +229 -0
- openbadgeslib-1.1.2/tests/test_ob3_credential.py +235 -0
- openbadgeslib-1.1.2/tests/test_ob3_signer.py +148 -0
- openbadgeslib-1.1.2/tests/test_ob3_verifier.py +343 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/test_signer_operation.py +186 -66
- openbadgeslib-1.1.2/tests/test_util.py +148 -0
- openbadgeslib-1.1.2/tests/test_verify_operation.py +239 -0
- openbadgeslib-0.4.2/CONTRIBUTORS.txt +0 -5
- openbadgeslib-0.4.2/Changelog.txt +0 -81
- openbadgeslib-0.4.2/PKG-INFO +0 -20
- openbadgeslib-0.4.2/README.txt +0 -2
- openbadgeslib-0.4.2/TODO.txt +0 -27
- openbadgeslib-0.4.2/docs/authors.rst +0 -17
- openbadgeslib-0.4.2/docs/development.rst +0 -19
- openbadgeslib-0.4.2/docs/glossary.rst +0 -113
- openbadgeslib-0.4.2/docs/index.rst +0 -26
- openbadgeslib-0.4.2/docs/installation.rst +0 -148
- openbadgeslib-0.4.2/docs/intro.rst +0 -18
- openbadgeslib-0.4.2/docs/userguide.rst +0 -89
- openbadgeslib-0.4.2/openbadgeslib/3dparty/jws/__init__.py +0 -76
- openbadgeslib-0.4.2/openbadgeslib/3dparty/jws/algos.py +0 -192
- openbadgeslib-0.4.2/openbadgeslib/3dparty/jws/exceptions.py +0 -11
- openbadgeslib-0.4.2/openbadgeslib/3dparty/jws/header.py +0 -67
- openbadgeslib-0.4.2/openbadgeslib/3dparty/jws/utils.py +0 -16
- openbadgeslib-0.4.2/openbadgeslib/openbadges_keygenerator.py +0 -98
- openbadgeslib-0.4.2/openbadgeslib/openbadges_signer.py +0 -150
- openbadgeslib-0.4.2/openbadgeslib/openbadges_verifier.py +0 -104
- openbadgeslib-0.4.2/openbadgeslib/signer.py +0 -200
- openbadgeslib-0.4.2/openbadgeslib/util.py +0 -84
- openbadgeslib-0.4.2/openbadgeslib/verifier.py +0 -161
- openbadgeslib-0.4.2/setup.cfg +0 -2
- openbadgeslib-0.4.2/setup.py +0 -50
- openbadgeslib-0.4.2/tests/runtests.sh +0 -4
- openbadgeslib-0.4.2/tests/test_common.py +0 -7
- openbadgeslib-0.4.2/tests/test_key_operation.py +0 -130
- openbadgeslib-0.4.2/tests/test_verify_operation.py +0 -17
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/LICENSE.txt +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/images/sample1.png +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/images/sample1.svg +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/images/userimage01.svg +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/logo Python Espan/314/203a.svg" +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/test_sign_ecc.pem +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/test_sign_rsa.pem +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/test_verify_ecc.pem +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/test_verify_rsa.pem +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/withoutxmlheader.svg +0 -0
- {openbadgeslib-0.4.2 → openbadgeslib-1.1.2}/tests/withxmlheader.svg +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
OpenBadgesLib - Changelog
|
|
2
|
+
=========================
|
|
3
|
+
|
|
4
|
+
Newest first. Dates are ISO 8601 (YYYY-MM-DD).
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
* v1.1.2 - 2026-06-27
|
|
8
|
+
|
|
9
|
+
- The -d/--debug flag now actually enables DEBUG-level console logging. It was
|
|
10
|
+
previously parsed but ignored in openbadges-signer; it is now wired up and
|
|
11
|
+
also added to openbadges-keygenerator and openbadges-verifier.
|
|
12
|
+
|
|
13
|
+
- Packaging metadata refreshed: README is now Markdown, and the project URLs
|
|
14
|
+
point to the GitHub Wiki (documentation) and the GitHub Pages API reference
|
|
15
|
+
instead of the old homepage.
|
|
16
|
+
|
|
17
|
+
- Docs & infra: the version is single-sourced from util.__version__; CI gates
|
|
18
|
+
catch doc drift (CLI flags, wiki links); an auto-generated pdoc API site and
|
|
19
|
+
an auto-synced wiki were added; Changelog normalised; CONTRIBUTORS.txt
|
|
20
|
+
removed (see docs/authors.rst); pending work is now tracked in GitHub Issues.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
* v1.1.1 - 2026-06-27
|
|
24
|
+
|
|
25
|
+
- OB3: recipient identifiers are normalised through one shared helper, fixing
|
|
26
|
+
a DID being corrupted into 'mailto:did:...'; the verifier CLI now delegates
|
|
27
|
+
recipient binding to OB3Verifier.verify() instead of re-implementing it.
|
|
28
|
+
|
|
29
|
+
- OB3: verify() cross-checks the JWT registered claims against the vc body -
|
|
30
|
+
a token whose 'iss'/'sub' disagree with the credential issuer/subject is
|
|
31
|
+
rejected - and OB3VerificationError now inherits from LibOpenBadgesException
|
|
32
|
+
so one except clause covers both OB2 and OB3.
|
|
33
|
+
|
|
34
|
+
- Refactor: shared keys.alg_for_key_type and CLI config helpers
|
|
35
|
+
(read_config_or_exit / resolve_badge_section / _resolve_trusted_pubkey)
|
|
36
|
+
remove the boilerplate duplicated across the entrypoints; first-party code
|
|
37
|
+
imports openbadgeslib.ob2 directly rather than the back-compat shims; the
|
|
38
|
+
oversized verify/main/_verify_ob3 functions were decomposed.
|
|
39
|
+
|
|
40
|
+
- Type hints added on the OB2/core byte-vs-str boundaries.
|
|
41
|
+
|
|
42
|
+
- Tests: OB2 signer CLI, the mail subsystem and read_from_file edge cases are
|
|
43
|
+
now covered; 258 tests, 92% line coverage; the whole repo (source and
|
|
44
|
+
tests) is flake8-clean.
|
|
45
|
+
|
|
46
|
+
- CI: a GitHub Actions workflow runs flake8 and the test suite on Python
|
|
47
|
+
3.10-3.13 for every push and pull request.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
* v1.1.0 - 2026-06-27
|
|
51
|
+
|
|
52
|
+
- SECURITY: signature verification now pins the accepted algorithm to the
|
|
53
|
+
verification key's type instead of trusting the token header. OB3Verifier
|
|
54
|
+
derives the RS*/ES* family from the key and passes it to jwt.decode;
|
|
55
|
+
_jws.verify_block rejects any header alg that doesn't match the key. This
|
|
56
|
+
blocks cross-type confusion and any none/HMAC downgrade.
|
|
57
|
+
|
|
58
|
+
- SECURITY: SVG badge XML is parsed with defusedxml, defusing entity-expansion
|
|
59
|
+
(billion-laughs) DoS on untrusted badges. New defusedxml dependency.
|
|
60
|
+
|
|
61
|
+
- SECURITY: iTXt PNG token extraction caps zlib decompression at 256 KB to
|
|
62
|
+
stop a decompression-bomb DoS during extraction (before verification).
|
|
63
|
+
|
|
64
|
+
- SECURITY: the OB2 verifier CLI now distinguishes a trusted operator key
|
|
65
|
+
(--local/--pubkey) from the badge's own embedded key. Without a trusted key
|
|
66
|
+
it reports the signature as internally-consistent-only, not "[+] correct"
|
|
67
|
+
(the embedded key proves nothing about issuer identity). --pubkey now
|
|
68
|
+
applies to OB2 as well as OB3.
|
|
69
|
+
|
|
70
|
+
- SECURITY: check_revocation guards the issuer / revocation-list downloads and
|
|
71
|
+
JSON parsing, raising a clean VerifierException instead of a raw error.
|
|
72
|
+
|
|
73
|
+
- Fixed: _jws base64url padding (wrong when the length was a multiple of 4);
|
|
74
|
+
BadgeSigned.get_serial_num crashed on badges read from a file (str vs bytes);
|
|
75
|
+
Verifier.check_identity crashed instead of skipping when no identity was
|
|
76
|
+
given; extract_svg_assertion could raise NameError masking the real error;
|
|
77
|
+
unknown key/image types now fail loudly instead of UnboundLocalError.
|
|
78
|
+
|
|
79
|
+
- Refactor: a single keys.key_to_pem() replaces three drifting copies, and a
|
|
80
|
+
new openbadgeslib.baking module is the one home for the SVG/PNG carrier
|
|
81
|
+
format (OB2 and OB3 share it; the OB2 fixed-offset PNG reader is gone in
|
|
82
|
+
favour of the structured iTXt parser).
|
|
83
|
+
|
|
84
|
+
- Cleanup: removed the redundant setup.py, the stale committed dist/ artifacts
|
|
85
|
+
and generated MANIFEST, and several never-raised exception classes; docs now
|
|
86
|
+
cover OB3 on the landing page and document the SMTP/email-badge feature;
|
|
87
|
+
test import hygiene tidied. 236 tests pass, 87% line coverage.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
* v1.0.2 - 2026-06-18
|
|
91
|
+
|
|
92
|
+
- SECURITY: OB2 verification now uses the operator-supplied trusted key when
|
|
93
|
+
one is given, instead of always trusting the key the badge points to. A
|
|
94
|
+
forged badge can no longer self-describe its own verification key.
|
|
95
|
+
|
|
96
|
+
- SECURITY: download_file now refuses non-HTTPS URLs by default (the verify
|
|
97
|
+
key is the OB2 root of trust); pass allow_insecure=True to override.
|
|
98
|
+
|
|
99
|
+
- Fixed expiration check: a badge is now considered expired when its
|
|
100
|
+
expiration date is in the past relative to *now*, not relative to its own
|
|
101
|
+
issue date (expired badges were previously reported VALID).
|
|
102
|
+
|
|
103
|
+
- Fixed openbadges-publish: it copied no verify key and stopped after the
|
|
104
|
+
first badge (it read a non-existent [keys] section). It now reads each
|
|
105
|
+
badge's public_key and iterates all badge_* sections.
|
|
106
|
+
|
|
107
|
+
- Fixed SMTP: use_ssl is parsed as a boolean (the string 'False' was truthy);
|
|
108
|
+
connection-level mail errors are caught instead of crashing a successful
|
|
109
|
+
sign; the example config uses 'username' (matching the code), not 'login'.
|
|
110
|
+
|
|
111
|
+
- openbadges-keygenerator now honours a key_type (RSA/ECC) field in the badge
|
|
112
|
+
profile, so ECC key pairs are reachable from the CLI.
|
|
113
|
+
|
|
114
|
+
- OB2 sign log and log files are opened in append mode (were truncated on
|
|
115
|
+
every run).
|
|
116
|
+
|
|
117
|
+
- OB3 credentials use the W3C VC 2.0 data model (validFrom/validUntil,
|
|
118
|
+
/ns/credentials/v2 context); from_jwt_payload still reads VC 1.1 fields.
|
|
119
|
+
OB3Verifier.verify() gained an optional expected_recipient argument and now
|
|
120
|
+
asserts the credential type; PNG token extraction parses the iTXt chunk
|
|
121
|
+
structure instead of a fixed offset.
|
|
122
|
+
|
|
123
|
+
- Robustness: unsigned PNGs raise a clear error instead of crashing;
|
|
124
|
+
read_from_file raises instead of calling sys.exit; identity is normalised to
|
|
125
|
+
bytes; revocation handles issuers without a revocationList.
|
|
126
|
+
|
|
127
|
+
- Tests: added CLI/publish/keygenerator/mail/revocation coverage and removed
|
|
128
|
+
the coverage omit list that hid those modules; expiration tests rewritten to
|
|
129
|
+
model real timestamps.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
* v1.0.1 - 2026-04-22
|
|
133
|
+
|
|
134
|
+
- OpenBadges 3.0 support (W3C Verifiable Credentials / JWT-VC):
|
|
135
|
+
new openbadgeslib.ob3 subpackage with OpenBadgeCredential, Issuer,
|
|
136
|
+
Achievement data model; OB3Signer for JWT-VC signing and SVG/PNG
|
|
137
|
+
baking; OB3Verifier for JWT-VC verification and token extraction.
|
|
138
|
+
|
|
139
|
+
- OpenBadges 2.0 implementation moved to openbadgeslib.ob2 subpackage.
|
|
140
|
+
Top-level badge.py / signer.py / verifier.py kept as backward-compatible
|
|
141
|
+
shims so existing code continues to work unchanged.
|
|
142
|
+
|
|
143
|
+
- All CLI tools (openbadges-keygenerator, openbadges-signer,
|
|
144
|
+
openbadges-verifier, openbadges-publish) gained a -V / --ob-version
|
|
145
|
+
flag to select the specification version (2 or 3, default 2).
|
|
146
|
+
|
|
147
|
+
- openbadges-verifier: new -k / --pubkey FILE option to supply the
|
|
148
|
+
public key directly when verifying OB3 badges without a config file.
|
|
149
|
+
|
|
150
|
+
- Python 3.10+ compatibility: removed distutils (dropped in 3.12),
|
|
151
|
+
packaging migrated to pyproject.toml with setuptools.build_meta.
|
|
152
|
+
|
|
153
|
+
- Replaced abandoned pycrypto with pycryptodome >= 3.20.
|
|
154
|
+
|
|
155
|
+
- Replaced custom bundled JWS engine (3dparty/jws/) with PyJWT[crypto]
|
|
156
|
+
algorithm classes (RS256/384/512, ES256/384/512). Internal module
|
|
157
|
+
moved to openbadgeslib/_jws/. Old 3dparty/ directory removed.
|
|
158
|
+
|
|
159
|
+
- TLS security fix: download_file relies on urllib's default TLS context
|
|
160
|
+
(certificate validation on) instead of the deprecated PROTOCOL_TLSv1 /
|
|
161
|
+
CERT_NONE settings.
|
|
162
|
+
|
|
163
|
+
- pypng API update: signature constant renamed, chunk tags now bytes.
|
|
164
|
+
|
|
165
|
+
- Copyright year range updated to 2014-2026 across all source files.
|
|
166
|
+
|
|
167
|
+
- Bug fixes: verifier signature check logic, ECC private-key detection,
|
|
168
|
+
hash_email str/bytes coercion.
|
|
169
|
+
|
|
170
|
+
- Test suite: 203 tests, 89% line coverage.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
* v0.4.2
|
|
174
|
+
|
|
175
|
+
- Adding support to verifying external openbadges.
|
|
176
|
+
|
|
177
|
+
- Adding a new parameter to show assertion before verifying.
|
|
178
|
+
|
|
179
|
+
- OpenBadges URLs are verified before the signing process.
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
* v0.4
|
|
183
|
+
|
|
184
|
+
- Support for PNG. The library can now verify and sign OpenBadges in PNG
|
|
185
|
+
format.
|
|
186
|
+
|
|
187
|
+
- Support for sending badges via mail.
|
|
188
|
+
|
|
189
|
+
- Badge signatures are registered in a log file.
|
|
190
|
+
|
|
191
|
+
- Mail code now supports SMTP auth. The config file needs modifications;
|
|
192
|
+
see the example.
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
* v0.3
|
|
196
|
+
|
|
197
|
+
- The email is no longer encoded in the filename when creating a new badge.
|
|
198
|
+
|
|
199
|
+
- When creating a badge, "-o" is optional.
|
|
200
|
+
|
|
201
|
+
- When specified, "-o" is supposed to be a directory.
|
|
202
|
+
|
|
203
|
+
- We can specify "-E" or "--no-evidence" when creating a new badge.
|
|
204
|
+
|
|
205
|
+
- When creating a new badge we now need to choose EXPLICITLY between
|
|
206
|
+
providing evidence or not.
|
|
207
|
+
|
|
208
|
+
- Badge sections in the INI file must now be prefixed with "badge_".
|
|
209
|
+
UPGRADE: if your badge is called "ponente2014", rename it to
|
|
210
|
+
"badge_ponente2014".
|
|
211
|
+
|
|
212
|
+
- Each badge can now have a different key.
|
|
213
|
+
UPGRADE: move your current key configuration to the badge section,
|
|
214
|
+
renaming "private" to "private_key" and "public" to "public_key".
|
|
215
|
+
UPGRADE: "openbadges-keygenerator -g" now requires a badge name.
|
|
216
|
+
|
|
217
|
+
- "openbadges-verifier" changes its parameter from "-lk" to "-l" and now
|
|
218
|
+
needs the name of the badge to locate its public key.
|
|
219
|
+
|
|
220
|
+
- "openbadges-verifier" now accepts a parameter -x to specify the expiration
|
|
221
|
+
for a badge.
|
|
222
|
+
|
|
223
|
+
- "openbadges-verifier" now checks the revocation status and the expiration
|
|
224
|
+
dates of badges.
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
* v0.2.1 - 2014-12-16
|
|
228
|
+
|
|
229
|
+
- config.ini now allows configuring the badge json url and image url.
|
|
230
|
+
|
|
231
|
+
- Signer now uses randomly salted emails by default in assertions.
|
|
232
|
+
|
|
233
|
+
- Compatibility with more SVG formats.
|
|
234
|
+
|
|
235
|
+
- Documentation in the "docs" directory.
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
* v0.2 - 2014-12-10
|
|
239
|
+
|
|
240
|
+
- "openbadges-init".
|
|
241
|
+
|
|
242
|
+
- New configuration format. If you are using the 0.1 format, you must
|
|
243
|
+
migrate by hand.
|
|
244
|
+
|
|
245
|
+
- Tests.
|
|
246
|
+
|
|
247
|
+
- Use proper logging instead of simple "print" calls.
|
|
248
|
+
|
|
249
|
+
- Massive cleanup of internal imports.
|
|
250
|
+
|
|
251
|
+
- Do not change "sys.path".
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
* v0.1 - 2014-12-01
|
|
255
|
+
|
|
256
|
+
- Initial release.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openbadgeslib
|
|
3
|
+
Version: 1.1.2
|
|
4
|
+
Summary: A library to sign and verify OpenBadges
|
|
5
|
+
Author-email: Luis González Fernández <luisgf@luisgf.es>, Jesús Cea Avión <jcea@jcea.es>
|
|
6
|
+
License: LGPLv3
|
|
7
|
+
Project-URL: Homepage, https://github.com/luisgf/openbadgeslib
|
|
8
|
+
Project-URL: Documentation, https://github.com/luisgf/openbadgeslib/wiki
|
|
9
|
+
Project-URL: API Reference, https://luisgf.github.io/openbadgeslib/
|
|
10
|
+
Project-URL: Source, https://github.com/luisgf/openbadgeslib
|
|
11
|
+
Project-URL: Changelog, https://github.com/luisgf/openbadgeslib/blob/master/Changelog.txt
|
|
12
|
+
Keywords: openbadges
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Natural Language :: English
|
|
21
|
+
Classifier: Natural Language :: Spanish
|
|
22
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE.txt
|
|
26
|
+
Requires-Dist: pycryptodome>=3.20
|
|
27
|
+
Requires-Dist: ecdsa>=0.19
|
|
28
|
+
Requires-Dist: pypng>=0.20220715.0
|
|
29
|
+
Requires-Dist: PyJWT[crypto]>=2.8
|
|
30
|
+
Requires-Dist: defusedxml>=0.7
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
34
|
+
Requires-Dist: flake8>=7.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pdoc>=14; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# OpenBadgesLib
|
|
39
|
+
|
|
40
|
+
[](https://github.com/luisgf/openbadgeslib/actions/workflows/ci.yml)
|
|
41
|
+
[](https://pypi.org/project/openbadgeslib/)
|
|
42
|
+
[](https://github.com/luisgf/openbadgeslib/actions/workflows/ci.yml)
|
|
43
|
+
[](#license)
|
|
44
|
+
|
|
45
|
+
A Python library and CLI for signing and verifying
|
|
46
|
+
[Open Badges](https://www.imsglobal.org/activity/digital-badges) embedded in SVG
|
|
47
|
+
and PNG image files. It supports both **OpenBadges 2.0** (JWS compact
|
|
48
|
+
serialisation) and **OpenBadges 3.0** (W3C Verifiable Credentials / JWT-VC).
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- Sign badge images (SVG and PNG) with a JWS assertion (OB 2.0)
|
|
53
|
+
- Issue and verify OpenBadges 3.0 JWT-VC credentials
|
|
54
|
+
- Bake OB 3.0 JWT tokens into SVG and PNG badge images
|
|
55
|
+
- RSA 2048-bit (RS256) and ECC NIST P-256 (ES256) key support
|
|
56
|
+
- SHA-256 hashed recipient identity with salt (OB 2.0)
|
|
57
|
+
- Expiration and revocation checking
|
|
58
|
+
- Five command-line tools included
|
|
59
|
+
|
|
60
|
+
## Requirements
|
|
61
|
+
|
|
62
|
+
- Python >= 3.10 (tested on 3.10–3.13)
|
|
63
|
+
- [pycryptodome](https://pypi.org/project/pycryptodome/) >= 3.20
|
|
64
|
+
- [ecdsa](https://pypi.org/project/ecdsa/) >= 0.19
|
|
65
|
+
- [pypng](https://pypi.org/project/pypng/) >= 0.20220715.0
|
|
66
|
+
- [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.8
|
|
67
|
+
- [defusedxml](https://pypi.org/project/defusedxml/) >= 0.7
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install openbadgeslib
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
All dependencies are installed automatically. For a development checkout with
|
|
76
|
+
the test suite and linters:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install -e ".[dev]"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# 1. Initialize a configuration directory
|
|
86
|
+
openbadges-init ./config/
|
|
87
|
+
|
|
88
|
+
# 2. Generate a key pair for a badge
|
|
89
|
+
openbadges-keygenerator -c ./config/config.ini -g 1
|
|
90
|
+
|
|
91
|
+
# 3a. Sign a badge — OpenBadges 2.0 (default)
|
|
92
|
+
openbadges-signer -c ./config/config.ini -b 1 -r recipient@example.com -o /tmp/ -E
|
|
93
|
+
|
|
94
|
+
# 3b. Sign a badge — OpenBadges 3.0
|
|
95
|
+
openbadges-signer -c ./config/config.ini -b 1 -r recipient@example.com -o /tmp/ -E -V 3
|
|
96
|
+
|
|
97
|
+
# 4a. Verify — OpenBadges 2.0 (pin a trusted key with -l/--local or -k/--pubkey)
|
|
98
|
+
openbadges-verifier -i /tmp/badge_1_recipient@example.com.svg \
|
|
99
|
+
-r recipient@example.com -l 1
|
|
100
|
+
|
|
101
|
+
# 4b. Verify — OpenBadges 3.0
|
|
102
|
+
openbadges-verifier -i /tmp/badge_1_recipient@example.com.svg \
|
|
103
|
+
-r recipient@example.com -V 3 -k ./config/keys/verify_rsa_key_1.pem
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
See the [Quick Start](https://github.com/luisgf/openbadgeslib/wiki/Quick-Start)
|
|
107
|
+
and [CLI Reference](https://github.com/luisgf/openbadgeslib/wiki/CLI-Reference)
|
|
108
|
+
wiki pages for the full walkthrough and every flag.
|
|
109
|
+
|
|
110
|
+
## Using the library — OpenBadges 2.0
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from openbadgeslib.ob2 import Badge, BadgeImgType, BadgeType, Signer
|
|
114
|
+
from openbadgeslib.keys import KeyType
|
|
115
|
+
|
|
116
|
+
with open('sign.pem', 'rb') as f:
|
|
117
|
+
priv_pem = f.read()
|
|
118
|
+
with open('verify.pem', 'rb') as f:
|
|
119
|
+
pub_pem = f.read()
|
|
120
|
+
with open('badge.svg', 'rb') as f:
|
|
121
|
+
image = f.read()
|
|
122
|
+
|
|
123
|
+
badge = Badge(
|
|
124
|
+
ini_name='my_badge',
|
|
125
|
+
name='My Badge',
|
|
126
|
+
description='Awarded for excellence',
|
|
127
|
+
image_type=BadgeImgType.SVG,
|
|
128
|
+
image=image,
|
|
129
|
+
image_url='https://example.com/badge.svg',
|
|
130
|
+
criteria_url='https://example.com/criteria.html',
|
|
131
|
+
json_url='https://example.com/badge.json',
|
|
132
|
+
verify_key_url='https://example.com/verify.pem',
|
|
133
|
+
key_type=KeyType.RSA,
|
|
134
|
+
privkey_pem=priv_pem,
|
|
135
|
+
pubkey_pem=pub_pem,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
signer = Signer(identity='recipient@example.com', badge_type=BadgeType.SIGNED)
|
|
139
|
+
signed = signer.sign_badge(badge)
|
|
140
|
+
signed.save_to_file('/tmp/signed_badge.svg')
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Using the library — OpenBadges 3.0 (JWT-VC)
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from openbadgeslib.ob3 import (
|
|
147
|
+
Issuer, Achievement, OpenBadgeCredential, OB3Signer, OB3Verifier,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
issuer = Issuer(id='https://example.com/issuer', name='Example Org')
|
|
151
|
+
achievement = Achievement(
|
|
152
|
+
id='https://example.com/achievements/python',
|
|
153
|
+
name='Python Developer',
|
|
154
|
+
description='Awarded for Python proficiency',
|
|
155
|
+
criteria_narrative='Must pass the Python assessment',
|
|
156
|
+
)
|
|
157
|
+
credential = OpenBadgeCredential(
|
|
158
|
+
issuer=issuer,
|
|
159
|
+
recipient_id='mailto:recipient@example.com',
|
|
160
|
+
achievement=achievement,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
with open('sign.pem', 'rb') as f:
|
|
164
|
+
priv_pem = f.read()
|
|
165
|
+
signer = OB3Signer(privkey_pem=priv_pem, algorithm='RS256')
|
|
166
|
+
|
|
167
|
+
# Bake the signed JWT-VC into a badge image
|
|
168
|
+
with open('badge.svg', 'rb') as f:
|
|
169
|
+
baked_svg = signer.sign_into_svg(credential, f.read())
|
|
170
|
+
|
|
171
|
+
# Verify
|
|
172
|
+
with open('verify.pem', 'rb') as f:
|
|
173
|
+
verifier = OB3Verifier(pubkey_pem=f.read())
|
|
174
|
+
token = OB3Verifier.extract_token_from_svg(baked_svg)
|
|
175
|
+
restored = verifier.verify(token, expected_recipient='recipient@example.com')
|
|
176
|
+
print('Recipient:', restored.recipient_id)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Documentation
|
|
180
|
+
|
|
181
|
+
- **User & developer guide** — the project [**Wiki**](https://github.com/luisgf/openbadgeslib/wiki):
|
|
182
|
+
installation, configuration, concepts, the security model, CLI reference and
|
|
183
|
+
how-to guides.
|
|
184
|
+
- **API reference** — generated from the docstrings and published at
|
|
185
|
+
[**luisgf.github.io/openbadgeslib**](https://luisgf.github.io/openbadgeslib/).
|
|
186
|
+
|
|
187
|
+
## Running the test suite
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pytest
|
|
191
|
+
pytest --cov=openbadgeslib # with coverage report
|
|
192
|
+
flake8 openbadgeslib tests # lint
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Changelog
|
|
196
|
+
|
|
197
|
+
See [`Changelog.txt`](Changelog.txt) for the full history, and the
|
|
198
|
+
[GitHub Releases](https://github.com/luisgf/openbadgeslib/releases) page for
|
|
199
|
+
release notes.
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
The **library** (`openbadgeslib/` package) is licensed under the
|
|
204
|
+
[GNU Lesser General Public License v3](https://opensource.org/licenses/lgpl-3.0.html)
|
|
205
|
+
(LGPLv3). The **command-line wrapper tools** are licensed under the
|
|
206
|
+
[BSD 2-Clause](https://opensource.org/licenses/BSD-2-Clause) license.
|
|
207
|
+
|
|
208
|
+
## Authors
|
|
209
|
+
|
|
210
|
+
- Luis González Fernández <luisgf@luisgf.es>
|
|
211
|
+
- Jesús Cea Avión <jcea@jcea.es>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# OpenBadgesLib
|
|
2
|
+
|
|
3
|
+
[](https://github.com/luisgf/openbadgeslib/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/openbadgeslib/)
|
|
5
|
+
[](https://github.com/luisgf/openbadgeslib/actions/workflows/ci.yml)
|
|
6
|
+
[](#license)
|
|
7
|
+
|
|
8
|
+
A Python library and CLI for signing and verifying
|
|
9
|
+
[Open Badges](https://www.imsglobal.org/activity/digital-badges) embedded in SVG
|
|
10
|
+
and PNG image files. It supports both **OpenBadges 2.0** (JWS compact
|
|
11
|
+
serialisation) and **OpenBadges 3.0** (W3C Verifiable Credentials / JWT-VC).
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Sign badge images (SVG and PNG) with a JWS assertion (OB 2.0)
|
|
16
|
+
- Issue and verify OpenBadges 3.0 JWT-VC credentials
|
|
17
|
+
- Bake OB 3.0 JWT tokens into SVG and PNG badge images
|
|
18
|
+
- RSA 2048-bit (RS256) and ECC NIST P-256 (ES256) key support
|
|
19
|
+
- SHA-256 hashed recipient identity with salt (OB 2.0)
|
|
20
|
+
- Expiration and revocation checking
|
|
21
|
+
- Five command-line tools included
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Python >= 3.10 (tested on 3.10–3.13)
|
|
26
|
+
- [pycryptodome](https://pypi.org/project/pycryptodome/) >= 3.20
|
|
27
|
+
- [ecdsa](https://pypi.org/project/ecdsa/) >= 0.19
|
|
28
|
+
- [pypng](https://pypi.org/project/pypng/) >= 0.20220715.0
|
|
29
|
+
- [PyJWT[crypto]](https://pypi.org/project/PyJWT/) >= 2.8
|
|
30
|
+
- [defusedxml](https://pypi.org/project/defusedxml/) >= 0.7
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install openbadgeslib
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
All dependencies are installed automatically. For a development checkout with
|
|
39
|
+
the test suite and linters:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e ".[dev]"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 1. Initialize a configuration directory
|
|
49
|
+
openbadges-init ./config/
|
|
50
|
+
|
|
51
|
+
# 2. Generate a key pair for a badge
|
|
52
|
+
openbadges-keygenerator -c ./config/config.ini -g 1
|
|
53
|
+
|
|
54
|
+
# 3a. Sign a badge — OpenBadges 2.0 (default)
|
|
55
|
+
openbadges-signer -c ./config/config.ini -b 1 -r recipient@example.com -o /tmp/ -E
|
|
56
|
+
|
|
57
|
+
# 3b. Sign a badge — OpenBadges 3.0
|
|
58
|
+
openbadges-signer -c ./config/config.ini -b 1 -r recipient@example.com -o /tmp/ -E -V 3
|
|
59
|
+
|
|
60
|
+
# 4a. Verify — OpenBadges 2.0 (pin a trusted key with -l/--local or -k/--pubkey)
|
|
61
|
+
openbadges-verifier -i /tmp/badge_1_recipient@example.com.svg \
|
|
62
|
+
-r recipient@example.com -l 1
|
|
63
|
+
|
|
64
|
+
# 4b. Verify — OpenBadges 3.0
|
|
65
|
+
openbadges-verifier -i /tmp/badge_1_recipient@example.com.svg \
|
|
66
|
+
-r recipient@example.com -V 3 -k ./config/keys/verify_rsa_key_1.pem
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
See the [Quick Start](https://github.com/luisgf/openbadgeslib/wiki/Quick-Start)
|
|
70
|
+
and [CLI Reference](https://github.com/luisgf/openbadgeslib/wiki/CLI-Reference)
|
|
71
|
+
wiki pages for the full walkthrough and every flag.
|
|
72
|
+
|
|
73
|
+
## Using the library — OpenBadges 2.0
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from openbadgeslib.ob2 import Badge, BadgeImgType, BadgeType, Signer
|
|
77
|
+
from openbadgeslib.keys import KeyType
|
|
78
|
+
|
|
79
|
+
with open('sign.pem', 'rb') as f:
|
|
80
|
+
priv_pem = f.read()
|
|
81
|
+
with open('verify.pem', 'rb') as f:
|
|
82
|
+
pub_pem = f.read()
|
|
83
|
+
with open('badge.svg', 'rb') as f:
|
|
84
|
+
image = f.read()
|
|
85
|
+
|
|
86
|
+
badge = Badge(
|
|
87
|
+
ini_name='my_badge',
|
|
88
|
+
name='My Badge',
|
|
89
|
+
description='Awarded for excellence',
|
|
90
|
+
image_type=BadgeImgType.SVG,
|
|
91
|
+
image=image,
|
|
92
|
+
image_url='https://example.com/badge.svg',
|
|
93
|
+
criteria_url='https://example.com/criteria.html',
|
|
94
|
+
json_url='https://example.com/badge.json',
|
|
95
|
+
verify_key_url='https://example.com/verify.pem',
|
|
96
|
+
key_type=KeyType.RSA,
|
|
97
|
+
privkey_pem=priv_pem,
|
|
98
|
+
pubkey_pem=pub_pem,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
signer = Signer(identity='recipient@example.com', badge_type=BadgeType.SIGNED)
|
|
102
|
+
signed = signer.sign_badge(badge)
|
|
103
|
+
signed.save_to_file('/tmp/signed_badge.svg')
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Using the library — OpenBadges 3.0 (JWT-VC)
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from openbadgeslib.ob3 import (
|
|
110
|
+
Issuer, Achievement, OpenBadgeCredential, OB3Signer, OB3Verifier,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
issuer = Issuer(id='https://example.com/issuer', name='Example Org')
|
|
114
|
+
achievement = Achievement(
|
|
115
|
+
id='https://example.com/achievements/python',
|
|
116
|
+
name='Python Developer',
|
|
117
|
+
description='Awarded for Python proficiency',
|
|
118
|
+
criteria_narrative='Must pass the Python assessment',
|
|
119
|
+
)
|
|
120
|
+
credential = OpenBadgeCredential(
|
|
121
|
+
issuer=issuer,
|
|
122
|
+
recipient_id='mailto:recipient@example.com',
|
|
123
|
+
achievement=achievement,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
with open('sign.pem', 'rb') as f:
|
|
127
|
+
priv_pem = f.read()
|
|
128
|
+
signer = OB3Signer(privkey_pem=priv_pem, algorithm='RS256')
|
|
129
|
+
|
|
130
|
+
# Bake the signed JWT-VC into a badge image
|
|
131
|
+
with open('badge.svg', 'rb') as f:
|
|
132
|
+
baked_svg = signer.sign_into_svg(credential, f.read())
|
|
133
|
+
|
|
134
|
+
# Verify
|
|
135
|
+
with open('verify.pem', 'rb') as f:
|
|
136
|
+
verifier = OB3Verifier(pubkey_pem=f.read())
|
|
137
|
+
token = OB3Verifier.extract_token_from_svg(baked_svg)
|
|
138
|
+
restored = verifier.verify(token, expected_recipient='recipient@example.com')
|
|
139
|
+
print('Recipient:', restored.recipient_id)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Documentation
|
|
143
|
+
|
|
144
|
+
- **User & developer guide** — the project [**Wiki**](https://github.com/luisgf/openbadgeslib/wiki):
|
|
145
|
+
installation, configuration, concepts, the security model, CLI reference and
|
|
146
|
+
how-to guides.
|
|
147
|
+
- **API reference** — generated from the docstrings and published at
|
|
148
|
+
[**luisgf.github.io/openbadgeslib**](https://luisgf.github.io/openbadgeslib/).
|
|
149
|
+
|
|
150
|
+
## Running the test suite
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pytest
|
|
154
|
+
pytest --cov=openbadgeslib # with coverage report
|
|
155
|
+
flake8 openbadgeslib tests # lint
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Changelog
|
|
159
|
+
|
|
160
|
+
See [`Changelog.txt`](Changelog.txt) for the full history, and the
|
|
161
|
+
[GitHub Releases](https://github.com/luisgf/openbadgeslib/releases) page for
|
|
162
|
+
release notes.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
The **library** (`openbadgeslib/` package) is licensed under the
|
|
167
|
+
[GNU Lesser General Public License v3](https://opensource.org/licenses/lgpl-3.0.html)
|
|
168
|
+
(LGPLv3). The **command-line wrapper tools** are licensed under the
|
|
169
|
+
[BSD 2-Clause](https://opensource.org/licenses/BSD-2-Clause) license.
|
|
170
|
+
|
|
171
|
+
## Authors
|
|
172
|
+
|
|
173
|
+
- Luis González Fernández <luisgf@luisgf.es>
|
|
174
|
+
- Jesús Cea Avión <jcea@jcea.es>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Authors
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Contributors
|
|
5
|
+
------------
|
|
6
|
+
|
|
7
|
+
Sorted alphabetically:
|
|
8
|
+
|
|
9
|
+
* Jesús Cea Avión <jcea@jcea.es>
|
|
10
|
+
* Luis González Fernández <luisgf@luisgf.es>
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
License
|
|
14
|
+
-------
|
|
15
|
+
|
|
16
|
+
The **library** (``openbadgeslib/`` package) is licensed under the terms of
|
|
17
|
+
the :term:`LGPL3`.
|
|
18
|
+
|
|
19
|
+
The **command-line wrapper tools** (``openbadges-init``,
|
|
20
|
+
``openbadges-keygenerator``, ``openbadges-signer``, ``openbadges-verifier``,
|
|
21
|
+
``openbadges-publish``) are licensed under the :term:`BSD 2-Clause` licence.
|