test_inbound_signature.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. """签名金标向量与 HMAC/SHA256 守卫(任务书 P1-H)。
  2. 金标向量由独立实现(Node.js crypto)生成,与 C# 端 InboundSignatureHandler 同一算法:
  3. message = METHOD&path&accessKey&timestamp&nonce&bodySha256&idempotencyKey
  4. signature = Base64(HMAC-SHA256(secret, message))
  5. """
  6. from __future__ import annotations
  7. from clients.inbound_client import InboundClient, build_message, sha256_hex, sign
  8. EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  9. GOLDEN = {
  10. "secret": "sim-secret-golden",
  11. "method": "POST",
  12. "path": "/api/mdp/inbound/S1_SALES_ORDER_ENTRY",
  13. "accessKey": "SIMKEY01",
  14. "timestamp": "1750000000",
  15. "nonce": "nonce-fixed-0001",
  16. "bodySha256": EMPTY_SHA256,
  17. "idempotencyKey": "IDEM-GOLDEN-0001",
  18. "signature": "caR7gxdSziU4IXI/kX4u8X11tOq0jGRmzzNHn8rEEAU=",
  19. }
  20. def test_sha256_empty_body():
  21. assert sha256_hex(b"") == EMPTY_SHA256
  22. def test_signature_golden_vector():
  23. message = build_message(
  24. GOLDEN["method"], GOLDEN["path"], GOLDEN["accessKey"], GOLDEN["timestamp"],
  25. GOLDEN["nonce"], GOLDEN["bodySha256"], GOLDEN["idempotencyKey"])
  26. assert message == (
  27. "POST&/api/mdp/inbound/S1_SALES_ORDER_ENTRY&SIMKEY01&1750000000"
  28. "&nonce-fixed-0001&" + EMPTY_SHA256 + "&IDEM-GOLDEN-0001")
  29. assert sign(GOLDEN["secret"], message) == GOLDEN["signature"]
  30. def test_signature_changes_with_secret():
  31. message = build_message("POST", "/api/mdp/inbound/MDM_ITEM", "K", "1", "n", EMPTY_SHA256, "I")
  32. assert sign("secret-a", message) != sign("secret-b", message)
  33. def test_request_headers_do_not_leak_signature():
  34. """sent_headers 中签名必须遮蔽;Secret 不出现在任何返回头。"""
  35. client = InboundClient("http://127.0.0.1:1", "SIMKEY01", GOLDEN["secret"])
  36. # 目标端口 1 必然连接失败,但 headers 在异常前已构造并返回
  37. status, body, headers = client.request(
  38. "GET", "/api/mdp/inbound/MDM_ITEM/schema", b"",
  39. idem=GOLDEN["idempotencyKey"], ts=int(GOLDEN["timestamp"]),
  40. nonce=GOLDEN["nonce"], contract_version="v2")
  41. assert status == 0
  42. assert headers["X-Signature"] == "***REDACTED***"
  43. assert headers["X-Access-Key"] == "SIMKEY01"
  44. assert headers["X-Content-SHA256"] == EMPTY_SHA256
  45. assert headers["X-Mdp-Contract-Version"] == "v2"
  46. assert GOLDEN["signature"] not in str(headers)
  47. for value in headers.values():
  48. assert GOLDEN["secret"] not in str(value)
  49. assert "SOURCE_UNREACHABLE" in str(body)
  50. def test_missing_idempotency_header_when_skipped():
  51. client = InboundClient("http://127.0.0.1:1", "SIMKEY01", "s")
  52. _, _, headers = client.request("POST", "/api/mdp/inbound/MDM_ITEM", b"{}", skip_idem=True)
  53. assert "Idempotency-Key" not in headers