Repository navigation
Expand file tree
/
Copy pathverify-execution-grant.sh
More file actions
executable file
·76 lines (72 loc) · 3.67 KB
/
Copy pathverify-execution-grant.sh
File metadata and controls
executable file
·76 lines (72 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/usr/bin/env bash
set -euo pipefail
GRANT_FILE="${1:?usage: verify-execution-grant.sh <grant.json>}"
TRUST_DIR="${A2Z_EXECUTION_GRANT_TRUST_DIR:-}"
[[ -f "$GRANT_FILE" && ! -L "$GRANT_FILE" ]] || { echo "BLOCK: grant must be a regular non-symlink file" >&2; exit 42; }
[[ -n "$TRUST_DIR" && -d "$TRUST_DIR" && ! -L "$TRUST_DIR" ]] || { echo "BLOCK: grant trust directory is unavailable" >&2; exit 40; }
[[ "$(stat -c %u -- "$TRUST_DIR")" == "0" ]] || { echo "BLOCK: grant trust directory must be root-owned" >&2; exit 43; }
TRUST_MODE="$(stat -c %a -- "$TRUST_DIR")"
(( (8#$TRUST_MODE & 0027) == 0 )) || { echo "BLOCK: grant trust permissions are too broad" >&2; exit 44; }
TRUST_PARENT="$(dirname -- "$TRUST_DIR")"
[[ -d "$TRUST_PARENT" && ! -L "$TRUST_PARENT" && "$(stat -c %u -- "$TRUST_PARENT")" == "0" ]] || {
echo "BLOCK: grant trust parent must be root-owned" >&2; exit 45;
}
PARENT_MODE="$(stat -c %a -- "$TRUST_PARENT")"
(( (8#$PARENT_MODE & 0022) == 0 )) || { echo "BLOCK: grant trust parent is writable by group/other" >&2; exit 46; }
FILE_SIZE="$(stat -c %s -- "$GRANT_FILE")"
(( FILE_SIZE <= 32768 )) || { echo "BLOCK: execution grant exceeds 32 KiB" >&2; exit 47; }
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
KEY_ID="$(python3 - "$GRANT_FILE" <<'PY'
import json, pathlib, re, sys
def unique_pairs(items):
result = {}
for key, value in items:
if key in result:
raise SystemExit("BLOCK: duplicate JSON key")
result[key] = value
return result
record = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"), object_pairs_hook=unique_pairs)
if not isinstance(record, dict) or set(record) != {"grant", "signature"}:
raise SystemExit("BLOCK: invalid execution grant envelope")
signature = record["signature"]
if not isinstance(signature, dict) or set(signature) != {"algorithm", "key_id", "signature_base64"} or signature.get("algorithm") != "ed25519":
raise SystemExit("BLOCK: invalid Ed25519 signature envelope")
key_id = signature.get("key_id")
if not isinstance(key_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,128}", key_id):
raise SystemExit("BLOCK: invalid execution grant key id")
print(key_id)
PY
)"
KEY_PATH="${TRUST_DIR}/${KEY_ID}.pem"
[[ -f "$KEY_PATH" && ! -L "$KEY_PATH" && "$(stat -c %u -- "$KEY_PATH")" == "0" ]] || {
echo "BLOCK: trusted grant issuer key is missing or not root-owned" >&2; exit 48;
}
KEY_MODE="$(stat -c %a -- "$KEY_PATH")"
(( (8#$KEY_MODE & 0022) == 0 )) || { echo "BLOCK: grant issuer key is writable by group/other" >&2; exit 49; }
python3 - "$GRANT_FILE" "$TMP_DIR" <<'PY'
import base64, hashlib, json, pathlib, sys
def unique_pairs(items):
result = {}
for key, value in items:
if key in result:
raise SystemExit("BLOCK: duplicate JSON key")
result[key] = value
return result
record = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"), object_pairs_hook=unique_pairs)
try:
signature = base64.b64decode(record["signature"]["signature_base64"], validate=True)
payload = json.dumps(
{"domain": "a2z.execution-grant.v2", "grant": record["grant"]},
sort_keys=True, separators=(",", ":"), ensure_ascii=True, allow_nan=False,
).encode("utf-8")
except (KeyError, TypeError, ValueError) as exc:
raise SystemExit(f"BLOCK: invalid execution grant encoding: {exc}")
temp = pathlib.Path(sys.argv[2])
(temp / "payload.json").write_bytes(payload)
(temp / "signature.bin").write_bytes(signature)
print(f"execution_grant_sha256={hashlib.sha256(payload).hexdigest()}")
PY
openssl pkeyutl -verify -pubin -inkey "$KEY_PATH" -rawin \
-in "$TMP_DIR/payload.json" -sigfile "$TMP_DIR/signature.bin" >/dev/null
echo "ALLOW: execution grant signature verified"