"""Sign a Plynth robot API call with Python's standard library, and send it. GATEWAY_URL=https://... GATEWAY_ROBOT_ID=... GATEWAY_ROBOT_SECRET=... python3 sign.py [METHOD PATH [BODY]] python3 sign.py --vector # the test vectors' signatures, one per line """ import hashlib import hmac import os import sys import time import urllib.error import urllib.request def sign(secret, method, path, timestamp, body=b""): """Hex HMAC-SHA256 keyed with the secret's own characters, over METHOD \\n PATH \\n TIMESTAMP \\n and then the body.""" message = f"{method}\n{path}\n{timestamp}\n".encode() + body return hmac.new(secret.encode(), message, hashlib.sha256).hexdigest() def call(method, path, body=b""): """The body is bytes, sent as signed: never serialize it again after sign().""" timestamp = str(int(time.time())) headers = {"X-Robot-Id": os.environ["GATEWAY_ROBOT_ID"], "X-Timestamp": timestamp, "X-Signature": sign(os.environ["GATEWAY_ROBOT_SECRET"], method, path, timestamp, body)} if body: headers["Content-Type"] = "application/json" request = urllib.request.Request(os.environ["GATEWAY_URL"].rstrip("/") + path, data=body or None, headers=headers, method=method) with urllib.request.urlopen(request, timeout=10) as response: return response.read().decode() if __name__ == "__main__": if sys.argv[1:] == ["--vector"]: key = "test-secret-for-docs-only-0123456789abcdef" print(sign(key, "GET", "/v1/me", "1767225600")) print(sign(key, "POST", "/v1/passages", "1767225600", b'{"passage_id":"3f2b9c1e-6a4d-4e8b-9f1a-2c7d5e0b8a14","resource_id":"door"}')) sys.exit() method = sys.argv[1] if len(sys.argv) > 1 else "GET" path = sys.argv[2] if len(sys.argv) > 2 else "/v1/me" body = sys.argv[3].encode() if len(sys.argv) > 3 else b"" try: print(call(method, path, body)) except urllib.error.HTTPError as e: sys.exit(f"HTTP {e.code}: {e.read().decode()}")