openbadgeslib 3.12.0__tar.gz → 3.13.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.
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/Changelog.txt +16 -0
- {openbadgeslib-3.12.0/openbadgeslib.egg-info → openbadgeslib-3.13.0}/PKG-INFO +1 -1
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/eudi.py +50 -1
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/util.py +1 -1
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0/openbadgeslib.egg-info}/PKG-INFO +1 -1
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib.egg-info/SOURCES.txt +1 -0
- openbadgeslib-3.13.0/tests/test_ob3_eudi_x5c.py +120 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/LICENSE.txt +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/MANIFEST.in +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/README.md +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/SECURITY.md +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/docs/README.md +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/__init__.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/_jws/__init__.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/_jws/exceptions.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/_jws/utils.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/badge.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/baking.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/config.ini.example +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/confparser.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/errors.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/issue.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/keys.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/logs.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/mail.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob1/__init__.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob1/badge.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob1/signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob1/verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob2/__init__.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob2/models.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob2/signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob2/verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/__init__.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/__init__.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/credential.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/did.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/ldp.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/status.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/status_list.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/status_registry.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/openbadges_init.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/openbadges_keygenerator.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/openbadges_publish.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/openbadges_signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/openbadges_verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/py.typed +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib.egg-info/dependency_links.txt +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib.egg-info/entry_points.txt +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib.egg-info/requires.txt +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib.egg-info/top_level.txt +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/pyproject.toml +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/setup.cfg +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/config1.ini +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/conformance/Dockerfile.ob3 +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/conformance/README.md +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/conformance/conftest.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/conformance/docker-compose.yml +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/conformance/test_official_validators.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/conftest.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/ob_schemas/README.md +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/ob_schemas/ob_v3p0_achievementcredential_schema.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/ob_schemas/refresh.sh +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/README.md +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/images/sample1.png +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/images/sample1.svg +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/images/userimage01.svg +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/ldp_helpers.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/logo Python Espan/314/203a.svg" +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/logo Python Espa/303/261a.svg" +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_baking.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_cli_json.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_cli_smoke.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_confparser.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_docs.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_eddsa.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_examples.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_issue.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_jws.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_key_operation.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_keys.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_mail.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob1_badge_io.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob1_deprecation.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob1_signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob1_verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob2_cli.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob2_error_branches.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob2_models.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob2_signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob2_verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_conformance_schema.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_contexts.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_credential.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_did.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_did_doc.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_endorsement.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_eudi_sd_jwt.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_eudi_type_metadata.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_ldp.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_ldp_cli.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_ldp_ecdsa_sd.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_ldp_errors.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_ldp_sign.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_publish_cli.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_signer.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_signer_cli_ldp.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_status.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_status_list.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_status_registry.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_ob3_verifier.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_sign_ecc.pem +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_sign_rsa.pem +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_signer_batch.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_util.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_verifier_cli_coverage.py +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_verify_ecc.pem +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/test_verify_rsa.pem +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/withoutxmlheader.svg +0 -0
- {openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/withxmlheader.svg +0 -0
|
@@ -4,6 +4,22 @@ OpenBadgesLib - Changelog
|
|
|
4
4
|
Newest first. Dates are ISO 8601 (YYYY-MM-DD).
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
* v3.13.0 - 2026-07-09
|
|
8
|
+
|
|
9
|
+
- feat(eudi): **opt-in X.509 / EU Trusted List trust for received SD-JWT
|
|
10
|
+
badges** (#178). `verify_badge_sd_jwt(..., x5c_trust_anchors=…)` verifies a
|
|
11
|
+
third-party badge whose issuer JWT carries an `x5c` chain by routing through
|
|
12
|
+
openvc-core's `verify_credential` pipeline, which path-validates the chain
|
|
13
|
+
to the caller's X.509 roots and **binds it to `iss`** before using the leaf
|
|
14
|
+
key — the eIDAS trust root the JWK-pinned suite path cannot reach. Build the
|
|
15
|
+
anchors from an EU Trusted List with
|
|
16
|
+
`openvc.trustlist.walk_lotl(...).certificates` (LOTL → national TLs,
|
|
17
|
+
fail-closed, caller-pinned signers, XXE-hardened) or pass your own roots.
|
|
18
|
+
JWK-pinning stays the default — exactly one of `pubkey_pem` /
|
|
19
|
+
`x5c_trust_anchors` is required — and the return type is unchanged
|
|
20
|
+
(openvc-core's `VerifiedSdJwt`). Status is not checked on this path, as on
|
|
21
|
+
the pinned one. (Closes #178)
|
|
22
|
+
|
|
7
23
|
* v3.12.0 - 2026-07-09
|
|
8
24
|
|
|
9
25
|
- feat(eudi): publish SD-JWT VC **Type Metadata** for the badge `vct`, so a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbadgeslib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.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
|
|
@@ -223,11 +223,12 @@ def issue_badge_sd_jwt(
|
|
|
223
223
|
def verify_badge_sd_jwt(
|
|
224
224
|
token: str,
|
|
225
225
|
*,
|
|
226
|
-
pubkey_pem: Any,
|
|
226
|
+
pubkey_pem: Any = None,
|
|
227
227
|
audience: Optional[str] = None,
|
|
228
228
|
nonce: Optional[str] = None,
|
|
229
229
|
require_key_binding: bool = False,
|
|
230
230
|
expected_vct: Optional[str] = OB3_SD_JWT_VCT,
|
|
231
|
+
x5c_trust_anchors: Any = None,
|
|
231
232
|
) -> Any:
|
|
232
233
|
"""Verify a badge SD-JWT VC (issuer form or a holder presentation).
|
|
233
234
|
|
|
@@ -235,7 +236,29 @@ def verify_badge_sd_jwt(
|
|
|
235
236
|
``.key_bound``, ``.confirmation``). Raises :class:`EudiError` on any failure.
|
|
236
237
|
Pass *audience*/*nonce* (and ``require_key_binding=True``) to check a Key
|
|
237
238
|
Binding JWT from a holder presentation.
|
|
239
|
+
|
|
240
|
+
Trust comes from exactly one of two sources:
|
|
241
|
+
|
|
242
|
+
* *pubkey_pem* — pin the issuer's public key (the default; right for a known
|
|
243
|
+
issuer whose key you already hold).
|
|
244
|
+
* *x5c_trust_anchors* — a sequence of trusted X.509 root ``Certificate``
|
|
245
|
+
objects (e.g. an EU Trusted List's ``TrustAnchorSet.certificates`` from
|
|
246
|
+
``openvc.trustlist``). Opts into eIDAS **X.509 issuer trust**: a received
|
|
247
|
+
third-party badge whose issuer JWT carries an ``x5c`` chain is
|
|
248
|
+
path-validated to those anchors and bound to ``iss`` before its leaf key
|
|
249
|
+
is used — routed through openvc-core's ``verify_credential`` pipeline (the
|
|
250
|
+
JWK-pin suite path cannot do X.509 trust). Status is not checked here, as
|
|
251
|
+
with the pinned path.
|
|
238
252
|
"""
|
|
253
|
+
if x5c_trust_anchors is not None:
|
|
254
|
+
return _verify_sd_jwt_x5c(
|
|
255
|
+
token, x5c_trust_anchors, expected_vct=expected_vct,
|
|
256
|
+
audience=audience, nonce=nonce,
|
|
257
|
+
require_key_binding=require_key_binding)
|
|
258
|
+
if pubkey_pem is None:
|
|
259
|
+
raise EudiError(
|
|
260
|
+
"verify_badge_sd_jwt needs either pubkey_pem (pin the issuer's key) "
|
|
261
|
+
"or x5c_trust_anchors (eIDAS X.509 / EU Trusted List trust)")
|
|
239
262
|
_, _, _, SdJwtVcProofSuite = _require_openvc()
|
|
240
263
|
try:
|
|
241
264
|
public_key_jwk = public_jwk_from_pem(pubkey_pem)
|
|
@@ -246,3 +269,29 @@ def verify_badge_sd_jwt(
|
|
|
246
269
|
raise
|
|
247
270
|
except Exception as exc:
|
|
248
271
|
raise EudiError("SD-JWT VC badge verification failed: %s" % exc) from exc
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _verify_sd_jwt_x5c(token: str, x5c_trust_anchors: Any, *,
|
|
275
|
+
expected_vct: Optional[str], audience: Optional[str],
|
|
276
|
+
nonce: Optional[str], require_key_binding: bool) -> Any:
|
|
277
|
+
"""Verify a badge SD-JWT whose issuer JWT carries an ``x5c`` chain against
|
|
278
|
+
*x5c_trust_anchors* (X.509 roots), via openvc-core's ``verify_credential``
|
|
279
|
+
pipeline — which path-validates the chain and binds it to ``iss`` before
|
|
280
|
+
using the leaf key. Returns the underlying ``VerifiedSdJwt``."""
|
|
281
|
+
try:
|
|
282
|
+
from openvc import VerificationPolicy, verify_credential
|
|
283
|
+
except ImportError as exc:
|
|
284
|
+
raise EudiError(_INSTALL_HINT) from exc
|
|
285
|
+
policy = VerificationPolicy(
|
|
286
|
+
expected_vct=expected_vct, audience=audience, nonce=nonce,
|
|
287
|
+
require_key_binding=require_key_binding, require_status=False)
|
|
288
|
+
try:
|
|
289
|
+
result = verify_credential(
|
|
290
|
+
token, policy=policy, x5c_trust_anchors=x5c_trust_anchors)
|
|
291
|
+
except EudiError:
|
|
292
|
+
raise
|
|
293
|
+
except Exception as exc:
|
|
294
|
+
raise EudiError(
|
|
295
|
+
"SD-JWT VC badge verification against the X.509 trust anchors "
|
|
296
|
+
"failed: %s" % exc) from exc
|
|
297
|
+
return result.raw
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbadgeslib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.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
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Tests for opt-in X.509 / EU Trusted List trust anchoring in
|
|
2
|
+
verify_badge_sd_jwt (the EUDI SD-JWT track, #178).
|
|
3
|
+
|
|
4
|
+
A received third-party badge whose issuer JWT carries an `x5c` chain is verified
|
|
5
|
+
by routing through openvc-core's verify_credential pipeline, which path-validates
|
|
6
|
+
the chain to the caller's trust anchors (e.g. an EU Trusted List's
|
|
7
|
+
TrustAnchorSet.certificates) and binds it to `iss`. The crypto needs the [eudi]
|
|
8
|
+
extra and skips without it; the "extra absent" test runs always.
|
|
9
|
+
"""
|
|
10
|
+
import base64
|
|
11
|
+
import datetime as dt
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from openbadgeslib.ob3.eudi import (OB3_SD_JWT_VCT, EudiError,
|
|
18
|
+
verify_badge_sd_jwt)
|
|
19
|
+
|
|
20
|
+
ISS = 'https://issuer.example'
|
|
21
|
+
_PAST = dt.datetime(2020, 1, 1, tzinfo=dt.timezone.utc)
|
|
22
|
+
_FUTURE = dt.datetime(2035, 1, 1, tzinfo=dt.timezone.utc)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _chain():
|
|
26
|
+
"""Build a root -> intermediate -> leaf chain (leaf SAN = ISS). Returns the
|
|
27
|
+
x5c list [leaf, inter] (DER-base64), the root cert, and the leaf key."""
|
|
28
|
+
from cryptography import x509
|
|
29
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
30
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
31
|
+
from cryptography.x509.oid import NameOID
|
|
32
|
+
|
|
33
|
+
def name(cn):
|
|
34
|
+
return x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)])
|
|
35
|
+
|
|
36
|
+
def cert(subject, issuer, issuer_key, subject_key, *, ca, san=None):
|
|
37
|
+
builder = (x509.CertificateBuilder()
|
|
38
|
+
.subject_name(name(subject)).issuer_name(name(issuer))
|
|
39
|
+
.public_key(subject_key.public_key())
|
|
40
|
+
.serial_number(x509.random_serial_number())
|
|
41
|
+
.not_valid_before(_PAST).not_valid_after(_FUTURE)
|
|
42
|
+
.add_extension(x509.BasicConstraints(ca=ca, path_length=None),
|
|
43
|
+
critical=True))
|
|
44
|
+
if ca:
|
|
45
|
+
builder = builder.add_extension(x509.KeyUsage(
|
|
46
|
+
digital_signature=False, content_commitment=False,
|
|
47
|
+
key_encipherment=False, data_encipherment=False,
|
|
48
|
+
key_agreement=False, key_cert_sign=True, crl_sign=True,
|
|
49
|
+
encipher_only=False, decipher_only=False), critical=True)
|
|
50
|
+
if san:
|
|
51
|
+
builder = builder.add_extension(
|
|
52
|
+
x509.SubjectAlternativeName(san), critical=False)
|
|
53
|
+
return builder.sign(issuer_key, hashes.SHA256())
|
|
54
|
+
|
|
55
|
+
def der_b64(crt):
|
|
56
|
+
return base64.b64encode(
|
|
57
|
+
crt.public_bytes(serialization.Encoding.DER)).decode('ascii')
|
|
58
|
+
|
|
59
|
+
root_key = ec.generate_private_key(ec.SECP256R1())
|
|
60
|
+
root = cert('root', 'root', root_key, root_key, ca=True)
|
|
61
|
+
inter_key = ec.generate_private_key(ec.SECP256R1())
|
|
62
|
+
inter = cert('inter', 'root', root_key, inter_key, ca=True)
|
|
63
|
+
leaf_key = ec.generate_private_key(ec.SECP256R1())
|
|
64
|
+
leaf = cert('leaf', 'inter', inter_key, leaf_key, ca=False,
|
|
65
|
+
san=[x509.UniformResourceIdentifier(ISS)])
|
|
66
|
+
return [der_b64(leaf), der_b64(inter)], root, leaf_key
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _sd_jwt_with_x5c(leaf_key, x5c, *, iss=ISS, vct=OB3_SD_JWT_VCT):
|
|
70
|
+
"""A minimal badge SD-JWT (no disclosures) whose issuer JWT carries *x5c*."""
|
|
71
|
+
from openvc.keys import P256SigningKey
|
|
72
|
+
from openvc.proof._jws import sign_compact
|
|
73
|
+
header = {'alg': 'ES256', 'typ': 'dc+sd-jwt', 'kid': 'leaf',
|
|
74
|
+
'x5c': list(x5c)}
|
|
75
|
+
payload = {'iss': iss, 'vct': vct, 'iat': int(time.time()),
|
|
76
|
+
'name': 'Python 101',
|
|
77
|
+
'achievement': {'id': 'https://ex/b/1', 'name': 'Python 101'}}
|
|
78
|
+
return sign_compact(
|
|
79
|
+
header, payload, signing_key=P256SigningKey(leaf_key, kid='leaf')) + '~'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestX5cTrust:
|
|
83
|
+
@pytest.fixture(autouse=True)
|
|
84
|
+
def _needs_openvc(self):
|
|
85
|
+
pytest.importorskip('openvc')
|
|
86
|
+
|
|
87
|
+
def test_trusted_anchor_verifies_and_binds_issuer(self):
|
|
88
|
+
x5c, root, leaf_key = _chain()
|
|
89
|
+
result = verify_badge_sd_jwt(
|
|
90
|
+
_sd_jwt_with_x5c(leaf_key, x5c), x5c_trust_anchors=[root])
|
|
91
|
+
assert result.issuer == ISS
|
|
92
|
+
assert result.vct == OB3_SD_JWT_VCT
|
|
93
|
+
|
|
94
|
+
def test_untrusted_anchor_rejected(self):
|
|
95
|
+
x5c, _root, leaf_key = _chain()
|
|
96
|
+
unrelated_root = _chain()[1]
|
|
97
|
+
with pytest.raises(EudiError):
|
|
98
|
+
verify_badge_sd_jwt(_sd_jwt_with_x5c(leaf_key, x5c),
|
|
99
|
+
x5c_trust_anchors=[unrelated_root])
|
|
100
|
+
|
|
101
|
+
def test_tampered_signature_rejected(self):
|
|
102
|
+
x5c, root, leaf_key = _chain()
|
|
103
|
+
jwt_part, sep, rest = _sd_jwt_with_x5c(leaf_key, x5c).partition('~')
|
|
104
|
+
head, payload, sig = jwt_part.split('.')
|
|
105
|
+
sig = ('A' if sig[0] != 'A' else 'B') + sig[1:] # break the signature
|
|
106
|
+
with pytest.raises(EudiError):
|
|
107
|
+
verify_badge_sd_jwt('.'.join([head, payload, sig]) + sep + rest,
|
|
108
|
+
x5c_trust_anchors=[root])
|
|
109
|
+
|
|
110
|
+
def test_neither_pubkey_nor_anchors_is_rejected(self):
|
|
111
|
+
x5c, _root, leaf_key = _chain()
|
|
112
|
+
with pytest.raises(EudiError, match='pubkey_pem'):
|
|
113
|
+
verify_badge_sd_jwt(_sd_jwt_with_x5c(leaf_key, x5c))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_x5c_path_needs_the_extra(monkeypatch):
|
|
117
|
+
for name in ('openvc', 'openvc.keys', 'openvc.proof.sd_jwt'):
|
|
118
|
+
monkeypatch.setitem(sys.modules, name, None)
|
|
119
|
+
with pytest.raises(EudiError, match=r'openbadgeslib\[eudi\]'):
|
|
120
|
+
verify_badge_sd_jwt('x~', x5c_trust_anchors=['anchor'])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/credentials-v2.json
RENAMED
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json
RENAMED
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json
RENAMED
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json
RENAMED
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/openbadgeslib/ob3/contexts/security-multikey-v1.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt
RENAMED
|
File without changes
|
{openbadgeslib-3.12.0 → openbadgeslib-3.13.0}/tests/fixtures/vc_di_eddsa/signed-credential.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|