wop-python-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. wop_python_sdk-0.1.0/LICENSE +21 -0
  2. wop_python_sdk-0.1.0/PKG-INFO +139 -0
  3. wop_python_sdk-0.1.0/README.md +109 -0
  4. wop_python_sdk-0.1.0/pyproject.toml +60 -0
  5. wop_python_sdk-0.1.0/setup.cfg +4 -0
  6. wop_python_sdk-0.1.0/src/wop_python_sdk.egg-info/PKG-INFO +139 -0
  7. wop_python_sdk-0.1.0/src/wop_python_sdk.egg-info/SOURCES.txt +35 -0
  8. wop_python_sdk-0.1.0/src/wop_python_sdk.egg-info/dependency_links.txt +1 -0
  9. wop_python_sdk-0.1.0/src/wop_python_sdk.egg-info/requires.txt +8 -0
  10. wop_python_sdk-0.1.0/src/wop_python_sdk.egg-info/top_level.txt +1 -0
  11. wop_python_sdk-0.1.0/src/wop_sdk/__init__.py +39 -0
  12. wop_python_sdk-0.1.0/src/wop_sdk/canonical.py +47 -0
  13. wop_python_sdk-0.1.0/src/wop_sdk/client.py +258 -0
  14. wop_python_sdk-0.1.0/src/wop_sdk/digest.py +64 -0
  15. wop_python_sdk-0.1.0/src/wop_sdk/encoding.py +74 -0
  16. wop_python_sdk-0.1.0/src/wop_sdk/envelope.py +165 -0
  17. wop_python_sdk-0.1.0/src/wop_sdk/errors.py +48 -0
  18. wop_python_sdk-0.1.0/src/wop_sdk/keys.py +105 -0
  19. wop_python_sdk-0.1.0/src/wop_sdk/signature.py +57 -0
  20. wop_python_sdk-0.1.0/src/wop_sdk/sm2crypto.py +108 -0
  21. wop_python_sdk-0.1.0/src/wop_sdk/sm4gcm.py +86 -0
  22. wop_python_sdk-0.1.0/src/wop_sdk/suites.py +79 -0
  23. wop_python_sdk-0.1.0/src/wop_sdk/transports/__init__.py +39 -0
  24. wop_python_sdk-0.1.0/src/wop_sdk/transports/httpx_transport.py +37 -0
  25. wop_python_sdk-0.1.0/src/wop_sdk/transports/requests_transport.py +37 -0
  26. wop_python_sdk-0.1.0/src/wop_sdk/transports/urllib_transport.py +29 -0
  27. wop_python_sdk-0.1.0/tests/test_canonical.py +48 -0
  28. wop_python_sdk-0.1.0/tests/test_client.py +357 -0
  29. wop_python_sdk-0.1.0/tests/test_coverage_gaps.py +203 -0
  30. wop_python_sdk-0.1.0/tests/test_digest.py +110 -0
  31. wop_python_sdk-0.1.0/tests/test_encoding.py +99 -0
  32. wop_python_sdk-0.1.0/tests/test_envelope.py +266 -0
  33. wop_python_sdk-0.1.0/tests/test_keys.py +119 -0
  34. wop_python_sdk-0.1.0/tests/test_signature.py +165 -0
  35. wop_python_sdk-0.1.0/tests/test_suites.py +96 -0
  36. wop_python_sdk-0.1.0/tests/test_transports.py +144 -0
  37. wop_python_sdk-0.1.0/tests/test_transports_real.py +101 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wop-platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: wop-python-sdk
3
+ Version: 0.1.0
4
+ Summary: WOP 商户侧官方 Python SDK:协议核心(签名/摘要/数字信封/验签解密)+ 可插拔 HTTP 适配层
5
+ Author: wop-platform
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/wop-platform/wop-python-sdk
8
+ Keywords: wop,gateway,sm2,sm4-gcm,rsa,digital-envelope
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Security :: Cryptography
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: cryptography>=41
24
+ Requires-Dist: gmssl>=3.2.2
25
+ Provides-Extra: httpx
26
+ Requires-Dist: httpx>=0.24; extra == "httpx"
27
+ Provides-Extra: requests
28
+ Requires-Dist: requests>=2.28; extra == "requests"
29
+ Dynamic: license-file
30
+
31
+ # WOP Python SDK
32
+
33
+ WOP 网关商户侧官方 Python 客户端库:封装协议核心(套件解析 / 结构化签名 / 内容摘要 /
34
+ L2 数字信封 / 验签解密)与可插拔 HTTP 适配层,使商户无需理解 canonicalRequest、
35
+ 算法套件推导与线上字节格式即可安全对接网关。
36
+
37
+ - 协议真源:[crypto-strategy-spec.md](https://github.com/wop-platform/wop-specs/blob/main/crypto/crypto-strategy-spec.md)(v0.3-reviewed)+ [wop-sdk-spec.md](https://github.com/wop-platform/wop-specs/blob/main/sdk/wop-sdk-spec.md)(v1.0-ratified)
38
+ - 向量真源:[crypto-vectors.json](https://github.com/wop-platform/wop-specs/blob/main/crypto/crypto-vectors.json)(本仓 fixture 为字节级副本,禁手改)
39
+ - Python ≥ 3.9
40
+ - 三套件全支持:`WOP-RSA3072-SHA256` / `WOP-RSA4096-SHA256` / `WOP-SM2-SM3`
41
+ - 密码依赖(唯一指定路径):`cryptography`(RSA/AES)+ `gmssl ≥ 3.2.2`(SM2/SM3/SM4)
42
+ - 主包零额外依赖;HTTP 适配器以 peer 依赖交付(`wop-python-sdk[httpx]` / `wop-python-sdk[requests]`)
43
+
44
+ ## 快速开始
45
+
46
+ ```bash
47
+ pip install wop-python-sdk # 或从源码:pip install -e .
48
+ pip install 'wop-python-sdk[httpx]' # 可选:httpx peer 适配器(另含 requests extras)
49
+ ```
50
+
51
+ ```python
52
+ from wop_sdk import WopClient, WopConfig
53
+ from wop_sdk.transports import UrllibTransport, send_draft
54
+
55
+ client = WopClient(WopConfig(
56
+ app_key="app_10012481831",
57
+ suite="WOP-RSA3072-SHA256", # 或 WOP-RSA4096-SHA256 / WOP-SM2-SM3
58
+ merchant_private_key=MERCHANT_PRIV_PEM, # 商户私钥(PEM 或 Base64 单行)
59
+ platform_public_key=PLATFORM_PUB_PEM, # 平台公钥(PEM 或 Base64 单行)
60
+ gateway_base_url="https://wop.example.com",
61
+ ))
62
+
63
+ # L0 明文请求
64
+ draft = client.build_request("POST", "/gateway/order.create", {"orderId": 42})
65
+
66
+ # 发送(任意 HTTP 栈;此处 stdlib urllib 适配器)
67
+ resp = send_draft(UrllibTransport(), client._config.gateway_base_url, draft)
68
+
69
+ # 校验平台响应(F6 固定顺序:验签 → digest 复核 → DEK 解包 → alg 族比对 → bulk 解密)
70
+ result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
71
+ if result.ok:
72
+ print(result.plaintext)
73
+ else:
74
+ print(result.reason) # 验签/解密失败对外模糊(I7),格式/完整性/一致性类明确
75
+ ```
76
+
77
+ ## 密钥准备
78
+
79
+ 密钥入参为字符串(PEM 或 Base64 单行),SDK 内部解析(D12 分发契约):
80
+
81
+ | 套件 | 商户私钥 | 平台公钥 | 约束 |
82
+ |------|----------|----------|------|
83
+ | `WOP-RSA3072-SHA256` | PKCS#8 DER,Base64/PEM | X.509 SPKI DER,Base64/PEM | 密钥必须 3072 位 |
84
+ | `WOP-RSA4096-SHA256` | 同上 | 同上 | 密钥必须 4096 位 |
85
+ | `WOP-SM2-SM3` | `d` 标量 32 字节,Base64 | 未压缩点 `04‖X‖Y` 65 字节,Base64 | 点必须在 sm2p256v1 曲线上(I5) |
86
+
87
+ - RSA 公钥与私钥均接受 PEM 包装(`-----BEGIN PUBLIC KEY-----`)或裸 Base64;
88
+ - SM2 材料喂给 RSA 套件(或反向)在配置期即拒绝;跨族算法组合(如 `WOP-RSA3072-SM3`)
89
+ 在套件解析期拒绝。
90
+
91
+ ## L0 / L2 示例
92
+
93
+ ### L0(明文,摘要为唯一完整性防线)
94
+
95
+ ```python
96
+ draft = client.build_request("POST", "/gateway/order.query", {"orderId": 42})
97
+ # 有 body 必产 x-wop-content-digest 且必入 signedHeaders(D2/I1);GET 无 body 则缺席
98
+ ```
99
+
100
+ ### L2(数字信封:AES-256-GCM / SM4-GCM 全文加密)
101
+
102
+ ```python
103
+ draft = client.build_request("POST", "/gateway/order.create", {"card": "6222..."}, level="L2")
104
+ # wire_body = {"encrypted":"<base64url(ciphertext||tag)>"}
105
+ # x-wop-encrypt: L2;dek=<base64url(OAEP/SM2 包装的 DEK 载荷)>
106
+ # DEK 与 IV 每次调用 CSPRNG 新生成(I4:同一密钥下 IV 永不复用)
107
+
108
+ result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
109
+ # result.plaintext = 解密后的业务报文
110
+
111
+ # 回调校验(URI 取回调 path,方法恒 POST)
112
+ cb = client.verify_callback(cb_headers, cb_body, "/callback/notify")
113
+ ```
114
+
115
+ 线上字节格式(F7/D9/D10):全部 base64url **无填充**(严格拒收 `=`);
116
+ RSA 签名 = PKCS#1 v1.5;SM2 签名 = 裸 `r‖s` 64 字节(禁 DER);
117
+ SM2 密文 = `C1C3C2` 裸拼接(C1 = 未压缩点 65B);RSA-OAEP = 显式双 SHA-256 + 空 label。
118
+
119
+ ## 向量自测
120
+
121
+ 黄金向量 fixture 位于 `tests/fixtures/crypto-vectors.json`(与网关真源字节级一致,
122
+ 禁手改)。本地复跑 conformance 套件:
123
+
124
+ ```bash
125
+ pip install -e '.[httpx]' coverage
126
+ python3 -m pytest --cov=wop_sdk --cov-branch --cov-fail-under=98
127
+ ```
128
+
129
+ 覆盖:RSA3072/4096 与 SM2 签名字节级断言、OAEP 包装/解包、AES-256-GCM 与 SM4-GCM
130
+ 密文字节级断言、SM3/SHA-256 摘要、DEK 载荷组装、digest 头全部格式规则;负向量含
131
+ tamper / 跨族 / 63B、65B 签名 / 带 `=` 的 base64url / C1C2C3 旧国标顺序 /
132
+ MGF1-SHA1 陷阱密文,全部必须拒绝。CI(3.9–3.14 矩阵)执行同一命令。
133
+
134
+ ## 错误处理与模糊化
135
+
136
+ - **明确**(公开协议知识,帮助集成自查):套件格式/跨族、密钥材料、digest 头格式与
137
+ 不匹配、DEK alg 与套件族不符;
138
+ - **模糊**(依赖密钥参与,防 oracle,I7):签名验证失败、解密失败——对外消息不区分
139
+ tag 失败 / 密钥不符等原因细节。
@@ -0,0 +1,109 @@
1
+ # WOP Python SDK
2
+
3
+ WOP 网关商户侧官方 Python 客户端库:封装协议核心(套件解析 / 结构化签名 / 内容摘要 /
4
+ L2 数字信封 / 验签解密)与可插拔 HTTP 适配层,使商户无需理解 canonicalRequest、
5
+ 算法套件推导与线上字节格式即可安全对接网关。
6
+
7
+ - 协议真源:[crypto-strategy-spec.md](https://github.com/wop-platform/wop-specs/blob/main/crypto/crypto-strategy-spec.md)(v0.3-reviewed)+ [wop-sdk-spec.md](https://github.com/wop-platform/wop-specs/blob/main/sdk/wop-sdk-spec.md)(v1.0-ratified)
8
+ - 向量真源:[crypto-vectors.json](https://github.com/wop-platform/wop-specs/blob/main/crypto/crypto-vectors.json)(本仓 fixture 为字节级副本,禁手改)
9
+ - Python ≥ 3.9
10
+ - 三套件全支持:`WOP-RSA3072-SHA256` / `WOP-RSA4096-SHA256` / `WOP-SM2-SM3`
11
+ - 密码依赖(唯一指定路径):`cryptography`(RSA/AES)+ `gmssl ≥ 3.2.2`(SM2/SM3/SM4)
12
+ - 主包零额外依赖;HTTP 适配器以 peer 依赖交付(`wop-python-sdk[httpx]` / `wop-python-sdk[requests]`)
13
+
14
+ ## 快速开始
15
+
16
+ ```bash
17
+ pip install wop-python-sdk # 或从源码:pip install -e .
18
+ pip install 'wop-python-sdk[httpx]' # 可选:httpx peer 适配器(另含 requests extras)
19
+ ```
20
+
21
+ ```python
22
+ from wop_sdk import WopClient, WopConfig
23
+ from wop_sdk.transports import UrllibTransport, send_draft
24
+
25
+ client = WopClient(WopConfig(
26
+ app_key="app_10012481831",
27
+ suite="WOP-RSA3072-SHA256", # 或 WOP-RSA4096-SHA256 / WOP-SM2-SM3
28
+ merchant_private_key=MERCHANT_PRIV_PEM, # 商户私钥(PEM 或 Base64 单行)
29
+ platform_public_key=PLATFORM_PUB_PEM, # 平台公钥(PEM 或 Base64 单行)
30
+ gateway_base_url="https://wop.example.com",
31
+ ))
32
+
33
+ # L0 明文请求
34
+ draft = client.build_request("POST", "/gateway/order.create", {"orderId": 42})
35
+
36
+ # 发送(任意 HTTP 栈;此处 stdlib urllib 适配器)
37
+ resp = send_draft(UrllibTransport(), client._config.gateway_base_url, draft)
38
+
39
+ # 校验平台响应(F6 固定顺序:验签 → digest 复核 → DEK 解包 → alg 族比对 → bulk 解密)
40
+ result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
41
+ if result.ok:
42
+ print(result.plaintext)
43
+ else:
44
+ print(result.reason) # 验签/解密失败对外模糊(I7),格式/完整性/一致性类明确
45
+ ```
46
+
47
+ ## 密钥准备
48
+
49
+ 密钥入参为字符串(PEM 或 Base64 单行),SDK 内部解析(D12 分发契约):
50
+
51
+ | 套件 | 商户私钥 | 平台公钥 | 约束 |
52
+ |------|----------|----------|------|
53
+ | `WOP-RSA3072-SHA256` | PKCS#8 DER,Base64/PEM | X.509 SPKI DER,Base64/PEM | 密钥必须 3072 位 |
54
+ | `WOP-RSA4096-SHA256` | 同上 | 同上 | 密钥必须 4096 位 |
55
+ | `WOP-SM2-SM3` | `d` 标量 32 字节,Base64 | 未压缩点 `04‖X‖Y` 65 字节,Base64 | 点必须在 sm2p256v1 曲线上(I5) |
56
+
57
+ - RSA 公钥与私钥均接受 PEM 包装(`-----BEGIN PUBLIC KEY-----`)或裸 Base64;
58
+ - SM2 材料喂给 RSA 套件(或反向)在配置期即拒绝;跨族算法组合(如 `WOP-RSA3072-SM3`)
59
+ 在套件解析期拒绝。
60
+
61
+ ## L0 / L2 示例
62
+
63
+ ### L0(明文,摘要为唯一完整性防线)
64
+
65
+ ```python
66
+ draft = client.build_request("POST", "/gateway/order.query", {"orderId": 42})
67
+ # 有 body 必产 x-wop-content-digest 且必入 signedHeaders(D2/I1);GET 无 body 则缺席
68
+ ```
69
+
70
+ ### L2(数字信封:AES-256-GCM / SM4-GCM 全文加密)
71
+
72
+ ```python
73
+ draft = client.build_request("POST", "/gateway/order.create", {"card": "6222..."}, level="L2")
74
+ # wire_body = {"encrypted":"<base64url(ciphertext||tag)>"}
75
+ # x-wop-encrypt: L2;dek=<base64url(OAEP/SM2 包装的 DEK 载荷)>
76
+ # DEK 与 IV 每次调用 CSPRNG 新生成(I4:同一密钥下 IV 永不复用)
77
+
78
+ result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
79
+ # result.plaintext = 解密后的业务报文
80
+
81
+ # 回调校验(URI 取回调 path,方法恒 POST)
82
+ cb = client.verify_callback(cb_headers, cb_body, "/callback/notify")
83
+ ```
84
+
85
+ 线上字节格式(F7/D9/D10):全部 base64url **无填充**(严格拒收 `=`);
86
+ RSA 签名 = PKCS#1 v1.5;SM2 签名 = 裸 `r‖s` 64 字节(禁 DER);
87
+ SM2 密文 = `C1C3C2` 裸拼接(C1 = 未压缩点 65B);RSA-OAEP = 显式双 SHA-256 + 空 label。
88
+
89
+ ## 向量自测
90
+
91
+ 黄金向量 fixture 位于 `tests/fixtures/crypto-vectors.json`(与网关真源字节级一致,
92
+ 禁手改)。本地复跑 conformance 套件:
93
+
94
+ ```bash
95
+ pip install -e '.[httpx]' coverage
96
+ python3 -m pytest --cov=wop_sdk --cov-branch --cov-fail-under=98
97
+ ```
98
+
99
+ 覆盖:RSA3072/4096 与 SM2 签名字节级断言、OAEP 包装/解包、AES-256-GCM 与 SM4-GCM
100
+ 密文字节级断言、SM3/SHA-256 摘要、DEK 载荷组装、digest 头全部格式规则;负向量含
101
+ tamper / 跨族 / 63B、65B 签名 / 带 `=` 的 base64url / C1C2C3 旧国标顺序 /
102
+ MGF1-SHA1 陷阱密文,全部必须拒绝。CI(3.9–3.14 矩阵)执行同一命令。
103
+
104
+ ## 错误处理与模糊化
105
+
106
+ - **明确**(公开协议知识,帮助集成自查):套件格式/跨族、密钥材料、digest 头格式与
107
+ 不匹配、DEK alg 与套件族不符;
108
+ - **模糊**(依赖密钥参与,防 oracle,I7):签名验证失败、解密失败——对外消息不区分
109
+ tag 失败 / 密钥不符等原因细节。
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wop-python-sdk"
7
+ version = "0.1.0"
8
+ description = "WOP 商户侧官方 Python SDK:协议核心(签名/摘要/数字信封/验签解密)+ 可插拔 HTTP 适配层"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "wop-platform" }]
12
+ requires-python = ">=3.9"
13
+ keywords = ["wop", "gateway", "sm2", "sm4-gcm", "rsa", "digital-envelope"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Security :: Cryptography",
26
+ ]
27
+ # 白名单(任务书 E5):cryptography + gmssl;除此之外零运行时依赖
28
+ dependencies = [
29
+ "cryptography>=41",
30
+ "gmssl>=3.2.2",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ # peer 适配器(不污染核心依赖面)
35
+ httpx = ["httpx>=0.24"]
36
+ requests = ["requests>=2.28"]
37
+
38
+ [dependency-groups]
39
+ # 发布构建前端(PEP 735,不进发布元数据):
40
+ # pip install --group dev && python -m build
41
+ dev = ["build"]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/wop-platform/wop-python-sdk"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+ pythonpath = ["src"]
52
+ addopts = "-q"
53
+
54
+ [tool.coverage.run]
55
+ source = ["wop_sdk"]
56
+ branch = true
57
+
58
+ [tool.coverage.report]
59
+ show_missing = true
60
+ precision = 2
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: wop-python-sdk
3
+ Version: 0.1.0
4
+ Summary: WOP 商户侧官方 Python SDK:协议核心(签名/摘要/数字信封/验签解密)+ 可插拔 HTTP 适配层
5
+ Author: wop-platform
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/wop-platform/wop-python-sdk
8
+ Keywords: wop,gateway,sm2,sm4-gcm,rsa,digital-envelope
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Security :: Cryptography
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: cryptography>=41
24
+ Requires-Dist: gmssl>=3.2.2
25
+ Provides-Extra: httpx
26
+ Requires-Dist: httpx>=0.24; extra == "httpx"
27
+ Provides-Extra: requests
28
+ Requires-Dist: requests>=2.28; extra == "requests"
29
+ Dynamic: license-file
30
+
31
+ # WOP Python SDK
32
+
33
+ WOP 网关商户侧官方 Python 客户端库:封装协议核心(套件解析 / 结构化签名 / 内容摘要 /
34
+ L2 数字信封 / 验签解密)与可插拔 HTTP 适配层,使商户无需理解 canonicalRequest、
35
+ 算法套件推导与线上字节格式即可安全对接网关。
36
+
37
+ - 协议真源:[crypto-strategy-spec.md](https://github.com/wop-platform/wop-specs/blob/main/crypto/crypto-strategy-spec.md)(v0.3-reviewed)+ [wop-sdk-spec.md](https://github.com/wop-platform/wop-specs/blob/main/sdk/wop-sdk-spec.md)(v1.0-ratified)
38
+ - 向量真源:[crypto-vectors.json](https://github.com/wop-platform/wop-specs/blob/main/crypto/crypto-vectors.json)(本仓 fixture 为字节级副本,禁手改)
39
+ - Python ≥ 3.9
40
+ - 三套件全支持:`WOP-RSA3072-SHA256` / `WOP-RSA4096-SHA256` / `WOP-SM2-SM3`
41
+ - 密码依赖(唯一指定路径):`cryptography`(RSA/AES)+ `gmssl ≥ 3.2.2`(SM2/SM3/SM4)
42
+ - 主包零额外依赖;HTTP 适配器以 peer 依赖交付(`wop-python-sdk[httpx]` / `wop-python-sdk[requests]`)
43
+
44
+ ## 快速开始
45
+
46
+ ```bash
47
+ pip install wop-python-sdk # 或从源码:pip install -e .
48
+ pip install 'wop-python-sdk[httpx]' # 可选:httpx peer 适配器(另含 requests extras)
49
+ ```
50
+
51
+ ```python
52
+ from wop_sdk import WopClient, WopConfig
53
+ from wop_sdk.transports import UrllibTransport, send_draft
54
+
55
+ client = WopClient(WopConfig(
56
+ app_key="app_10012481831",
57
+ suite="WOP-RSA3072-SHA256", # 或 WOP-RSA4096-SHA256 / WOP-SM2-SM3
58
+ merchant_private_key=MERCHANT_PRIV_PEM, # 商户私钥(PEM 或 Base64 单行)
59
+ platform_public_key=PLATFORM_PUB_PEM, # 平台公钥(PEM 或 Base64 单行)
60
+ gateway_base_url="https://wop.example.com",
61
+ ))
62
+
63
+ # L0 明文请求
64
+ draft = client.build_request("POST", "/gateway/order.create", {"orderId": 42})
65
+
66
+ # 发送(任意 HTTP 栈;此处 stdlib urllib 适配器)
67
+ resp = send_draft(UrllibTransport(), client._config.gateway_base_url, draft)
68
+
69
+ # 校验平台响应(F6 固定顺序:验签 → digest 复核 → DEK 解包 → alg 族比对 → bulk 解密)
70
+ result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
71
+ if result.ok:
72
+ print(result.plaintext)
73
+ else:
74
+ print(result.reason) # 验签/解密失败对外模糊(I7),格式/完整性/一致性类明确
75
+ ```
76
+
77
+ ## 密钥准备
78
+
79
+ 密钥入参为字符串(PEM 或 Base64 单行),SDK 内部解析(D12 分发契约):
80
+
81
+ | 套件 | 商户私钥 | 平台公钥 | 约束 |
82
+ |------|----------|----------|------|
83
+ | `WOP-RSA3072-SHA256` | PKCS#8 DER,Base64/PEM | X.509 SPKI DER,Base64/PEM | 密钥必须 3072 位 |
84
+ | `WOP-RSA4096-SHA256` | 同上 | 同上 | 密钥必须 4096 位 |
85
+ | `WOP-SM2-SM3` | `d` 标量 32 字节,Base64 | 未压缩点 `04‖X‖Y` 65 字节,Base64 | 点必须在 sm2p256v1 曲线上(I5) |
86
+
87
+ - RSA 公钥与私钥均接受 PEM 包装(`-----BEGIN PUBLIC KEY-----`)或裸 Base64;
88
+ - SM2 材料喂给 RSA 套件(或反向)在配置期即拒绝;跨族算法组合(如 `WOP-RSA3072-SM3`)
89
+ 在套件解析期拒绝。
90
+
91
+ ## L0 / L2 示例
92
+
93
+ ### L0(明文,摘要为唯一完整性防线)
94
+
95
+ ```python
96
+ draft = client.build_request("POST", "/gateway/order.query", {"orderId": 42})
97
+ # 有 body 必产 x-wop-content-digest 且必入 signedHeaders(D2/I1);GET 无 body 则缺席
98
+ ```
99
+
100
+ ### L2(数字信封:AES-256-GCM / SM4-GCM 全文加密)
101
+
102
+ ```python
103
+ draft = client.build_request("POST", "/gateway/order.create", {"card": "6222..."}, level="L2")
104
+ # wire_body = {"encrypted":"<base64url(ciphertext||tag)>"}
105
+ # x-wop-encrypt: L2;dek=<base64url(OAEP/SM2 包装的 DEK 载荷)>
106
+ # DEK 与 IV 每次调用 CSPRNG 新生成(I4:同一密钥下 IV 永不复用)
107
+
108
+ result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
109
+ # result.plaintext = 解密后的业务报文
110
+
111
+ # 回调校验(URI 取回调 path,方法恒 POST)
112
+ cb = client.verify_callback(cb_headers, cb_body, "/callback/notify")
113
+ ```
114
+
115
+ 线上字节格式(F7/D9/D10):全部 base64url **无填充**(严格拒收 `=`);
116
+ RSA 签名 = PKCS#1 v1.5;SM2 签名 = 裸 `r‖s` 64 字节(禁 DER);
117
+ SM2 密文 = `C1C3C2` 裸拼接(C1 = 未压缩点 65B);RSA-OAEP = 显式双 SHA-256 + 空 label。
118
+
119
+ ## 向量自测
120
+
121
+ 黄金向量 fixture 位于 `tests/fixtures/crypto-vectors.json`(与网关真源字节级一致,
122
+ 禁手改)。本地复跑 conformance 套件:
123
+
124
+ ```bash
125
+ pip install -e '.[httpx]' coverage
126
+ python3 -m pytest --cov=wop_sdk --cov-branch --cov-fail-under=98
127
+ ```
128
+
129
+ 覆盖:RSA3072/4096 与 SM2 签名字节级断言、OAEP 包装/解包、AES-256-GCM 与 SM4-GCM
130
+ 密文字节级断言、SM3/SHA-256 摘要、DEK 载荷组装、digest 头全部格式规则;负向量含
131
+ tamper / 跨族 / 63B、65B 签名 / 带 `=` 的 base64url / C1C2C3 旧国标顺序 /
132
+ MGF1-SHA1 陷阱密文,全部必须拒绝。CI(3.9–3.14 矩阵)执行同一命令。
133
+
134
+ ## 错误处理与模糊化
135
+
136
+ - **明确**(公开协议知识,帮助集成自查):套件格式/跨族、密钥材料、digest 头格式与
137
+ 不匹配、DEK alg 与套件族不符;
138
+ - **模糊**(依赖密钥参与,防 oracle,I7):签名验证失败、解密失败——对外消息不区分
139
+ tag 失败 / 密钥不符等原因细节。
@@ -0,0 +1,35 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/wop_python_sdk.egg-info/PKG-INFO
5
+ src/wop_python_sdk.egg-info/SOURCES.txt
6
+ src/wop_python_sdk.egg-info/dependency_links.txt
7
+ src/wop_python_sdk.egg-info/requires.txt
8
+ src/wop_python_sdk.egg-info/top_level.txt
9
+ src/wop_sdk/__init__.py
10
+ src/wop_sdk/canonical.py
11
+ src/wop_sdk/client.py
12
+ src/wop_sdk/digest.py
13
+ src/wop_sdk/encoding.py
14
+ src/wop_sdk/envelope.py
15
+ src/wop_sdk/errors.py
16
+ src/wop_sdk/keys.py
17
+ src/wop_sdk/signature.py
18
+ src/wop_sdk/sm2crypto.py
19
+ src/wop_sdk/sm4gcm.py
20
+ src/wop_sdk/suites.py
21
+ src/wop_sdk/transports/__init__.py
22
+ src/wop_sdk/transports/httpx_transport.py
23
+ src/wop_sdk/transports/requests_transport.py
24
+ src/wop_sdk/transports/urllib_transport.py
25
+ tests/test_canonical.py
26
+ tests/test_client.py
27
+ tests/test_coverage_gaps.py
28
+ tests/test_digest.py
29
+ tests/test_encoding.py
30
+ tests/test_envelope.py
31
+ tests/test_keys.py
32
+ tests/test_signature.py
33
+ tests/test_suites.py
34
+ tests/test_transports.py
35
+ tests/test_transports_real.py
@@ -0,0 +1,8 @@
1
+ cryptography>=41
2
+ gmssl>=3.2.2
3
+
4
+ [httpx]
5
+ httpx>=0.24
6
+
7
+ [requests]
8
+ requests>=2.28
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+ """WOP 商户侧官方 Python SDK。
3
+
4
+ 协议核心(签名/摘要/数字信封/验签解密,纯函数、零网络 IO)+ 可插拔 HTTP 适配层。
5
+ """
6
+ from .client import RequestDraft, VerifyResult, WopClient, WopConfig
7
+ from .errors import (
8
+ DecryptError,
9
+ DekConsistencyError,
10
+ DigestMismatchError,
11
+ KeyMaterialError,
12
+ ProtocolFormatError,
13
+ SignatureVerifyError,
14
+ SuiteParseError,
15
+ UnsupportedSuiteError,
16
+ WopSdkError,
17
+ )
18
+ from .suites import Suite, parse_suite
19
+
20
+ __version__ = "0.1.0"
21
+
22
+ __all__ = [
23
+ "WopClient",
24
+ "WopConfig",
25
+ "RequestDraft",
26
+ "VerifyResult",
27
+ "Suite",
28
+ "parse_suite",
29
+ "WopSdkError",
30
+ "SuiteParseError",
31
+ "UnsupportedSuiteError",
32
+ "ProtocolFormatError",
33
+ "KeyMaterialError",
34
+ "DigestMismatchError",
35
+ "SignatureVerifyError",
36
+ "DecryptError",
37
+ "DekConsistencyError",
38
+ "__version__",
39
+ ]
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ """canonicalRequest 构造(F2)。
3
+
4
+ 结构(5 段 '\\n' 连接,对照网关 CanonicalRequestBuilder):
5
+
6
+ authString\\nHTTPMethod\\ncanonicalURI\\ncanonicalQueryString\\ncanonicalHeaders
7
+
8
+ POST 的 canonicalQueryString 为空字符串(分隔空行不可省略);
9
+ header 值编码 = Java URLEncoder 语义(空格 → %20)。
10
+ """
11
+ from typing import Dict, Optional
12
+
13
+ from .encoding import java_urlencode, trimall
14
+
15
+
16
+ def canonical_headers(headers: Optional[Dict[str, str]]) -> str:
17
+ """规范标头:名称 lowercase+trimall+urlencode,值 trimall+urlencode,
18
+ 名称 ASCII 升序,行间 '\\n' 连接,尾行不加 '\\n'。"""
19
+ if not headers:
20
+ return ""
21
+ normalized = {}
22
+ for name, value in headers.items():
23
+ normalized[trimall(name).lower()] = trimall(value)
24
+ return "\n".join(
25
+ "%s:%s" % (java_urlencode(k), java_urlencode(normalized[k]))
26
+ for k in sorted(normalized)
27
+ )
28
+
29
+
30
+ def build_canonical(
31
+ auth_string: Optional[str],
32
+ method: Optional[str],
33
+ canonical_uri: Optional[str],
34
+ query_string: Optional[str],
35
+ canonical_headers: Optional[str],
36
+ ) -> str:
37
+ """组装规范请求(5 段);None 段落输出空串,保持 5 段结构。"""
38
+ safe_method = (method or "").strip().upper()
39
+ return "\n".join(
40
+ [
41
+ auth_string or "",
42
+ safe_method,
43
+ canonical_uri or "",
44
+ query_string or "",
45
+ canonical_headers or "",
46
+ ]
47
+ )