openbadgeslib 4.1.0__tar.gz → 4.2.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-4.1.0 → openbadgeslib-4.2.0}/Changelog.txt +23 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/PKG-INFO +1 -1
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/eudi.py +68 -4
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/util.py +1 -1
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib.egg-info/PKG-INFO +1 -1
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_eudi_oid4vci.py +71 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/CONTRIBUTING.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/GOVERNANCE.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/LICENSE.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/MANIFEST.in +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/README.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/SECURITY.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/docs/README.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/__init__.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/_jsonmodel.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/_jws/__init__.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/_jws/exceptions.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/_jws/utils.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/_ob1_api.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/badge_model.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/baking.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/cli_common.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/config.ini.example +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/confparser.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/errors.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/issue.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/keys.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/logs.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/mail.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob1/__init__.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob1/badge.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob1/signer.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob1/verifier.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob2/__init__.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob2/models.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob2/signer.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob2/verifier.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/__init__.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/__init__.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/credentials-v2.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-extensions.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/security-data-integrity-v2.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/security-multikey-v1.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/credential.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/did.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/ldp.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/publish.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/signer.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/status.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/status_list.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/status_registry.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/verifier.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/openbadges_cli.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/openbadges_init.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/openbadges_keygenerator.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/openbadges_publish.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/openbadges_signer.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/openbadges_status.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/openbadges_verifier.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/py.typed +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/verify.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib.egg-info/SOURCES.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib.egg-info/dependency_links.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib.egg-info/entry_points.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib.egg-info/requires.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib.egg-info/top_level.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/pyproject.toml +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/setup.cfg +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/config1.ini +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/conformance/Dockerfile.ob3 +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/conformance/README.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/conformance/conftest.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/conformance/docker-compose.yml +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/conformance/test_official_validators.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/conftest.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/ob_schemas/README.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/ob_schemas/ob_v3p0_achievementcredential_schema.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/ob_schemas/refresh.sh +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/README.md +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/doc-hash.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/key-pair.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/signed-credential.json +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/images/sample1.png +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/images/sample1.svg +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/images/userimage01.svg +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/ldp_helpers.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/logo Python Espan/314/203a.svg" +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/logo Python Espa/303/261a.svg" +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_baking.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_cli_json.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_cli_smoke.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_confparser.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_docs.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_eddsa.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_errors.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_examples.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_issue.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_jsonmodel.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_jws.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_key_operation.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_keys.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_mail.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob1_badge_io.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob1_deprecation.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob1_signer.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob1_verifier.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob2_cli.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob2_error_branches.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob2_models.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob2_signer.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob2_verifier.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_conformance_schema.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_contexts.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_credential.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_did.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_did_doc.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_endorsement.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_eudi_sd_jwt.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_eudi_type_metadata.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_eudi_x5c.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_ldp.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_ldp_cli.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_ldp_ecdsa_sd.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_ldp_errors.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_ldp_sign.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_publish_cli.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_signer.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_signer_cli_ldp.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_status.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_status_list.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_status_registry.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_ob3_verifier.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_openbadges_cli.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_public_api.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_sign_ecc.pem +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_sign_rsa.pem +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_signer_batch.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_util.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_verifier_cli_coverage.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_verify.py +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_verify_ecc.pem +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/test_verify_rsa.pem +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/withoutxmlheader.svg +0 -0
- {openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/withxmlheader.svg +0 -0
|
@@ -4,6 +4,29 @@ OpenBadgesLib - Changelog
|
|
|
4
4
|
Newest first. Dates are ISO 8601 (YYYY-MM-DD).
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
* v4.2.0 - 2026-07-29
|
|
8
|
+
|
|
9
|
+
The OpenID4VCI credential configuration an issuer advertises can now declare
|
|
10
|
+
the key attestation it requires from a wallet — opt-in, because the spec makes
|
|
11
|
+
the field's mere presence a demand.
|
|
12
|
+
|
|
13
|
+
- feat(eudi): `badge_credential_configuration()` takes
|
|
14
|
+
`key_attestations_required=`, so an issuer that requires a wallet key
|
|
15
|
+
attestation can advertise it — `{}` for "required, no additional
|
|
16
|
+
constraints", or the OpenID4VCI 1.0 D.2 resistance values it accepts under
|
|
17
|
+
`key_storage` / `user_authentication`. The default stays **omitted**, and
|
|
18
|
+
that default is normative rather than conservative: §12.2.4 says the
|
|
19
|
+
parameter "MUST NOT be present in the metadata" unless the issuer requires an
|
|
20
|
+
attestation, and an empty object is not a neutral placeholder but the
|
|
21
|
+
positive claim that one is needed — emitting it unasked would have every
|
|
22
|
+
badge issuer demand a hardware-backed attestation and lock out the software
|
|
23
|
+
wallets that cannot produce one. Callers who pass nothing get the same bytes
|
|
24
|
+
as before. The shape is validated (`EudiError` on an unknown key, an empty
|
|
25
|
+
array where the spec says non-empty, a bare string where an array belongs, or
|
|
26
|
+
a non-string level) and defensively copied; the resistance values are not
|
|
27
|
+
checked, since D.2 lets each ecosystem define its own (#276).
|
|
28
|
+
|
|
29
|
+
|
|
7
30
|
* v4.1.0 - 2026-07-27
|
|
8
31
|
|
|
9
32
|
The EUDI SD-JWT VC track becomes revocable (IETF Token Status List) and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbadgeslib
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.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
|
|
@@ -275,12 +275,61 @@ _CLAIM_DISPLAY_NAMES: dict[tuple[str, ...], str] = {
|
|
|
275
275
|
("credentialSubject",): "Recipient",
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
# The only two constraints OpenID4VCI 1.0 Appendix D.2 defines inside
|
|
279
|
+
# ``key_attestations_required``. The *values* are deliberately extensible there
|
|
280
|
+
# ("ecosystems may define their own values"), so only the shape is checked here,
|
|
281
|
+
# never the level strings themselves.
|
|
282
|
+
_KEY_ATTESTATION_CONSTRAINTS = ("key_storage", "user_authentication")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _key_attestations_required(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
286
|
+
"""Validate and copy a ``key_attestations_required`` object.
|
|
287
|
+
|
|
288
|
+
Empty is legal and meaningful — OpenID4VCI 1.0 §12.2.4: with both
|
|
289
|
+
``key_storage`` and ``user_authentication`` absent the object indicates "a key
|
|
290
|
+
attestation is needed without additional constraints". What is rejected is the
|
|
291
|
+
shape a wallet cannot act on: an unknown key, an empty array where the spec
|
|
292
|
+
says non-empty, or a bare string where an array belongs — that last one is
|
|
293
|
+
silent otherwise, since ``list("iso_18045_moderate")`` is a list of letters.
|
|
294
|
+
"""
|
|
295
|
+
if not isinstance(value, Mapping):
|
|
296
|
+
raise EudiError(
|
|
297
|
+
"key_attestations_required must be a mapping (use {} for 'required, "
|
|
298
|
+
"no additional constraints'), got %s" % type(value).__name__)
|
|
299
|
+
unknown = sorted(k for k in value if k not in _KEY_ATTESTATION_CONSTRAINTS)
|
|
300
|
+
if unknown:
|
|
301
|
+
raise EudiError(
|
|
302
|
+
"unknown key_attestations_required key(s) %s; OpenID4VCI 1.0 D.2 "
|
|
303
|
+
"defines only %s" % (", ".join(unknown),
|
|
304
|
+
", ".join(_KEY_ATTESTATION_CONSTRAINTS)))
|
|
305
|
+
required: dict[str, Any] = {}
|
|
306
|
+
for name in _KEY_ATTESTATION_CONSTRAINTS:
|
|
307
|
+
if name not in value:
|
|
308
|
+
continue
|
|
309
|
+
levels = value[name]
|
|
310
|
+
if isinstance(levels, (str, bytes)) or not isinstance(levels, (list, tuple)):
|
|
311
|
+
raise EudiError(
|
|
312
|
+
"key_attestations_required.%s must be a list of attack potential "
|
|
313
|
+
"resistance values, got %s" % (name, type(levels).__name__))
|
|
314
|
+
if not levels:
|
|
315
|
+
raise EudiError(
|
|
316
|
+
"key_attestations_required.%s must be a non-empty array; omit the "
|
|
317
|
+
"key entirely to leave it unconstrained" % name)
|
|
318
|
+
for level in levels:
|
|
319
|
+
if not isinstance(level, str):
|
|
320
|
+
raise EudiError(
|
|
321
|
+
"key_attestations_required.%s values must be strings, got %s"
|
|
322
|
+
% (name, type(level).__name__))
|
|
323
|
+
required[name] = list(levels)
|
|
324
|
+
return required
|
|
325
|
+
|
|
278
326
|
|
|
279
327
|
def badge_credential_configuration(
|
|
280
328
|
*,
|
|
281
329
|
vct: str = OB3_SD_JWT_VCT,
|
|
282
330
|
signing_alg_values: Iterable[str] = ("ES256",),
|
|
283
331
|
proof_signing_alg_values: Optional[Iterable[str]] = None,
|
|
332
|
+
key_attestations_required: Optional[Mapping[str, Any]] = None,
|
|
284
333
|
cryptographic_binding_methods: Iterable[str] = ("jwk",),
|
|
285
334
|
display: Optional[Iterable[Mapping[str, Any]]] = None,
|
|
286
335
|
scope: Optional[str] = None,
|
|
@@ -293,7 +342,8 @@ def badge_credential_configuration(
|
|
|
293
342
|
``credential_signing_alg_values_supported``,
|
|
294
343
|
``cryptographic_binding_methods_supported``, ``proof_types_supported``
|
|
295
344
|
(``jwt``, whose ``proof_signing_alg_values_supported`` defaults to
|
|
296
|
-
*signing_alg_values*), ``claims`` and ``display``, plus ``scope``
|
|
345
|
+
*signing_alg_values*), ``claims`` and ``display``, plus ``scope`` and the
|
|
346
|
+
proof type's ``key_attestations_required`` when given.
|
|
297
347
|
Put it in your metadata document under the ``credential_configuration_id`` you
|
|
298
348
|
want wallets to request::
|
|
299
349
|
|
|
@@ -313,6 +363,18 @@ def badge_credential_configuration(
|
|
|
313
363
|
passes its own name, logo and locales. *signing_alg_values* defaults to
|
|
314
364
|
``ES256``, the algorithm HAIP pins for EUDI wallets.
|
|
315
365
|
|
|
366
|
+
*key_attestations_required* is omitted by default, and that default is
|
|
367
|
+
normative rather than conservative: OpenID4VCI 1.0 §12.2.4 says the parameter
|
|
368
|
+
"MUST NOT be present in the metadata" unless the issuer requires a key
|
|
369
|
+
attestation, and an **empty** object is not a neutral placeholder — it is the
|
|
370
|
+
positive claim "a key attestation is needed without additional constraints".
|
|
371
|
+
Emitting it unasked would have every badge issuer demand a hardware-backed
|
|
372
|
+
attestation from every holder and lock out software wallets. An issuer that
|
|
373
|
+
does require one passes ``{}``, or ``{"key_storage": [...],
|
|
374
|
+
"user_authentication": [...]}`` with the Appendix D.2 resistance values it
|
|
375
|
+
accepts; the shape is validated (:class:`EudiError`), the values are not —
|
|
376
|
+
D.2 lets each ecosystem define its own.
|
|
377
|
+
|
|
316
378
|
Two things this deliberately does NOT do. It does not build the enclosing
|
|
317
379
|
``/.well-known/openid-credential-issuer`` document — ``credential_issuer``,
|
|
318
380
|
the endpoints, the authorization servers and per-tenant display are
|
|
@@ -329,6 +391,10 @@ def badge_credential_configuration(
|
|
|
329
391
|
"""
|
|
330
392
|
algs = list(signing_alg_values)
|
|
331
393
|
proof_algs = list(proof_signing_alg_values) if proof_signing_alg_values is not None else algs
|
|
394
|
+
proof_jwt: dict[str, Any] = {"proof_signing_alg_values_supported": proof_algs}
|
|
395
|
+
if key_attestations_required is not None:
|
|
396
|
+
proof_jwt["key_attestations_required"] = _key_attestations_required(
|
|
397
|
+
key_attestations_required)
|
|
332
398
|
claims: list[dict[str, Any]] = []
|
|
333
399
|
for claim in badge_type_metadata(vct)["claims"]:
|
|
334
400
|
path = list(claim["path"])
|
|
@@ -345,9 +411,7 @@ def badge_credential_configuration(
|
|
|
345
411
|
"vct": vct,
|
|
346
412
|
"credential_signing_alg_values_supported": algs,
|
|
347
413
|
"cryptographic_binding_methods_supported": list(cryptographic_binding_methods),
|
|
348
|
-
"proof_types_supported": {
|
|
349
|
-
"jwt": {"proof_signing_alg_values_supported": proof_algs},
|
|
350
|
-
},
|
|
414
|
+
"proof_types_supported": {"jwt": proof_jwt},
|
|
351
415
|
"claims": claims,
|
|
352
416
|
"display": ([dict(d) for d in display] if display is not None
|
|
353
417
|
else [{"name": "Open Badge 3.0", "locale": locale}]),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbadgeslib
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.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
|
|
@@ -12,6 +12,7 @@ from openbadgeslib.ob3.eudi import (
|
|
|
12
12
|
DEFAULT_DISCLOSABLE,
|
|
13
13
|
OB3_SD_JWT_FORMAT,
|
|
14
14
|
OB3_SD_JWT_VCT,
|
|
15
|
+
EudiError,
|
|
15
16
|
badge_credential_configuration,
|
|
16
17
|
badge_to_sd_jwt_claims,
|
|
17
18
|
badge_type_metadata,
|
|
@@ -29,6 +30,8 @@ class TestCredentialConfigurationShape:
|
|
|
29
30
|
"proof_signing_alg_values_supported"] == ["ES256"]
|
|
30
31
|
assert cfg["display"] == [{"name": "Open Badge 3.0", "locale": "en"}]
|
|
31
32
|
assert "scope" not in cfg # only when asked for
|
|
33
|
+
assert "key_attestations_required" not in cfg[
|
|
34
|
+
"proof_types_supported"]["jwt"] # ditto — see below
|
|
32
35
|
|
|
33
36
|
def test_caller_overrides(self):
|
|
34
37
|
cfg = badge_credential_configuration(
|
|
@@ -57,6 +60,74 @@ class TestCredentialConfigurationShape:
|
|
|
57
60
|
json.loads(json.dumps(badge_credential_configuration()))
|
|
58
61
|
|
|
59
62
|
|
|
63
|
+
class TestKeyAttestationsRequired:
|
|
64
|
+
"""An empty ``key_attestations_required`` is not a placeholder: OpenID4VCI
|
|
65
|
+
1.0 §12.2.4 makes it the claim "a key attestation is needed without
|
|
66
|
+
additional constraints", and says the parameter MUST NOT be present at all
|
|
67
|
+
unless the issuer requires one. So the default is absence, and asking for it
|
|
68
|
+
is explicit."""
|
|
69
|
+
|
|
70
|
+
def _jwt(self, **kwargs):
|
|
71
|
+
return badge_credential_configuration(**kwargs)["proof_types_supported"]["jwt"]
|
|
72
|
+
|
|
73
|
+
def test_absent_by_default(self):
|
|
74
|
+
# Do not "fix" a strict wallet by emitting {} here: that would declare
|
|
75
|
+
# every badge issuer demands a key attestation, locking out the software
|
|
76
|
+
# wallets that cannot produce one.
|
|
77
|
+
assert "key_attestations_required" not in self._jwt()
|
|
78
|
+
|
|
79
|
+
def test_empty_object_means_required_without_constraints(self):
|
|
80
|
+
assert self._jwt(key_attestations_required={})[
|
|
81
|
+
"key_attestations_required"] == {}
|
|
82
|
+
|
|
83
|
+
def test_constraints_are_emitted(self):
|
|
84
|
+
jwt = self._jwt(key_attestations_required={
|
|
85
|
+
"key_storage": ["iso_18045_moderate"],
|
|
86
|
+
"user_authentication": ["iso_18045_moderate", "iso_18045_high"]})
|
|
87
|
+
assert jwt["key_attestations_required"] == {
|
|
88
|
+
"key_storage": ["iso_18045_moderate"],
|
|
89
|
+
"user_authentication": ["iso_18045_moderate", "iso_18045_high"]}
|
|
90
|
+
assert jwt["proof_signing_alg_values_supported"] == ["ES256"]
|
|
91
|
+
|
|
92
|
+
def test_is_copied_not_aliased(self):
|
|
93
|
+
mine = {"key_storage": ["iso_18045_moderate"]}
|
|
94
|
+
emitted = self._jwt(key_attestations_required=mine)[
|
|
95
|
+
"key_attestations_required"]
|
|
96
|
+
emitted["user_authentication"] = ["iso_18045_high"]
|
|
97
|
+
emitted["key_storage"].append("iso_18045_basic")
|
|
98
|
+
assert mine == {"key_storage": ["iso_18045_moderate"]}
|
|
99
|
+
|
|
100
|
+
def test_is_json_serializable(self):
|
|
101
|
+
import json
|
|
102
|
+
json.loads(json.dumps(badge_credential_configuration(
|
|
103
|
+
key_attestations_required={"key_storage": ["iso_18045_moderate"]})))
|
|
104
|
+
|
|
105
|
+
def test_unknown_key_is_rejected(self):
|
|
106
|
+
with pytest.raises(EudiError):
|
|
107
|
+
self._jwt(key_attestations_required={
|
|
108
|
+
"key_storag": ["iso_18045_moderate"]})
|
|
109
|
+
|
|
110
|
+
def test_empty_array_is_rejected(self):
|
|
111
|
+
# The spec says "non-empty array"; omitting the key is how you say
|
|
112
|
+
# "unconstrained".
|
|
113
|
+
with pytest.raises(EudiError):
|
|
114
|
+
self._jwt(key_attestations_required={"key_storage": []})
|
|
115
|
+
|
|
116
|
+
def test_bare_string_is_rejected(self):
|
|
117
|
+
# Left alone this would be emitted as a list of single letters.
|
|
118
|
+
with pytest.raises(EudiError):
|
|
119
|
+
self._jwt(key_attestations_required={
|
|
120
|
+
"user_authentication": "iso_18045_moderate"})
|
|
121
|
+
|
|
122
|
+
def test_non_string_level_is_rejected(self):
|
|
123
|
+
with pytest.raises(EudiError):
|
|
124
|
+
self._jwt(key_attestations_required={"key_storage": [4]})
|
|
125
|
+
|
|
126
|
+
def test_non_mapping_is_rejected(self):
|
|
127
|
+
with pytest.raises(EudiError):
|
|
128
|
+
self._jwt(key_attestations_required=["iso_18045_moderate"])
|
|
129
|
+
|
|
130
|
+
|
|
60
131
|
class TestNoDrift:
|
|
61
132
|
"""The reason this function exists: a hand-written copy gets the vct and the
|
|
62
133
|
claim set wrong, and a wallet then either rejects the credential or displays
|
|
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
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.1.json
RENAMED
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.2.json
RENAMED
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-context-3.0.3.json
RENAMED
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/openbadgeslib/ob3/contexts/ob-v3p0-extensions.json
RENAMED
|
File without changes
|
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/ob_schemas/ob_v3p0_profile_schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/credentials-examples-v2.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.0}/tests/fixtures/vc_di_eddsa/proof-config-hash.txt
RENAMED
|
File without changes
|
{openbadgeslib-4.1.0 → openbadgeslib-4.2.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|