Releases: luisgf/openbadgeslib
Release list
v3.3.0
Feature release: issuance of W3C Data Integrity (Linked Data Proof) credentials — the signing counterpart to the verification shipped in v3.2.0. Open Badges 3.0 now issues and verifies in both proof formats the spec allows — compact VC-JWT (JOSE) and embedded Data Integrity — from one offline library. Data Integrity stays opt-in via the [ldp] extra, which now covers both directions:
pip install "openbadgeslib[ldp]"Added
feat(ob3):OB3LdpSignerissues OB 3.0 credentials carrying an embedded Data Integrity proof, cryptosuiteeddsa-rdfc-2022— the signing counterpart of the 3.2.0 verifier, sharing its canonicalization core and reproducing the official W3C vc-di-eddsa test vector byte for byte. The low-level core is exposed asadd_data_integrity_prooffor non-OB3 VCs. Ed25519 keys only; requires the[ldp]extra, which now covers signing and verification.feat(cli):openbadges-signer -P/--proof-format {vc-jwt,ldp}(OB 3.0 only), plus an optional per-badgeproof_formatconfig key (the flag wins; default staysvc-jwt). With[issuer] didconfigured, the proof names thedid:web:…#badge_NverificationMethod thatopenbadges-publish -V 3publishes (trusted on verify via--resolve-did); otherwise it falls back to a self-asserteddid:keyderived from the signing key and tells the operator so. LDP-signed badges are taggedPROOF ldpin the signer audit log. Status lists stay VC-JWT regardless of the badge's proof format.feat(ob3): multikey / did:key encoders inob3.did—multikey_from_pem(publicKeyMultibase) anddid_key_from_pem, the exact inverses of the resolvers, for Ed25519 and NIST P-256.
Fixed
fix(cli):Badge.create_from_confrejectedkey_type = ED25519outright, so the CLI could never sign with the keysopenbadges-keygenerator -t ED25519produces. Ed25519 key material now loads, and the legacy OB 1.0 path (-V 1) refuses non-RSA/ECC keys with a clean message instead of failing mid-JWS.
Not included (deferred): the ecdsa-sd-2023 selective-disclosure cryptosuite — no Python library in the ecosystem implements it yet; it fails closed with a clear "unsupported cryptosuite" message, and the cryptosuite registry keeps it an additive future module.
Everything still works without the extra: the base install is unchanged, LDP tests skip cleanly, and attempting LDP signing/verification reports pip install openbadgeslib[ldp].
Verified with flake8, mypy (strict) and the full test suite (805 passed with the [ldp] extra) locally, and the CI matrix on Python 3.10–3.13, which gated the automatic PyPI publish.
Full history: Changelog.txt
v3.2.0
Feature release: verification of W3C Data Integrity (Linked Data Proof) credentials — the second proof format Open Badges 3.0 allows, alongside the VC-JWT stack this library already ships. Verify-only by design (issuing stays JWT-VC), and fully opt-in via a new packaging extra:
pip install "openbadgeslib[ldp]"Added
feat(ob3):OB3LdpVerifierverifies OB 3.0 credentials secured with an embedded Data Integrity proof, cryptosuiteeddsa-rdfc-2022(W3C Recommendation vc-di-eddsa) — validated byte-for-byte against the official W3C test vectors, which ship as fixtures. The low-level crypto core is exposed asverify_data_integrity_prooffor non-OB3 VCs. Same API, trust model and exceptions as the JWT verifier: pinned key wins; otherwise the key resolves fromproof.verificationMethodwith fail-closed binding to the credential's DID issuer;expected_recipient/check_statusbehave identically.feat(ob3): JSON-LD@contextdocuments are never fetched from the network — the exact context files (VC 2.0, the published OB v3p0 revisions,data-integrity/v2,multikey/v1) ship pinned inside the wheel with recorded provenance, behind an exact-match allowlist (unknown context ⇒ fail closed). Documents are capped at 256 KiB before canonicalization (poison-graph guard).feat(ob3):resolve_verification_methodresolves the exact verificationMethod URL a proof names (did:key fragment validation; did:web entry selected byid, no silent[0]fallback).feat(baking): the OB 3.0 §5.3 text-content carrier for Data Integrity credentials —bake_svg(..., as_text=True)/extract_svg(..., text_fallback=True); PNG needed no change.feat(cli):openbadges-verifier -V 3autodetects the baked payload (compact JWT vs credential JSON) with the same trust flags — no new options — and reportsproof_format("vc-jwt"/"ldp") in--json. Without the extra, the failure reason carries the install hint.feat(api):OpenBadgeCredential.from_vc_documentreconstructs a credential from its JSON-LD document shape.
Not included (deferred): the ecdsa-sd-2023 selective-disclosure cryptosuite — it fails closed with a clear "unsupported cryptosuite" message; the cryptosuite registry makes it an additive future module.
Everything works without the extra: the base install is unchanged, LDP tests skip cleanly, and attempting an LDP verification reports pip install openbadgeslib[ldp].
Verified with flake8, mypy (strict), the full test suite (760 with the extra; 726 + 27 skipped without), a real-CLI end-to-end run (pinned key trusted / did:key untrusted / tampered rejected), and a clean-venv wheel install check, on Python 3.10–3.13.
Full history: Changelog.txt
v3.1.1
Discoverability release — no functional change (the library is identical to 3.1.0).
Packaging & docs
- A descriptive PyPI summary that actually says what the library does (full OB 3.0 issuer lifecycle: issue VC-JWT, verify, and revoke/suspend with Bitstring Status Lists and did:web).
- Expanded keywords and trove classifiers (
verifiable-credentials,vc-jwt,did-web,bitstring-status-list,revocation, Education, Cryptography) so the package is findable. - README: a positioning line, a "Why openbadgeslib" section, and an honest OB 3.0 Python comparison table (best-effort, dated, corrections welcome).
Verified with twine check on a real sdist + wheel build, plus flake8, mypy and the full test suite (701 passed) on Python 3.10–3.13.
Full history: Changelog.txt
v3.1.0
Feature release: the OpenBadges 3.0 issuer-side lifecycle — issue → publish trust artefacts → revoke/suspend. All new behaviour is opt-in; existing configs sign and publish exactly as in 3.0.x.
Added
feat(ob3): issuer-side credential status. Badges that opt in withstatus_lists = revocation, suspensionget W3C Bitstring Status List entries attached to every newly signed credential (openbadges-signer -V 3), with the index recorded in a private per-badge registry (${base}/status/badge_N.json— random allocation per the spec's privacy recommendation, atomic writes). (#131, #132)feat(publish):openbadges-publish -V 3is no longer a no-op. It generates the issuer's did:web document (did.json) and, per opted-in badge, the signed status list credentials (revocation.jwt,suspension.jwt, signed with the badge key) plusverify.pem. New management flags--revoke ID,--suspend ID,--unsuspend ID(ID = jti or recipient email;-bscopes,--reasonannotates) update the registry and regenerate the lists. Revocation is permanent; the-V 3output directory may already exist and its files are replaced atomically.feat(config): new opt-in keys —[paths] base_status,[issuer] did(autoderives a did:web issuer id frompublish_url), and per-badgestatus_lists/status_size_bits/status_base.openbadges-initnow also createsstatus/.feat(api): new public API —openbadgeslib.ob3.status_list(encode_bitstring,build_status_list_credential,sign_status_list_credential,status_entry),openbadgeslib.ob3.StatusRegistry,did_web_from_url/build_did_document,openbadgeslib.keys.public_jwk_from_pem,OB3Signer.sign_payload. The OB3 signer path now also appends to the signer log, including the credential jti and status index.
Documentation (not part of the packaged distribution)
- New publish-and-revoke walkthrough in Guides; updated Configuration, CLI-Reference, Quick-Start, OB2-vs-OB3, Security-Model (registry privacy notes and the
verificationMethod[0]resolver limitation), Python-API-OB3 and Glossary.
Full history: Changelog.txt
v3.0.1
Patch release.
Fixed
fix(cli): the OpenBadges 1.0 signer (-V 1) now writes the signed badge before appending to the audit log, and guards the log write, so a missing or unwritablebase_logreports a clean error instead of crashing with a rawOSErrorand losing the already-signed badge (matches the OB2 path). (#127)
Documentation (not part of the packaged distribution)
- Corrected OB1/OB2/OB3-relabel and native VC-JWT drift across the wiki: CLI-Reference, Configuration, Security-Model, Python-API-OB3, Glossary, Guides, Contributing. (#122–#126, #128–#130)
Full history: Changelog.txt
v3.0.0
Strict Open Badges 2.0 — and an honest version split.
Highlights
- Strict Open Badges 2.0 (
-V 2, newopenbadgeslib.ob2): conformant JSON-LD assertions (@context,type, an IRIid, a booleanhashed, ISO 8601 dates, averificationobject),OB2Signer/OB2Verifier, real HostedBadge verification (HTTPS fetch of the assertionidwith an issuer-origin scope check), and SignedBadgeverification.creator→CryptographicKeyresolution with anowner/publicKeyback-link check. - Conformant publishing (
openbadges-publish -V 2): an issuerProfilewith apublicKeyarray, aBadgeClassand aCryptographicKey(key.json) per badge, and aRevocationList. - Legacy relabelled: the pre-2.0 format previously shipped as
-V 2is now OpenBadges 1.0 (-V 1,openbadgeslib.ob1), frozen unchanged.
Breaking changes
- The default
-Vis now 3 (was2) acrossopenbadges-signer/-verifier/-publish/-keygenerator. from openbadgeslib.ob2 import Signer/Verifier/Badgeno longer resolves — import fromopenbadgeslib.ob1(the top-levelopenbadgeslib.signer/verifier/badgeshims are unchanged).
See the Changelog for the full list.
openbadgeslib 2.0.0
Major release: the OpenBadges 3.0 path is now natively conformant to the spec (§8.2 VC-JWT and document formats), from the conformance audit. Breaking for OB3 consumers. OpenBadges 2.0 is unchanged.
⚠️ Breaking changes (OB3 only)
- Native VC-JWT serialization (§8.2). The JWT payload is the credential (its members at the top level — no
vcclaim wrapper),validFrommaps tonbf(there is noiat), and the JOSE header carries the issuer's public key as ajwk. Tokens issued by 1.x (the VCDM-1.1-stylevc-wrapper) are not compatible. - OB 3.0 baking identifiers. PNG
iTXtkeywordopenbadgecredentialand SVG<openbadges:credential>(namespacehttps://purl.imsglobal.org/ob/v3p0), replacing the OB 2.0openbadges/<openbadges:assertion>. OB3 images baked by 1.x are not read by this verifier.
Conformance improvements
- Verifier accepts spec-valid credentials it used to reject: the
AchievementCredentialtype alias, anissuergiven as a string IRI, and acredentialSubjectwithout anid(identity viaidentifier). - Verifier enforces required structure it used to ignore: the
@context(VC 2.0 + OB v3p0 pair) is validated, and theiss/nbfregistered claims are required (subrequired when the subject has an id). credentialStatushonorsstatusPurpose.suspensionis reported distinctly fromrevocation, a non-revocation/suspension purpose (e.g.message) no longer fails verification, and the entry's purpose is cross-checked against the fetched status list.
Migration
Re-issue OB3 badges with 2.0.0 to get native tokens/images. OB2 signing and verification are unaffected — no changes needed.
Tracked in #116 (issues #111–#115). See Changelog.txt.
openbadgeslib 1.3.0
Security-hardening release on top of the v1.2.0 OpenBadges 3.0 / DID feature set. Mostly a drop-in upgrade — see the one behaviour change below.
⚠️ Behaviour change
openbadges-verifier --json now sets its exit status from issuer trust, not just signature validity:
0— valid and issuer-trusted2— signature valid but the issuer is not anchored (an OB2 badge-embedded key, or a self-asserteddid:key)1— any failure
In v1.2.0 a valid-but-untrusted badge exited 0, so a CI gate keying on the exit code could accept a signature that does not prove issuer identity. The JSON body (valid / trusted / reason) is unchanged. Update any automation that gated on exit 0.
Security
-
SSRF protection in
download_file. The URLs it fetches are attacker-influenced (an OB2 badge/issuer/revocationListURL, an OB3did:webhost, an OB3credentialStatuslist), so it now resolves the destination host and refuses any private, loopback, link-local, reserved, multicast, unspecified or carrier-grade-NAT (100.64.0.0/10) address; the check is re-applied to redirect targets. Previously a crafted badge could steer a verifier into GETting cloud-metadata endpoints or internal hosts. Anallow_private=Trueopt-out is available for private deployments. -
OB3 issuer binding for DID-anchored verifiers.
OB3Verifier.for_issuer_did()now requires the credential's own issuer id to equal the DID whose key was resolved.did:webis not self-certifying, so without this a credential signed by the resolved key could claim a different (trusted) issuer and still verify. Verifiers built directly from a public key are unchanged (the caller vouches for the key). -
--jsonexit status reflects issuer trust (see the behaviour change above), so machine consumers cannot gate on a valid-but-untrusted signature. -
A resolved
did:keyis reported untrusted. With--resolve-did, a self-asserteddid:keyissuer is nowtrusted: false(with a warning), mirroring OB2's badge-embedded-key case; only an operator-supplied key or a DNS/TLS-anchoreddid:webis trusted.
Fixes
-
OB3 Bitstring Status List
statusSize > 1now fails closed. The bit reader assumed one bit per entry; a multi-bit list would have read the wrong bit and could report a revoked credential as valid. An unsupportedstatusSizeis now rejected rather than misread. -
confparserresolves a relative[paths] baseagainst the config-file directory (not the process CWD). Previously only a bare.was handled and its value replaced wholesale, silently dropping the suffix of./dataor../sharedand misplacing key/log/image trees. A$in the config directory path is now handled correctly.
Docs
- Ed25519 (EdDSA) support documented across the README and wiki (key types, algorithms,
openbadges-keygeneratorkey_type, the shipped config example); the--jsonexit-status scheme documented; and stale crypto-library / dependency notes corrected.
See Changelog.txt for the full list.
openbadgeslib 1.2.0
Minor release adding four OpenBadges 3.0 / cryptography features on top of the v1.1.6 security & correctness baseline. No breaking changes; a drop-in upgrade. cryptography is now an explicit dependency (it was already pulled in transitively by PyJWT[crypto]).
New features
-
Ed25519 (EdDSA) keys, end to end. Key generation (
key_type = ED25519in the badge profile), OB2 JWS and OB3 JWT-VC signing and verification.detect_key_typeclassifies an Ed25519 PEM explicitly — theecdsalibrary would otherwise misread it as a NIST/ECC key — and the algorithm-pinning allowlists bindEdDSAto Ed25519 keys, so cross-type tokens are still rejected. (#103) -
OB3 credential revocation via
credentialStatus. The OB3 counterpart of OB2's revocation control.OB3Verifier.verify(check_status=True)(andopenbadges-verifier --check-status) resolves each status entry — W3C Bitstring Status List v1.0 and the legacy StatusList2021 — fetches the status list over HTTPS, decodes the multibase base64url + GZIP bitstring under a bounded inflate, and rejects a set revocation/suspension bit. Fail-closed when enabled; verifies the published status bit only, not the status-list credential's own signature (documented). (#104) -
DID resolution for OB3 issuer identity. New
ob3.resolve_did()andOB3Verifier.for_issuer_did()supportdid:key(Ed25519 and P-256, self-certifying, offline) anddid:web(fetches the DID document over HTTPS and reads its first verification method'spublicKeyJwkorpublicKeyMultibase).openbadges-verifier --resolve-didreads the issuer DID from the token and resolves it when no key is supplied.did:keyneeds no external trust;did:webtrusts the host's DNS + TLS (documented in the Security Model). (#105) -
--jsonverifier output.openbadges-verifier --jsonemits a single machine-readable JSON result (valid,ob_version,recipient,reason, plus OB2trusted/statusor OB3issuer/achievement/…) instead of the human lines, and exits 0 when valid / non-zero otherwise — usable in CI and services without scraping stdout. The default human output and its exit codes are unchanged. (#106)
See Changelog.txt for the full list.
openbadgeslib 1.1.6
Patch release closing the security & correctness audit backlog accumulated since v1.1.5 — 49 fixes across OB2/OB3 verification, the JWS engine, config parsing, and the CLI entrypoints. No API changes, no new dependencies; a drop-in upgrade.
Security
- OB2 revocation no longer fails open on a falsy reason. A revoked serial whose issuer-published reason was
"",null,false, or0was reported VALID instead of REVOKED. A matched serial is now always honored as revoked. - JWS: a private key supplied where a public verify key is expected is treated as a failed signature (
SignatureError) instead of crashing with a rawAttributeError— closes a remotely-triggerable crash on the OB2 badge-embedded-key fallback path. - OB3 independently re-validates
vc.validUntil/vc.validFromagainst wall-clock time instead of trusting only the JWTexpclaim, so an expired or not-yet-valid credential is rejected even if the JWT claim is decoupled from the vc-level dates. download_file()rejects an HTTP redirect to a non-HTTPS target, closing a TLS scheme-downgrade gap in the HTTPS-only enforcement for the OB2 verify key, issuer document, and revocation list.download_file()caps the response body at 5 MiB, bounding memory use against an attacker-influenced URL.
Correctness & robustness
The verify/sign/CLI boundaries no longer leak raw KeyError/TypeError/AttributeError/ValueError/OSError tracebacks on malformed or hostile input. Highlights: every _jws exception now inherits LibOpenBadgesException; OB2 read_from_file / check_revocation / check_expiration and OB3 credential parsing reject non-object/non-string claims with typed errors; malformed config.ini (bad INI syntax, unresolvable ${...} interpolation, encoding mismatch, missing/empty [paths] base) exits every CLI cleanly with a [!] … message; openbadges-init/-publish exit cleanly on an existing target and restore the process umask in a try/finally; recipient mailto: binding is compared case-insensitively.
See Changelog.txt for the complete, itemized list.