Metadata-Version: 2.4
Name: wop-python-sdk
Version: 0.1.6
Summary: WOP 商户侧官方 Python SDK：协议核心（签名/摘要/数字信封/验签解密）+ 可插拔 HTTP 适配层
Author: wop-platform
License: MIT
Project-URL: Homepage, https://github.com/wop-platform/wop-python-sdk
Keywords: wop,gateway,sm2,sm4-gcm,rsa,digital-envelope
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=41
Requires-Dist: gmssl>=3.2.2
Provides-Extra: httpx
Requires-Dist: httpx>=0.24; extra == "httpx"
Provides-Extra: requests
Requires-Dist: requests>=2.28; extra == "requests"
Dynamic: license-file

# WOP Python SDK

WOP 网关商户侧官方 Python 客户端库：封装协议核心（套件解析 / 结构化签名 / 内容摘要 /
L2 数字信封 / 验签解密）与可插拔 HTTP 适配层，使商户无需理解 canonicalRequest、
算法套件推导与线上字节格式即可安全对接网关。

- 协议真源：[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）
- 向量真源：[crypto-vectors.json](https://github.com/wop-platform/wop-specs/blob/main/crypto/crypto-vectors.json)（本仓 fixture 为字节级副本，禁手改）
- Python ≥ 3.9
- 三套件全支持：`WOP-RSA3072-SHA256` / `WOP-RSA4096-SHA256` / `WOP-SM2-SM3`
- 密码依赖（唯一指定路径）：`cryptography`（RSA/AES）+ `gmssl ≥ 3.2.2`（SM2/SM3/SM4）
- 主包零额外依赖；HTTP 适配器以 peer 依赖交付（`wop-python-sdk[httpx]` / `wop-python-sdk[requests]`）

## 快速开始

```bash
pip install wop-python-sdk            # 或从源码：pip install -e .
pip install 'wop-python-sdk[httpx]'   # 可选：httpx peer 适配器（另含 requests extras）
```

```python
from wop_sdk import WopClient, WopConfig
from wop_sdk.transports import UrllibTransport, send_draft

client = WopClient(WopConfig(
    app_key="app_10012481831",
    suite="WOP-RSA3072-SHA256",            # 或 WOP-RSA4096-SHA256 / WOP-SM2-SM3
    merchant_private_key=MERCHANT_PRIV_PEM,  # 商户私钥（PEM 或 Base64 单行）
    platform_public_key=PLATFORM_PUB_PEM,    # 平台公钥（PEM 或 Base64 单行）
    gateway_base_url="https://wop.example.com",
))

# L0 明文请求
draft = client.build_request("POST", "/gateway/order.create", {"orderId": 42})

# 发送（任意 HTTP 栈；此处 stdlib urllib 适配器）
resp = send_draft(UrllibTransport(), client._config.gateway_base_url, draft)

# 校验平台响应（F6 固定顺序：验签 → digest 复核 → DEK 解包 → alg 族比对 → bulk 解密）
result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
if result.ok:
    print(result.plaintext)
else:
    print(result.reason)  # 验签/解密失败对外模糊（I7），格式/完整性/一致性类明确
```

## 密钥准备

密钥入参为字符串（PEM 或 Base64 单行），SDK 内部解析（D12 分发契约）：

| 套件 | 商户私钥 | 平台公钥 | 约束 |
|------|----------|----------|------|
| `WOP-RSA3072-SHA256` | PKCS#8 DER，Base64/PEM | X.509 SPKI DER，Base64/PEM | 密钥必须 3072 位 |
| `WOP-RSA4096-SHA256` | 同上 | 同上 | 密钥必须 4096 位 |
| `WOP-SM2-SM3` | `d` 标量 32 字节，Base64 | 未压缩点 `04‖X‖Y` 65 字节，Base64 | 点必须在 sm2p256v1 曲线上（I5） |

- RSA 公钥与私钥均接受 PEM 包装（`-----BEGIN PUBLIC KEY-----`）或裸 Base64；
- SM2 材料喂给 RSA 套件（或反向）在配置期即拒绝；跨族算法组合（如 `WOP-RSA3072-SM3`）
  在套件解析期拒绝。

## L0 / L2 示例

### L0（明文，摘要为唯一完整性防线）

```python
draft = client.build_request("POST", "/gateway/order.query", {"orderId": 42})
# 有 body 必产 x-wop-content-digest 且必入 signedHeaders（D2/I1）；GET 无 body 则缺席
```

### L2（数字信封：AES-256-GCM / SM4-GCM 全文加密）

```python
draft = client.build_request("POST", "/gateway/order.create", {"card": "6222..."}, level="L2")
# wire_body = {"encrypted":"<base64url(ciphertext||tag)>"}
# x-wop-encrypt: L2;dek=<base64url(OAEP/SM2 包装的 DEK 载荷)>
# DEK 与 IV 每次调用 CSPRNG 新生成（I4：同一密钥下 IV 永不复用）

result = client.verify_response(resp.headers, resp.body, "/gateway/order.create")
# result.plaintext = 解密后的业务报文

# 回调校验（URI 取回调 path，方法恒 POST）
cb = client.verify_callback(cb_headers, cb_body, "/callback/notify")
```

线上字节格式（F7/D9/D10）：全部 base64url **无填充**（严格拒收 `=`）；
RSA 签名 = PKCS#1 v1.5；SM2 签名 = 裸 `r‖s` 64 字节（禁 DER）；
SM2 密文 = `C1C3C2` 裸拼接（C1 = 未压缩点 65B）；RSA-OAEP = 显式双 SHA-256 + 空 label。

## 向量自测

黄金向量 fixture 位于 `tests/fixtures/crypto-vectors.json`（与网关真源字节级一致，
禁手改）。本地复跑 conformance 套件：

```bash
pip install -e '.[httpx]' coverage
python3 -m pytest --cov=wop_sdk --cov-branch --cov-fail-under=98
```

覆盖：RSA3072/4096 与 SM2 签名字节级断言、OAEP 包装/解包、AES-256-GCM 与 SM4-GCM
密文字节级断言、SM3/SHA-256 摘要、DEK 载荷组装、digest 头全部格式规则；负向量含
tamper / 跨族 / 63B、65B 签名 / 带 `=` 的 base64url / C1C2C3 旧国标顺序 /
MGF1-SHA1 陷阱密文，全部必须拒绝。CI（3.9–3.14 矩阵）执行同一命令。

## 错误处理与模糊化

- **明确**（公开协议知识，帮助集成自查）：套件格式/跨族、密钥材料、digest 头格式与
  不匹配、DEK alg 与套件族不符；
- **模糊**（依赖密钥参与，防 oracle，I7）：签名验证失败、解密失败——对外消息不区分
  tag 失败 / 密钥不符等原因细节。
