Skip to main content

A library to sign and verify OpenBadges

Project description

OpenBadgesLib
=============

A Python library for signing and verifying `OpenBadges`_ assertions
embedded in SVG and PNG image files. Supports both **OpenBadges 2.0**
(JWS compact serialisation) and **OpenBadges 3.0** (W3C Verifiable
Credentials / JWT-VC).

.. _OpenBadges: https://www.imsglobal.org/activity/digital-badges


Features
--------

* Sign badge images (SVG and PNG) with a JWS assertion (OB 2.0)
* Issue and verify OpenBadges 3.0 JWT-VC credentials
* Bake OB 3.0 JWT tokens into SVG and PNG badge images
* RSA 2048-bit (RS256) and ECC NIST P-256 (ES256) key support
* SHA-256 hashed recipient identity with salt (OB 2.0)
* Expiration and revocation checking
* Command-line wrapper tools included


Requirements
------------

* Python >= 3.10
* pycryptodome >= 3.20
* ecdsa >= 0.19
* pypng >= 0.20220715.0
* PyJWT[crypto] >= 2.8


Installation
------------

::

pip install openbadgeslib

All dependencies are installed automatically.

To install in development mode with the test suite::

pip install -e ".[dev]"


Quick Start
-----------

**1. Initialize a configuration directory**::

openbadges-init ./config/

**2. Generate a key pair for a badge**::

openbadges-keygenerator -c ./config/config.ini -g 1

**3. Sign a badge**::

# OpenBadges 2.0 (default)
openbadges-signer -c ./config/config.ini -b 1 -r recipient@example.com -o /tmp/

# OpenBadges 3.0
openbadges-signer -c ./config/config.ini -b 1 -r recipient@example.com -o /tmp/ -V 3

**4. Verify a signed badge**::

# OpenBadges 2.0 (default)
openbadges-verifier -i /tmp/badge_1_recipient@example.com.svg -r recipient@example.com

# OpenBadges 3.0 (supply public key directly)
openbadges-verifier -i /tmp/badge_1_recipient@example.com.svg -r recipient@example.com \
-V 3 -k ./config/keys/verify_rsa_key_1.pem


Using the library directly
--------------------------

::

from openbadgeslib.badge import Badge, BadgeImgType
from openbadgeslib.keys import KeyType
from openbadgeslib.signer import Signer
from openbadgeslib.badge import BadgeType

# Load key material
with open('sign.pem', 'rb') as f:
priv_pem = f.read()
with open('verify.pem', 'rb') as f:
pub_pem = f.read()

# Build a Badge descriptor
badge = Badge(
ini_name='my_badge',
name='My Badge',
description='Awarded for excellence',
image_type=BadgeImgType.SVG,
image=open('badge.svg', 'rb').read(),
image_url='https://example.com/badge.svg',
criteria_url='https://example.com/criteria.html',
json_url='https://example.com/badge.json',
verify_key_url='https://example.com/verify.pem',
key_type=KeyType.RSA,
privkey_pem=priv_pem,
pubkey_pem=pub_pem,
)

# Sign
signer = Signer(identity='recipient@example.com', badge_type=BadgeType.SIGNED)
signed = signer.sign_badge(badge)
signed.save_to_file('/tmp/signed_badge.svg')


OpenBadges 3.0 (JWT-VC)
-----------------------

::

from openbadgeslib.ob3 import (
Issuer, Achievement, OpenBadgeCredential,
OB3Signer, OB3Verifier,
)

# Build the credential data model
issuer = Issuer(id='https://example.com/issuer', name='Example Org')
achievement = Achievement(
id='https://example.com/achievements/python',
name='Python Developer',
description='Awarded for Python proficiency',
criteria_narrative='Must pass the Python assessment',
)
credential = OpenBadgeCredential(
issuer=issuer,
recipient_id='mailto:recipient@example.com',
achievement=achievement,
)

# Sign — returns a JWT-VC string
with open('sign.pem', 'rb') as f:
priv_pem = f.read()
signer = OB3Signer(privkey_pem=priv_pem, algorithm='RS256')
token = signer.sign(credential)

# Bake the token into a badge image
with open('badge.svg', 'rb') as f:
svg_bytes = f.read()
baked_svg = signer.sign_into_svg(credential, svg_bytes)

# Verify
with open('verify.pem', 'rb') as f:
pub_pem = f.read()
verifier = OB3Verifier(pubkey_pem=pub_pem)
extracted_token = OB3Verifier.extract_token_from_svg(baked_svg)
restored_credential = verifier.verify(extracted_token)
print('Recipient:', restored_credential.recipient_id)


Running the test suite
----------------------

::

pytest
pytest --cov=openbadgeslib # with coverage report


Documentation
-------------

Full documentation is in the ``docs/`` directory (Sphinx RST sources).

Build the HTML docs::

pip install sphinx sphinx-rtd-theme
sphinx-build -b html docs/ docs/_build/html/


Changelog
---------

**v1.1.1** (2026-06-27)

* **OB3 fixes** — recipient identifiers are normalised through one shared
helper (a DID is no longer corrupted into ``mailto:did:...``); ``verify()``
cross-checks the JWT ``iss``/``sub`` claims against the credential and
``OB3VerificationError`` now inherits from ``LibOpenBadgesException``
* **Refactor** — shared ``alg_for_key_type`` and CLI config/key helpers,
first-party code imports ``openbadgeslib.ob2`` directly, and the oversized
verify/sign functions were decomposed
* **Type hints** added on the OB2/core byte-vs-str boundaries
* **Tests & CI** — OB2 signer CLI, mail and ``read_from_file`` edges covered
(258 tests, 92 % coverage; repo is flake8-clean); a GitHub Actions workflow
lints and tests on Python 3.10-3.13

**v1.1.0** (2026-06-27)

* **Security — algorithm pinning** — verification now pins the accepted
signature algorithm to the key's type (RS*/ES*) instead of trusting the
token header, blocking cross-type confusion and any ``none``/HMAC downgrade
* **Security — hardened parsing** — SVG is parsed with ``defusedxml`` (defusing
billion-laughs DoS) and compressed PNG ``iTXt`` tokens are inflated with a
256 KB cap (defusing decompression bombs); adds a ``defusedxml`` dependency
* **Security — OB2 CLI trust** — the verifier only reports ``[+] Signature is
correct`` when a trusted key is supplied (``--local``/``--pubkey``);
otherwise it warns that the result is internally consistent only
* **Bug fixes** — base64url padding, ``get_serial_num`` on file-loaded badges,
``check_identity`` with no identity, SVG-parse error masking, and loud
failures for unknown key/image types
* **Refactor** — one ``keys.key_to_pem`` and a shared ``openbadgeslib.baking``
module remove the OB2/OB3 duplication (key conversion and SVG/PNG carrier)
* **Cleanup** — dropped ``setup.py``, stale ``dist/`` artifacts, ``MANIFEST``
and dead exceptions; OB3 documented on the landing page; SMTP feature
documented; 236 tests, 87 % line coverage

**v1.0.2** (2026-06-18)

* **Security** — OB2 verification uses the operator-supplied trusted key when
provided (a forged badge can no longer self-describe its verify key); and
``download_file`` rejects non-HTTPS URLs by default (the verify key is the
root of trust)
* **Expiration fix** — badges are now considered expired relative to the
current time, not relative to their own issue date
* **CLI fixes** — ``openbadges-publish`` now publishes every badge with its
verify key; ``openbadges-keygenerator`` honours a ``key_type`` (RSA/ECC) field
in the badge profile; SMTP ``use_ssl`` is parsed as a boolean and mail
connection errors no longer crash a successful sign
* **OB 3.0** — credentials use the W3C VC 2.0 data model
(``validFrom``/``validUntil``); ``OB3Verifier.verify()`` gained an optional
``expected_recipient`` argument and asserts the credential type; PNG token
extraction parses the iTXt chunk properly
* Logs are appended (no longer truncated on every run); expanded test coverage
of the CLI tools, publishing, mail, and revocation

**v1.0.1** (2026-04-22)

* **OpenBadges 3.0 support** — new ``openbadgeslib.ob3`` subpackage:
``OpenBadgeCredential``, ``Issuer``, ``Achievement`` data classes;
``OB3Signer`` (JWT-VC signing + SVG/PNG baking); ``OB3Verifier``
(JWT-VC verification + token extraction from SVG/PNG)
* **OpenBadges 2.0 subpackage** — OB2 implementation moved to
``openbadgeslib.ob2``; top-level modules kept as backward-compatible
shims so existing code requires no changes
* **``--ob-version`` flag** — all four CLI tools (keygenerator, signer,
verifier, publish) accept ``-V {2,3}`` to select the specification
version (default: ``2``)
* **``openbadges-verifier --pubkey``** — new ``-k FILE`` option to supply
the PEM public key directly for OB3 verification
* Python 3.10+ compatibility: removed distutils, migrated packaging to
``pyproject.toml`` with ``setuptools.build_meta``
* Replaced abandoned ``pycrypto`` with ``pycryptodome >= 3.20``
* Replaced custom JWS engine (``3dparty/jws/``) with ``PyJWT[crypto]``
algorithm classes (RS256/384/512, ES256/384/512); old ``3dparty/``
directory removed
* Fixed TLS: removed deprecated ``PROTOCOL_TLSv1`` / ``CERT_NONE``;
``download_file`` now uses the system default TLS context
* Updated pypng API: renamed ``signature`` constant, bytes chunk tags
* Copyright year range updated to 2014-2026 across all source files
* Fixed verifier logic bug: ``check_jws_signature`` return value was
compared with ``BadgeStatus`` via identity check, always evaluating True
* Added 203 unit tests, 89% line coverage

**v0.4.2** and earlier

* See git history at https://github.com/luisgf/openbadgeslib


License
-------

The library is licensed under the `GNU Lesser General Public License v3`_
(LGPLv3). The command-line wrapper tools are licensed under the
`BSD 2-Clause`_ license.

.. _GNU Lesser General Public License v3: https://opensource.org/licenses/lgpl-3.0.html
.. _BSD 2-Clause: https://opensource.org/licenses/BSD-2-Clause


Authors
-------

* Luis González Fernández <luisgf@luisgf.es>
* Jesús Cea Avión <jcea@jcea.es>

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openbadgeslib-1.1.1.tar.gz (103.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openbadgeslib-1.1.1-py3-none-any.whl (53.1 kB view details)

Uploaded Python 3

File details

Details for the file openbadgeslib-1.1.1.tar.gz.

File metadata

  • Download URL: openbadgeslib-1.1.1.tar.gz
  • Upload date:
  • Size: 103.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openbadgeslib-1.1.1.tar.gz
Algorithm Hash digest
SHA256 14d1ac41a01dddc47478510a0dca77af588e600269803f58dd788184198af95d
MD5 82264389893f5a44695dddedabde0c61
BLAKE2b-256 edf7c4c17803789afc84ee5be925d0542d9cadc700841791bc8c56e82c11ee59

See more details on using hashes here.

File details

Details for the file openbadgeslib-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: openbadgeslib-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 53.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openbadgeslib-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 be3b3bfe89e6e274103597379054c600d1fdbb3d1ef4baf692e41ed90300f3b4
MD5 65ac1a5ef9381048c6c780e328d187aa
BLAKE2b-256 958579408a7c99ec49fc501a94be95eef1011ac77d96848cc0095958b39a5a25

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page