50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Vector Zulu — LICK Signing Module
|
|
CONFIRMED WORKING: raw UTF-8 key bytes, no sort_keys on canonicalize.
|
|
Verified 2026-07-14 against korechain.korenet.cloud.
|
|
"""
|
|
import base64, hashlib, hmac, json, os, time, uuid
|
|
from typing import Optional
|
|
|
|
SIGNER_ID = 'vector-zulu'
|
|
|
|
def _get_lick_key() -> bytes:
|
|
key_raw = (
|
|
os.environ.get('VECTOR_ZULU_LICK_KEY') or
|
|
os.environ.get('KORE_LICK_HMAC_KEY') or
|
|
os.environ.get('LICK_HMAC_SECRET', '')
|
|
)
|
|
if not key_raw:
|
|
raise ValueError('LICK key not configured — set VECTOR_ZULU_LICK_KEY')
|
|
# Key is raw string — encode to bytes directly (NOT base64 decoded)
|
|
return key_raw.encode('utf-8')
|
|
|
|
def canonicalize(payload: dict) -> str:
|
|
"""Compact JSON — NO sort_keys (server does not sort)."""
|
|
return json.dumps(payload, separators=(',', ':'))
|
|
|
|
def sign_canonical(payload: dict, timestamp: Optional[int] = None) -> str:
|
|
if timestamp is None:
|
|
timestamp = int(time.time())
|
|
key = _get_lick_key()
|
|
canonical = canonicalize(payload)
|
|
msg = f"{canonical}\n{timestamp}"
|
|
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).hexdigest()
|
|
|
|
def build_headers(payload: dict, variant: str = 'canonical') -> dict:
|
|
ts = int(time.time())
|
|
nonce = str(uuid.uuid4())
|
|
sig = sign_canonical(payload, ts)
|
|
return {
|
|
'x-lick-ts': str(ts),
|
|
'x-lick-sig': sig,
|
|
'x-lick-nonce': nonce,
|
|
'x-lick-signer': SIGNER_ID,
|
|
'x-lick-variant': variant,
|
|
'x-sentinel-trace': str(uuid.uuid4()),
|
|
'Idempotency-Key': str(uuid.uuid4()),
|
|
'X-Tenant-Id': 'vector-zulu',
|
|
'Content-Type': 'application/json',
|
|
}
|