"""vdrpack: build, sign and verify VDR packages (.vdr) for the ViKernel App Store.
python vdrpack.py init
[--name NAME --publisher ID] scaffold an app and a DEMO publisher key
python vdrpack.py build zip the app and write META-VK/HASHES
python vdrpack.py sign [] ed25519 publisher signature
python vdrpack.py verify [--store-key ] format, hashes, signatures
The demo key written by `init` is for testing only. Never commit a private key.
"""
# --- VDR package library (self-contained copy) ---
import hashlib, io, json, zipfile
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
META = "META-VK/"
SIGNED_META = {"META-VK/HASHES", "META-VK/SIGNATURE", "META-VK/PUBLISHER"}
REQUIRED = ["manifest.json", "icon.png"]
TOOL_RISK = {"get_weather": 1, "search_web": 1, "list_files": 1, "read_file": 2, "app_run": 2,
"save_note": 3, "send_email": 4, "http_post": 5, "upload": 5, "run_shell": 9, "transfer_funds": 9}
def sha(b):
return hashlib.sha256(b).hexdigest()
def hashes_text(files):
return "".join("%s %s\n" % (sha(files[p]), p) for p in sorted(files) if not p.startswith(META)).encode()
def priv_from_hex(h):
return Ed25519PrivateKey.from_private_bytes(bytes.fromhex(h))
def pub_hex(priv):
from cryptography.hazmat.primitives import serialization as s
return priv.public_key().public_bytes(s.Encoding.Raw, s.PublicFormat.Raw).hex()
def write_zip(files):
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
for p in sorted(files):
zi = zipfile.ZipInfo(p, date_time=(2026, 11, 2, 0, 0, 0))
z.writestr(zi, files[p])
return buf.getvalue()
def read_zip(data):
with zipfile.ZipFile(io.BytesIO(data)) as z:
return {i.filename: z.read(i.filename) for i in z.infolist() if not i.is_dir()}
def sign(files, priv, publisher_id):
files = {p: b for p, b in files.items() if not p.startswith(META)}
h = hashes_text(files)
files["META-VK/HASHES"] = h
files["META-VK/SIGNATURE"] = priv.sign(h).hex().encode()
files["META-VK/PUBLISHER"] = json.dumps({"id": publisher_id, "public_key": pub_hex(priv)}).encode()
return files
def store_digest(files):
return hashlib.sha256(files["META-VK/HASHES"] + files["META-VK/SIGNATURE"]).digest()
def countersign(files, store_priv):
files = dict(files)
files["META-VK/STORE_SIG"] = store_priv.sign(store_digest(files)).hex().encode()
return files
def check(data, store_pub_hex=None, require_store=False):
"""Format, hash list, publisher signature (and store signature if asked). Returns (ok, steps, files)."""
steps = []
def step(name, ok, detail):
steps.append({"check": name, "ok": bool(ok), "detail": detail})
return ok
try:
files = read_zip(data)
except Exception as e: # noqa: BLE001
step("format", False, "not a valid zip: " + type(e).__name__)
return False, steps, None
allowed_meta = SIGNED_META | ({"META-VK/STORE_SIG"} if "META-VK/STORE_SIG" in files else set())
missing = [p for p in REQUIRED + sorted(SIGNED_META) if p not in files]
if not any(p.startswith("app/") for p in files):
missing.append("app/")
bad = [p for p in files if p.startswith(META) and p not in allowed_meta]
try:
man = json.loads(files.get("manifest.json", b"{}"))
need = ["name", "version", "publisher", "permissions", "rate_limits"]
miss_f = [k for k in need if k not in man]
perms = man.get("permissions", {})
bad_f = [] if isinstance(perms, dict) and isinstance(perms.get("tools", []), list) else ["permissions"]
except ValueError:
man, miss_f, bad_f = {}, ["manifest.json is not JSON"], []
if not step("format", not missing and not bad and not miss_f and not bad_f,
"ok: manifest + app/ + icon.png + META-VK" if not (missing or bad or miss_f or bad_f)
else "missing %s; unexpected %s; manifest fields missing %s %s" % (missing, bad, miss_f, bad_f)):
return False, steps, files
listed = {}
for line in files["META-VK/HASHES"].decode("utf-8", "replace").splitlines():
if " " in line:
h, p = line.split(" ", 1)
listed[p] = h
content = {p for p in files if not p.startswith(META)}
extra, absent = sorted(content - set(listed)), sorted(set(listed) - content)
changed = sorted(p for p in content & set(listed) if sha(files[p]) != listed[p])
if not step("hash list", not (extra or absent or changed),
"%d files, every sha256 matches" % len(listed) if not (extra or absent or changed)
else "changed %s; not listed %s; listed but missing %s" % (changed, extra, absent)):
return False, steps, files
try:
pub = json.loads(files["META-VK/PUBLISHER"])
Ed25519PublicKey.from_public_bytes(bytes.fromhex(pub["public_key"])).verify(
bytes.fromhex(files["META-VK/SIGNATURE"].decode().strip()), files["META-VK/HASHES"])
ok, det = True, "ed25519 valid for publisher %s (key %s...)" % (pub.get("id"), pub["public_key"][:12])
except Exception as e: # noqa: BLE001
ok, det = False, "publisher signature invalid (%s)" % type(e).__name__
if not step("publisher signature", ok, det):
return False, steps, files
if require_store:
try:
Ed25519PublicKey.from_public_bytes(bytes.fromhex(store_pub_hex)).verify(
bytes.fromhex(files["META-VK/STORE_SIG"].decode().strip()), store_digest(files))
ok, det = True, "store countersignature valid"
except Exception as e: # noqa: BLE001
ok, det = False, "no valid store countersignature (%s): package does not install" % type(e).__name__
if not step("store signature", ok, det):
return False, steps, files
return True, steps, files
def risk(man):
p = man.get("permissions", {})
tools = p.get("tools", [])
score = sum(TOOL_RISK.get(t, 6) for t in tools) + 2 * len(p.get("egress", [])) + len(p.get("paths", []))
level = "low" if score <= 6 else "medium" if score <= 14 else "high"
return score, level
class _V: pass
vdr = _V()
for _n in ("sha","hashes_text","priv_from_hex","pub_hex","write_zip","read_zip","sign","store_digest","countersign","check","risk","TOOL_RISK"):
setattr(vdr, _n, globals()[_n])
import base64, json, os, sys
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey # noqa: E402
ICON = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==")
def init(d, name="my-app", publisher="demo-publisher"):
os.makedirs(os.path.join(d, "app"), exist_ok=True)
man = {"name": name, "version": "1.0.0", "publisher": publisher,
"description": "Example agent app",
"permissions": {"tools": ["get_weather"], "egress": [], "paths": []},
"rate_limits": {"per_minute": 30}}
json.dump(man, open(os.path.join(d, "manifest.json"), "w"), indent=2)
json.dump({"steps": [{"tool": "get_weather", "args": {"city": "{city}"}}]}, open(os.path.join(d, "app", "workflow.json"), "w"), indent=2)
open(os.path.join(d, "icon.png"), "wb").write(ICON)
k = os.path.join(d, "publisher_demo.key")
if not os.path.exists(k):
open(k, "w").write(Ed25519PrivateKey.generate().private_bytes_raw().hex())
print("initialized", d, "(demo key:", k, "- do not commit)")
def build(d, out):
files = {}
for root, _, names in os.walk(d):
for n in names:
p = os.path.relpath(os.path.join(root, n), d).replace(os.sep, "/")
if p.endswith(".key") or p.startswith("META-VK/"):
continue
files[p] = open(os.path.join(root, n), "rb").read()
files["META-VK/HASHES"] = vdr.hashes_text(files)
open(out, "wb").write(vdr.write_zip(files))
print("built", out, len(files) - 1, "files")
def sign(src, keyfile, pid, out=None):
files = vdr.read_zip(open(src, "rb").read())
files = vdr.sign(files, vdr.priv_from_hex(open(keyfile).read().strip()), pid)
open(out or src, "wb").write(vdr.write_zip(files))
print("signed", out or src, "as", pid)
def verify(src, store_key=None):
ok, steps, _ = vdr.check(open(src, "rb").read(), store_key, require_store=bool(store_key))
for s in steps:
print(("PASS " if s["ok"] else "FAIL ") + s["check"] + ": " + s["detail"])
print("VERIFIED" if ok else "REJECTED")
return ok
if __name__ == "__main__":
a = sys.argv[1:]
if not a:
print(__doc__); sys.exit(1)
if a[0] == "init":
kw = {"name": a[a.index("--name") + 1]} if "--name" in a else {}
if "--publisher" in a:
kw["publisher"] = a[a.index("--publisher") + 1]
init(a[1], **kw)
elif a[0] == "build":
build(a[1], a[2])
elif a[0] == "sign":
sign(*a[1:5])
elif a[0] == "verify":
sys.exit(0 if verify(a[1], a[a.index("--store-key") + 1] if "--store-key" in a else None) else 2)
else:
print(__doc__); sys.exit(1)