ep-verify 0.1.0__py3-none-any.whl
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.
ep_verify/__init__.py
ADDED
ep_verify/cli.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""ep-verify <receipt.json> [--keys keys.json]
|
|
3
|
+
|
|
4
|
+
Verifies one EP-RECEIPT-v1 document fully offline against issuer public
|
|
5
|
+
key(s) you pin (base64url SPKI, a JSON string, array, or {kid: key} map).
|
|
6
|
+
Prints VERIFIED or REFUSED plus a machine-readable JSON line; exit 0 only
|
|
7
|
+
on VERIFIED. Fail closed: any error, missing key, or malformed input
|
|
8
|
+
refuses. Verification proves signature/binding/anchor integrity against
|
|
9
|
+
the pinned keys; it never proves the action was correct or sufficient.
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from emilia_verify import verify_receipt
|
|
15
|
+
|
|
16
|
+
USAGE = "usage: ep-verify <receipt.json> [--keys keys.json]"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_keys(path):
|
|
20
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
21
|
+
data = json.load(f)
|
|
22
|
+
if isinstance(data, str):
|
|
23
|
+
return [data]
|
|
24
|
+
if isinstance(data, list):
|
|
25
|
+
return [k for k in data if isinstance(k, str)]
|
|
26
|
+
if isinstance(data, dict):
|
|
27
|
+
return [v for v in data.values() if isinstance(v, str)]
|
|
28
|
+
return []
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main(argv=None):
|
|
32
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
33
|
+
if not argv or argv[0] in ("-h", "--help"):
|
|
34
|
+
print(__doc__ if argv and argv[0] in ("-h", "--help") else USAGE)
|
|
35
|
+
return 0 if argv else 1
|
|
36
|
+
receipt_path, keys_path = argv[0], None
|
|
37
|
+
if "--keys" in argv:
|
|
38
|
+
i = argv.index("--keys")
|
|
39
|
+
if i + 1 >= len(argv):
|
|
40
|
+
print("REFUSED")
|
|
41
|
+
print(json.dumps({"result": "refused", "reason": "keys_flag_without_path"}))
|
|
42
|
+
return 1
|
|
43
|
+
keys_path = argv[i + 1]
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
with open(receipt_path, "r", encoding="utf-8") as f:
|
|
47
|
+
doc = json.load(f)
|
|
48
|
+
except Exception:
|
|
49
|
+
print("REFUSED")
|
|
50
|
+
print(json.dumps({"result": "refused", "reason": "receipt_unreadable_or_malformed"}))
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
keys = []
|
|
54
|
+
if keys_path:
|
|
55
|
+
try:
|
|
56
|
+
keys = _load_keys(keys_path)
|
|
57
|
+
except Exception:
|
|
58
|
+
print("REFUSED")
|
|
59
|
+
print(json.dumps({"result": "refused", "reason": "keys_unreadable_or_malformed"}))
|
|
60
|
+
return 1
|
|
61
|
+
if not keys:
|
|
62
|
+
print("REFUSED")
|
|
63
|
+
print(json.dumps({"result": "refused", "reason": "no_pinned_keys"}))
|
|
64
|
+
return 1
|
|
65
|
+
|
|
66
|
+
last_error = None
|
|
67
|
+
for key in keys:
|
|
68
|
+
try:
|
|
69
|
+
result = verify_receipt(doc, key)
|
|
70
|
+
except Exception as e: # verifier crash on hostile input = refusal
|
|
71
|
+
last_error = f"verifier_error:{type(e).__name__}"
|
|
72
|
+
continue
|
|
73
|
+
if getattr(result, "valid", False) or (isinstance(result, dict) and result.get("valid")):
|
|
74
|
+
checks = getattr(result, "checks", None) or (result.get("checks") if isinstance(result, dict) else None)
|
|
75
|
+
print("VERIFIED")
|
|
76
|
+
print(json.dumps({"result": "verified", "receipt_id": (doc.get("payload") or {}).get("receipt_id"), "checks": checks}, default=str))
|
|
77
|
+
return 0
|
|
78
|
+
err = getattr(result, "error", None) or (result.get("error") if isinstance(result, dict) else None)
|
|
79
|
+
last_error = err or "signature_invalid"
|
|
80
|
+
|
|
81
|
+
print("REFUSED")
|
|
82
|
+
print(json.dumps({"result": "refused", "reason": last_error or "no_key_verified"}, default=str))
|
|
83
|
+
return 1
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
sys.exit(main())
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ep-verify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: One-line offline verifier for EMILIA Protocol authorization receipts (EP-RECEIPT-v1). Thin CLI over emilia-verify: proves signature, binding, and anchor integrity against keys you pin; never business correctness.
|
|
5
|
+
Project-URL: Homepage, https://www.emiliaprotocol.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/emiliaprotocol/emilia-protocol
|
|
7
|
+
Author-email: Iman Schrock <team@emiliaprotocol.ai>
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: emilia-verify>=2.2.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# ep-verify
|
|
14
|
+
|
|
15
|
+
One-line offline verifier for EMILIA Protocol authorization receipts
|
|
16
|
+
(EP-RECEIPT-v1). A thin CLI over [`emilia-verify`](https://pypi.org/project/emilia-verify/),
|
|
17
|
+
so `ep-verify` is the same verb on PyPI and npm.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install ep-verify
|
|
21
|
+
ep-verify receipt.json --keys keys.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Prints `VERIFIED` or `REFUSED` plus one machine-readable JSON line;
|
|
25
|
+
exit code 0 only on VERIFIED. Fully offline, fail closed: a missing key,
|
|
26
|
+
unreadable file, or verifier error is a refusal, never a pass.
|
|
27
|
+
|
|
28
|
+
What VERIFIED means: the Ed25519 signature over the canonical (RFC 8785)
|
|
29
|
+
payload validates against a key **you** pinned, and any Merkle anchor is
|
|
30
|
+
consistent. What it does NOT mean: that the action was correct, sufficient,
|
|
31
|
+
or accepted by any relying party; acceptance is a relying-party decision.
|
|
32
|
+
|
|
33
|
+
Apache-2.0. Part of the EMILIA Protocol reference tooling:
|
|
34
|
+
https://github.com/emiliaprotocol/emilia-protocol
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
ep_verify/__init__.py,sha256=5Zz0vQOwVBykyZA3d9aYh7ukHCBnfqpt3oGVgsUm5GY,218
|
|
2
|
+
ep_verify/cli.py,sha256=q0DYcnnJMAOBPbDxJp3i8oFvbL33Sp0oN1592FYNmEg,3203
|
|
3
|
+
ep_verify-0.1.0.dist-info/METADATA,sha256=D3w3jKKAdhonxn_zeg_HEfbuDh4RzxysU_SPnFRB0Xo,1487
|
|
4
|
+
ep_verify-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
ep_verify-0.1.0.dist-info/entry_points.txt,sha256=_J9YEtB6NfeBBXirEmBuJbQcGL2HBzVbrWaL5dGUMA4,49
|
|
6
|
+
ep_verify-0.1.0.dist-info/RECORD,,
|