Skip to content

Releases: luisgf/openbadgeslib

v3.3.0

Choose a tag to compare

@luisgf luisgf released this 04 Jul 08:21

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): OB3LdpSigner issues OB 3.0 credentials carrying an embedded Data Integrity proof, cryptosuite eddsa-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 as add_data_integrity_proof for 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-badge proof_format config key (the flag wins; default stays vc-jwt). With [issuer] did configured, the proof names the did:web:…#badge_N verificationMethod that openbadges-publish -V 3 publishes (trusted on verify via --resolve-did); otherwise it falls back to a self-asserted did:key derived from the signing key and tells the operator so. LDP-signed badges are tagged PROOF ldp in the signer audit log. Status lists stay VC-JWT regardless of the badge's proof format.
  • feat(ob3): multikey / did:key encoders in ob3.didmultikey_from_pem (publicKeyMultibase) and did_key_from_pem, the exact inverses of the resolvers, for Ed25519 and NIST P-256.

Fixed

  • fix(cli): Badge.create_from_conf rejected key_type = ED25519 outright, so the CLI could never sign with the keys openbadges-keygenerator -t ED25519 produces. 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

Choose a tag to compare

@luisgf luisgf released this 03 Jul 18:27

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): OB3LdpVerifier verifies OB 3.0 credentials secured with an embedded Data Integrity proof, cryptosuite eddsa-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 as verify_data_integrity_proof for non-OB3 VCs. Same API, trust model and exceptions as the JWT verifier: pinned key wins; otherwise the key resolves from proof.verificationMethod with fail-closed binding to the credential's DID issuer; expected_recipient / check_status behave identically.
  • feat(ob3): JSON-LD @context documents 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_method resolves the exact verificationMethod URL a proof names (did:key fragment validation; did:web entry selected by id, 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 3 autodetects the baked payload (compact JWT vs credential JSON) with the same trust flags — no new options — and reports proof_format ("vc-jwt" / "ldp") in --json. Without the extra, the failure reason carries the install hint.
  • feat(api): OpenBadgeCredential.from_vc_document reconstructs 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

Choose a tag to compare

@luisgf luisgf released this 03 Jul 17:22

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

Choose a tag to compare

@luisgf luisgf released this 02 Jul 21:34

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 with status_lists = revocation, suspension get 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 3 is 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) plus verify.pem. New management flags --revoke ID, --suspend ID, --unsuspend ID (ID = jti or recipient email; -b scopes, --reason annotates) update the registry and regenerate the lists. Revocation is permanent; the -V 3 output directory may already exist and its files are replaced atomically.
  • feat(config): new opt-in keys — [paths] base_status, [issuer] did (auto derives a did:web issuer id from publish_url), and per-badge status_lists / status_size_bits / status_base. openbadges-init now also creates status/.
  • 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

Choose a tag to compare

@luisgf luisgf released this 01 Jul 22:34

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 unwritable base_log reports a clean error instead of crashing with a raw OSError and 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

Choose a tag to compare

@luisgf luisgf released this 01 Jul 21:44
d1b8367

Strict Open Badges 2.0 — and an honest version split.

Highlights

  • Strict Open Badges 2.0 (-V 2, new openbadgeslib.ob2): conformant JSON-LD assertions (@context, type, an IRI id, a boolean hashed, ISO 8601 dates, a verification object), OB2Signer / OB2Verifier, real HostedBadge verification (HTTPS fetch of the assertion id with an issuer-origin scope check), and SignedBadge verification.creatorCryptographicKey resolution with an owner/publicKey back-link check.
  • Conformant publishing (openbadges-publish -V 2): an issuer Profile with a publicKey array, a BadgeClass and a CryptographicKey (key.json) per badge, and a RevocationList.
  • Legacy relabelled: the pre-2.0 format previously shipped as -V 2 is now OpenBadges 1.0 (-V 1, openbadgeslib.ob1), frozen unchanged.

Breaking changes

  • The default -V is now 3 (was 2) across openbadges-signer / -verifier / -publish / -keygenerator.
  • from openbadgeslib.ob2 import Signer/Verifier/Badge no longer resolves — import from openbadgeslib.ob1 (the top-level openbadgeslib.signer / verifier / badge shims are unchanged).

See the Changelog for the full list.

Merged in #121 · closes #117, #118, #119, #120.

openbadgeslib 2.0.0

Choose a tag to compare

@luisgf luisgf released this 01 Jul 18:04

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 vc claim wrapper), validFrom maps to nbf (there is no iat), and the JOSE header carries the issuer's public key as a jwk. Tokens issued by 1.x (the VCDM-1.1-style vc-wrapper) are not compatible.
  • OB 3.0 baking identifiers. PNG iTXt keyword openbadgecredential and SVG <openbadges:credential> (namespace https://purl.imsglobal.org/ob/v3p0), replacing the OB 2.0 openbadges / <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 AchievementCredential type alias, an issuer given as a string IRI, and a credentialSubject without an id (identity via identifier).
  • Verifier enforces required structure it used to ignore: the @context (VC 2.0 + OB v3p0 pair) is validated, and the iss/nbf registered claims are required (sub required when the subject has an id).
  • credentialStatus honors statusPurpose. suspension is reported distinctly from revocation, 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

Choose a tag to compare

@luisgf luisgf released this 01 Jul 14:06

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-trusted
  • 2 — signature valid but the issuer is not anchored (an OB2 badge-embedded key, or a self-asserted did: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/revocationList URL, an OB3 did:web host, an OB3 credentialStatus list), 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. An allow_private=True opt-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:web is 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).

  • --json exit status reflects issuer trust (see the behaviour change above), so machine consumers cannot gate on a valid-but-untrusted signature.

  • A resolved did:key is reported untrusted. With --resolve-did, a self-asserted did:key issuer is now trusted: false (with a warning), mirroring OB2's badge-embedded-key case; only an operator-supplied key or a DNS/TLS-anchored did:web is trusted.

Fixes

  • OB3 Bitstring Status List statusSize > 1 now 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 unsupported statusSize is now rejected rather than misread.

  • confparser resolves a relative [paths] base against the config-file directory (not the process CWD). Previously only a bare . was handled and its value replaced wholesale, silently dropping the suffix of ./data or ../shared and 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-keygenerator key_type, the shipped config example); the --json exit-status scheme documented; and stale crypto-library / dependency notes corrected.

See Changelog.txt for the full list.

openbadgeslib 1.2.0

Choose a tag to compare

@luisgf luisgf released this 01 Jul 12:54

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 = ED25519 in the badge profile), OB2 JWS and OB3 JWT-VC signing and verification. detect_key_type classifies an Ed25519 PEM explicitly — the ecdsa library would otherwise misread it as a NIST/ECC key — and the algorithm-pinning allowlists bind EdDSA to 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) (and openbadges-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() and OB3Verifier.for_issuer_did() support did:key (Ed25519 and P-256, self-certifying, offline) and did:web (fetches the DID document over HTTPS and reads its first verification method's publicKeyJwk or publicKeyMultibase). openbadges-verifier --resolve-did reads the issuer DID from the token and resolves it when no key is supplied. did:key needs no external trust; did:web trusts the host's DNS + TLS (documented in the Security Model). (#105)

  • --json verifier output. openbadges-verifier --json emits a single machine-readable JSON result (valid, ob_version, recipient, reason, plus OB2 trusted/status or OB3 issuer/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

Choose a tag to compare

@luisgf luisgf released this 01 Jul 08:18

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, or 0 was 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 raw AttributeError — closes a remotely-triggerable crash on the OB2 badge-embedded-key fallback path.
  • OB3 independently re-validates vc.validUntil/vc.validFrom against wall-clock time instead of trusting only the JWT exp claim, 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.