|
| 1 | +#!/usr/bin/env python3 |
| 2 | +import json, argparse, base64, os, datetime as dt, hashlib |
| 3 | +from cryptography.hazmat.primitives import hashes, serialization |
| 4 | +from cryptography.hazmat.primitives.asymmetric import ec, utils |
| 5 | + |
| 6 | +def b64url(data: bytes) -> str: |
| 7 | + return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii") |
| 8 | + |
| 9 | +def b64url_json(obj) -> str: |
| 10 | + return b64url(json.dumps(obj, separators=(",", ":"), sort_keys=True).encode("utf-8")) |
| 11 | + |
| 12 | +def load_key(path): |
| 13 | + with open(path, "rb") as f: |
| 14 | + return serialization.load_pem_private_key(f.read(), password=None) |
| 15 | + |
| 16 | +def main(): |
| 17 | + ap = argparse.ArgumentParser(description="UltimateDRM license signer (ES256)") |
| 18 | + ap.add_argument("--priv", required=True, help="EC P-256 private key PEM (vendor)") |
| 19 | + ap.add_argument("--org", required=True) |
| 20 | + ap.add_argument("--plan", required=True, choices=["edu-floating","named-device","site"]) |
| 21 | + ap.add_argument("--seats", type=int, default=None, help="None for site") |
| 22 | + ap.add_argument("--days", type=int, default=365, help="Validity days") |
| 23 | + ap.add_argument("--features", nargs="*", default=["rules-signing","gpu-gate","webhook"]) |
| 24 | + ap.add_argument("--key-id", default="v1") |
| 25 | + args = ap.parse_args() |
| 26 | + |
| 27 | + now = dt.datetime.utcnow() |
| 28 | + exp = now + dt.timedelta(days=args.days) |
| 29 | + |
| 30 | + header = {"alg":"ES256","kid":args.key_id,"typ":"JWT"} |
| 31 | + payload = { |
| 32 | + "iss":"UltimateDRM", |
| 33 | + "product":"UltimateDRM/agent", |
| 34 | + "org":args.org, |
| 35 | + "plan":args.plan, |
| 36 | + "seats":args.seats, |
| 37 | + "features":args.features, |
| 38 | + "iat":int(now.timestamp()), |
| 39 | + "exp":int(exp.timestamp()), |
| 40 | + "jti": b64url(os.urandom(12)), |
| 41 | + } |
| 42 | + |
| 43 | + signing_input = (b64url_json(header) + "." + b64url_json(payload)).encode("ascii") |
| 44 | + |
| 45 | + key = load_key(args.priv) |
| 46 | + h = hashlib.sha256(signing_input).digest() |
| 47 | + sig = key.sign(h, ec.ECDSA(utils.Prehashed(hashes.SHA256()))) |
| 48 | + token = signing_input.decode("ascii") + "." + b64url(sig) |
| 49 | + |
| 50 | + print(token) |
| 51 | + |
| 52 | +if __name__ == "__main__": |
| 53 | + main() |
0 commit comments