#!/usr/bin/env bash # The proxy's binary end to end, as an operator runs it: keys, a commitment list from a CPIX # document, an entitlement, the proxy in front of a licence server and the key server, then # a refused request, a granted one, an HLS key file by ticket, and the whole log verified. # The licence server is a fixture written here that answers every request with fixed bytes. # # bash smoke.sh [] # # Exit 0 only if every check holds. Needs curl and python3. set -euo pipefail here="$(cd "$(dirname "$0")" && pwd)" bin="${1:-${CARGO_TARGET_DIR:-$here/target}/release/wai-key-proxy}" corpus="$here/../live-conformance/keys" t="$(mktemp -d)" pids=() cleanup() { for p in ${pids[@]+"${pids[@]}"}; do kill "$p" 2>/dev/null || true; wait "$p" 2>/dev/null || true; done rm -rf "$t" } trap cleanup EXIT fail() { echo "FAIL $*"; exit 1; } pass() { echo "PASS $*"; } port() { python3 -I -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1])'; } wait_up() { for _ in $(seq 1 100); do curl -s -o /dev/null "$1" && return 0; sleep 0.1; done; fail "$1 did not come up"; } holder=$("$bin" keygen "$t/holder.key") authority=$("$bin" keygen "$t/authority.key") proxy=$("$bin" keygen "$t/proxy.key") device=$("$bin" keygen "$t/device.key") [ "$(stat -c %a "$t/proxy.key" 2>/dev/null || stat -f %Lp "$t/proxy.key")" = 600 ] || fail "a key file is not mode 0600" pass "keys written, mode 0600" cp "$corpus/film.cpix.xml" "$corpus/episode.cpix.xml" "$t/" "$bin" list "$t/film.cpix.xml" svod:premium "$t/holder.key" https://packager.example "$t/film.wkl1" >/dev/null "$bin" list "$t/episode.cpix.xml" svod:basic "$t/holder.key" https://packager.example "$t/episode.wkl1" >/dev/null pass "commitment lists derived from the CPIX documents" now=$(date +%s) premium=$("$bin" entitle "$t/authority.key" https://entitlements.example "$device" svod:premium $((now - 60)) $((now + 3600))) basic=$("$bin" entitle "$t/authority.key" https://entitlements.example "$device" svod:basic $((now - 60)) $((now + 3600))) lp=$(port); pp=$(port); ap=$(port); kp=$(port) python3 -I -c ' import http.server, sys class H(http.server.BaseHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers.get("content-length", 0))) self.send_response(200); self.send_header("content-type", "application/octet-stream"); self.end_headers() self.wfile.write(b"LICENCE:" + body) def log_message(self, *a): pass http.server.HTTPServer(("127.0.0.1", int(sys.argv[1])), H).serve_forever() ' "$lp" & pids+=($!) cat > "$t/keys.json" < "$t/proxy.json" <"$t/keyserver.log" & pids+=($!) "$bin" serve "$t/proxy.json" 2>"$t/proxy.log" & pids+=($!) wait_up "http://127.0.0.1:$pp/v1/health" wait_up "http://127.0.0.1:$kp/v1/key" pass "the proxy, the key server and the licence server are up" kids=$(python3 -I -c 'import json,sys; d=json.load(open(sys.argv[1])); print(",".join(k["kid"] for v in d["vectors"] if v["name"]=="release_drm" for k in v["log"][0]["keys"]))' "$corpus/vectors.json") printf 'licence challenge' > "$t/challenge" # The signed header fields of a request, as curl arguments in the array h. sign() { h=() while IFS= read -r line; do h+=(-H "$line"); done < <("$bin" sign-request "$t/device.key" "$1" "$2" "$3" "$4" streaming "$5" "$6") [ "${#h[@]}" -gt 0 ] || fail "sign-request wrote nothing" } code=$(curl -s -o "$t/refused.json" -w '%{http_code}' -X POST --data-binary @"$t/challenge" \ -H "WAI-Key-Ids: $kids" "http://127.0.0.1:$pp/v1/licence/film.a") [ "$code" = 403 ] || fail "a request with no entitlement answered $code" grep -q '"decision":"DeniedEntitlementInvalid"' "$t/refused.json" || fail "the refusal's receipt" pass "a request with no entitlement is refused with its receipt" sign "$basic" film-0001 drm edef8ba9-79d6-4ace-a3c8-27dcd51d21ed "$kids" "$t/challenge" code=$(curl -s -o "$t/wrong.json" -w '%{http_code}' -X POST --data-binary @"$t/challenge" "${h[@]}" "http://127.0.0.1:$pp/v1/licence/film.a") [ "$code" = 403 ] && grep -q '"decision":"DeniedNoEntitlement"' "$t/wrong.json" || fail "the wrong class answered $code" pass "the wrong entitlement class is refused" sign "$premium" film-0001 drm edef8ba9-79d6-4ace-a3c8-27dcd51d21ed "$kids" "$t/challenge" code=$(curl -s -D "$t/granted.h" -o "$t/licence" -w '%{http_code}' -X POST --data-binary @"$t/challenge" "${h[@]}" "http://127.0.0.1:$pp/v1/licence/film.a") [ "$code" = 200 ] && [ "$(cat "$t/licence")" = "LICENCE:licence challenge" ] || fail "a granted request answered $code" id=$(tr -d '\r' < "$t/granted.h" | awk -F': ' 'tolower($1)=="wai-key-receipt"{print $2}') curl -sf "http://127.0.0.1:$pp/v1/receipts/$id" | grep -q '"released":true' || fail "the release's receipt" pass "a granted request is forwarded and its licence returned, with its receipt" hkid=$(python3 -I -c 'import json,sys; d=json.load(open(sys.argv[1])); print([v for v in d["vectors"] if v["name"]=="release_hls_key_uri"][0]["log"][0]["keys"][0]["kid"])' "$corpus/vectors.json") sign "$basic" episode-0002 hls-key-uri - "$hkid" - loc=$(curl -s -o /dev/null -w '%{redirect_url}' "${h[@]}" "http://127.0.0.1:$pp/v1/licence/episode.hls") [ -n "$loc" ] || fail "no ticket" curl -sf -o "$t/key.bin" "$loc" || fail "the key server did not redeem the ticket" [ "$(wc -c < "$t/key.bin" | tr -d ' ')" = 16 ] || fail "the key file is not 16 octets" code=$(curl -s -o /dev/null -w '%{http_code}' "$loc") [ "$code" = 409 ] || fail "a ticket redeemed twice answered $code" keyhex=$(od -An -tx1 "$t/key.bin" | tr -d ' \n') ! grep -q "$keyhex" "$t/receipts.jsonl" || fail "the key is in the proxy's log" pass "an HLS key file is released by a ticket the key server redeems once; the key is not in the proxy's log" curl -sf -o "$t/log.jsonl" "http://127.0.0.1:$ap/v1/log" || fail "the log listener" out=$("$bin" verify "$t/log.jsonl" "$authority" "$proxy" "$t/film.wkl1" "$t/episode.wkl1") || fail "the log does not verify: $out" echo "$out" | grep -q '^accept: 4 receipts, 2 released' || fail "the log states: $out" pass "the whole log verifies: $out"