wop

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 28 Imported by: 0

README

WOP Go SDK

Go Reference CI Mutation Gate Release

Coverage Mutation Kill Gherkin CodeRabbit Pull Request Reviews

WOP 网关商户侧官方 Go 客户端库:封装协议核心(套件解析、canonicalRequest、结构化签名、 content-digest、L2 数字信封、验签解密)与 HTTP 适配层,商户无需理解线上字节格式即可安全对接。

  • 协议真源:crypto-strategy-spec.md(v0.3-reviewed)+ wop-sdk-spec.md(v1.0-ratified)
  • 向量真源:crypto-vectors.json(本仓 fixture 为字节级副本,禁手改)
  • 三套件全支持:WOP-RSA3072-SHA256 / WOP-RSA4096-SHA256(crypto/rsa)与 WOP-SM2-SM3(emmansun/gmsm)
  • 零非白名单依赖:运行时依赖仅 emmansun/gmsm(国密算法唯一指定路径,勿用 tjfoc/gmsm——无 GCM)

快速开始

package main

import (
	"fmt"

	wop "github.com/wop-platform/wop-go-sdk"
)

func main() {
	client, err := wop.NewClient(wop.Config{
		AppKey:             "app_test_001",
		SecurityReq:        "WOP-RSA3072-SHA256", // 或 WOP-RSA4096-SHA256 / WOP-SM2-SM3
		MerchantPrivateKey: merchantPrivateKeyPEM,
		PlatformPublicKey:  platformPublicKeyPEM,
		GatewayBaseURL:     "https://wop.example.com",
	})
	if err != nil {
		panic(err) // 配置类错误,语义明确,可直接用于接入自查
	}

	// 一站式:L2 加密 + 签名 → 发送 → F6 校验(验签→digest→DEK 解包→alg 族比对→解密)
	result, resp, err := client.Do("POST", "/gateway/logistics.order.query",
		[]byte(`{"waybillNo":"W202607200001"}`), wop.Level2)
	if err != nil {
		if we, ok := err.(*wop.Error); ok {
			fmt.Println("错误码:", we.Code) // 可编程处理;验签/解密类错误对外模糊(I7)
		}
		return
	}
	fmt.Println("HTTP", resp.StatusCode, "明文:", string(result.Plaintext))
}

自带 HTTP 栈时直接消费 RequestDraft(纯函数产出,零网络 IO):

draft, err := client.BuildRequest("POST", "/gateway/secure.api", body, wop.Level2)
// draft.Headers / draft.WireBody 交给任意 HTTP 客户端发送

回调校验(平台 → 商户,canonical URI 取回调 path):

res := client.VerifyCallback(callbackURL, http.Header(req.Header), body)
if !res.OK {
	log.Printf("回调校验失败: [%s] %s", res.Code, res.Reason)
	return
}
handle(res.Plaintext)

密钥准备(D12 分发契约)

密钥 格式 说明
RSA 商户私钥 PKCS#8 DER,PEM 或 Base64 单行 位数须与套件一致(3072/4096),不符报配置类错误
RSA 平台公钥 X.509 SPKI DER,PEM 或 Base64 单行 平台下发格式
SM2 商户私钥 32 字节大端标量 d,Base64 04‖X‖Y 对应公钥由 d·G 派生校验
SM2 平台公钥 未压缩点 04‖X‖Y(65 字节),Base64 解析时校验在 sm2p256v1 曲线上

密钥入参一律为字符串(PEM 块或 Base64 单行,容忍折行);解析失败全部返回配置类 wop.Error(明确指出问题),帮助商户自查。RSA 公钥分发统一 SPKI;SM2 线上格式三钉: 签名裸 r‖s 64 字节、密文 C1C3C2 裸拼接、禁 ASN.1/DER。

L0 / L2 示例

// L0 明文:仅签名 + content-digest(无 body 时 digest 头缺席,D2)
draft, _ := client.BuildRequest("POST", "/gateway/open.api", body, wop.Level0)

// L2 全文数字信封:
//   CSPRNG CEK(32B AES / 16B SM4) + IV(12B) → GCM 加密 → {"encrypted":"<b64url>"} 信封
//   DEK 载荷 alg$key$iv 经平台公钥包装(RSA-OAEP 双 SHA-256+空 label / SM2)→ x-wop-encrypt: L2;dek=...
//   content-digest 覆盖密文载体(摘要对象 = 线上原始字节)
draft, _ = client.BuildRequest("POST", "/gateway/secure.api", body, wop.Level2)

可测试性钩子:WithTimestamp / WithNonce / WithRandom 注入确定性随机源后 BuildRequest 同输入字节级一致(幂等重放断言);生产环境请勿固定随机源—— GCM 同密钥下 IV 复用即协议不变式 I4 违规。

向量自测(conformance)

黄金向量 fixture internal/testdata/crypto-vectors.json 是协议真源的字节副本(禁手改), 本地与 CI 消费同一副本:

go test ./... -covermode=atomic -coverprofile=cover.out -coverpkg=.   # 全量测试(含向量 conformance)
go tool cover -func=cover.out | grep total                # 语句覆盖率 ≥98%(当前 99.1%)

覆盖面(spec 附录 B.2 / D9):

  • 正向量字节级:SHA-256/SM3 摘要、RSA3072/4096 签名(PKCS#1 v1.5 确定性)、 SM2 fixed-k 签名(裸 r‖s)与加密(C1C3C2)、AES-256-GCM / SM4-GCM(ciphertext‖tag)、 OAEP 解包、DEK 载荷组装
  • 负向量全拒:MGF1-SHA1 陷阱密文(F2 头号跨语言漂移源)、C1C2C3 旧国标顺序、 63/65 字节签名、DER 签名、带 = 的 base64url、跨族 digest 标签/securityReq、 篡改签名/密文/摘要
  • 不变式矩阵:invariants_test.gospec:<ID> 注释索引逐条对账 (D2/I1/I2/I3/I5/I7/F2/F7/D9),Go 无原生分支计数,以显式负向量清单替代(spec §3 约定)

错误处理与模糊化(I7)

Code 分类 对外语义
CONFIG / SUITE_PARSE / SUITE_UNSUPPORTED / PROTOCOL 配置/解析/支持/协议 明确指出问题(鉴权前可判定的公开协议知识)
DIGEST_MISMATCH 完整性 明确"摘要不匹配"
VERIFY_FAILED 验签 模糊:"签名验证失败"(固定文案,无细节)
DECRYPT_FAILED 解密 模糊:"解密失败"(不区分 tag 失败/密钥不符,防 oracle)
ALG_MISMATCH 一致性 明确(公开映射知识,I3 允许提前拒)

Transport

// 默认 net/http 适配器
wop.DefaultTransport{HTTPClient: http.DefaultClient, BaseURL: "https://wop.example.com"}

// 桥接自带 http.RoundTripper(连接池/中间件复用)
tr := wop.RoundTripperTransport(myRoundTripper, baseURL)

// 函数适配(测试 mock)
tr := wop.TransportFunc(func(d wop.RequestDraft) (wop.TransportResponse, error) { ... })

License

MIT

Documentation

Overview

Package wop 是 WOP 网关商户侧官方 Go SDK:封装协议核心(套件解析、 canonicalRequest、结构化签名、content-digest、L2 数字信封、验签解密) 与 HTTP 适配层,商户无需理解线上字节格式即可安全对接网关。

协议真源:gtsp-wop-gateway/docs/crypto-strategy-spec.md(v0.3-reviewed) 与 docs/wop-sdk-spec.md(v1.0-ratified)。全部二进制线上编码为 base64url 无填充(拒收 '='),十六进制统一小写。

对外错误模糊化纪律(I7):验签与解密失败的错误信息不区分原因细节 (GCM tag 失败、密钥不符等),详细原因仅内部日志级别可见;配置类与 协议格式类错误语义明确,便于商户集成自查。

Index

Constants

View Source
const (
	// HeaderAppKey 应用唯一标识头。
	HeaderAppKey = "x-wop-appkey"
	// HeaderSign 结构化签名头。
	HeaderSign = "x-wop-sign"
	// HeaderContentDigest 报文摘要头。
	HeaderContentDigest = "x-wop-content-digest"
	// HeaderTimestamp 请求时间戳头。
	HeaderTimestamp = "x-wop-timestamp"
	// HeaderNonce 防重放随机数头。
	HeaderNonce = "x-wop-nonce"
	// HeaderEncrypt L2 数字信封元数据头。
	HeaderEncrypt = "x-wop-encrypt"
)

协议 Header 名称(x-wop- 前缀,与网关 GatewayConstants 对齐)。

View Source
const (
	// SignProtocolVersion 签名协议版本。
	SignProtocolVersion = "v1"
	// SignExpiredSecondsDefault 出站签名默认有效时长(秒)。
	SignExpiredSecondsDefault = int64(1800)
	// SignExpiredSecondsMax expiredSeconds 允许上限(秒),防超大窗口拉长重放风险。
	SignExpiredSecondsMax = int64(86400)
)

签名协议常量(spec §7 / 网关 GatewayConstants)。

Variables

This section is empty.

Functions

func CanonicalHeaders

func CanonicalHeaders(headers map[string]string) string

CanonicalHeaders 构造规范标头(F2):名称 lowercase + TrimAll + urlencode, 值 TrimAll + urlencode,按名称 ASCII 升序,行间 '\n' 连接,尾行不加 '\n'。

func CanonicalRequest

func CanonicalRequest(authString, method, canonicalURI, canonicalQueryString, canonicalHeaders string) string

CanonicalRequest 组装 5 段规范请求(F2):

authString\nhttpRequestMethod\ncanonicalURI\ncanonicalQueryString\ncanonicalHeaders

POST 的 canonicalQueryString 为空串,分隔空行不可省略; method 统一大写。Go string 零值即 "",天然等价网关 build 的 null→"" 合并。

func DecodeB64URL

func DecodeB64URL(s string) ([]byte, error)

DecodeB64URL 严格解码 base64url 无填充:含 '='、'+'、'/'、空白或 长度非法(%4==1)一律拒绝(F6/F7 负向量锚点)。

func DigestHeaderValue

func DigestHeaderValue(s Suite, data []byte) string

DigestHeaderValue 组装 x-wop-content-digest 线上值: 算法标记 + 恰一空格 + 小写 hex(D2)。摘要对象 = data 原始字节 (L2 时即密文载体,不摘明文)。

func EncodeB64URL

func EncodeB64URL(b []byte) string

EncodeB64URL 编码为 base64url 无填充。

func LowerHex

func LowerHex(b []byte) string

LowerHex 小写十六进制(D10:统一小写,.NET BitConverter 大写为经典翻车点)。

func ParseContentDigest

func ParseContentDigest(value string) (tag, hexSum string, err error)

ParseContentDigest 严格解析 digest 头值,返回 (tag, hex)。 结构非法(双空格、大写、长度不符、未支持 tag 等)→ 协议类明确错误。

func ParseSignHeader

func ParseSignHeader(header string) (signHeader, error)

ParseSignHeader 严格解析 x-wop-sign 值;结构非法为协议类明确错误。

func TrimAll

func TrimAll(s string) string

TrimAll:去首尾空白,连续空白折叠为单个空格(canonicalRequest 用)。 空白类对齐 Java Character.isWhitespace 常见子集:空格、\t、\n、\x0B、\f、\r。

func URLEncodeJava

func URLEncodeJava(s string) string

URLEncodeJava 按 java.net.URLEncoder(UTF-8) 语义编码,并将输出中的 '+' 替换回 %20(canonicalRequest 的 RFC 3986 风格钉子,F2): 保留 [A-Za-z0-9.-*_],其余字符按 UTF-8 字节 %XX,空格 → %20。

func ValidateContentDigest

func ValidateContentDigest(s Suite, headerValue string, wireBody []byte) error

ValidateContentDigest 复核线上报文摘要:结构(D2)→ 套件族耦合(I5)→ 值比对。 摘要不匹配返回完整性类明确错误(CodeDigestMismatch)。

func ValidateContentDigestHeader

func ValidateContentDigestHeader(s Suite, headerValue string) error

ValidateContentDigestHeader 结构 + 套件族耦合校验(D2/I5,不含值比对; 与网关 ContentDigestHeader.validate 对齐,formatRules 消费口径)。

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client 是线程安全的 WOP 协议客户端:BuildRequest 纯函数产线, VerifyResponse/VerifyCallback 消费 F6 管线,Do 一站式发送+校验。

func NewClient

func NewClient(cfg Config) (*Client, error)

NewClient 解析并校验配置(套件原子装配 + 密钥格式/位数校验,错误均明确)。

func (*Client) BuildRequest

func (c *Client) BuildRequest(method, path string, body []byte, level Level, opts ...RequestOption) (RequestDraft, error)

BuildRequest 构造已签名(L2 时已加密)的请求草稿(spec §2 buildRequest)。 纯计算、零网络 IO;除 CSPRNG 值外同输入同输出。

func (*Client) Do

func (c *Client) Do(method, path string, body []byte, level Level, opts ...RequestOption) (VerifyResult, TransportResponse, error)

Do 一站式调用:BuildRequest → Transport.Send → VerifyResponse(F6)。 构建或发送失败返回错误;响应校验失败时 err 携带 wop.Error(Code 可编程处理), VerifyResult 同时返回完整判定。

func (*Client) Suite

func (c *Client) Suite() Suite

Suite 返回已装配的算法套件(只读视图)。

func (*Client) VerifyCallback

func (c *Client) VerifyCallback(callbackURL string, header http.Header, wireBody []byte) VerifyResult

VerifyCallback 校验平台回调(spec §2):canonical URI 取回调 URL 的 path (不含 query),HTTP 方法恒为 POST。

func (*Client) VerifyResponse

func (c *Client) VerifyResponse(method, path string, header http.Header, wireBody []byte) VerifyResult

VerifyResponse 校验网关响应(F6 顺序钉死): 验签 → digest 复核 → DEK 解包 → alg 族比对(解包后、bulk 解密前)→ bulk 解密。 method/path 为商户原始请求的方法与路径(平台响应 canonical 复用请求 URI)。

type Config

type Config struct {
	// AppKey 平台分配的应用唯一标识。
	AppKey string
	// SecurityReq 算法套件标识(如 WOP-RSA3072-SHA256)。
	SecurityReq string
	// MerchantPrivateKey 商户私钥(加签 / L2 入站解包)。
	MerchantPrivateKey string
	// PlatformPublicKey 平台公钥(验签 / L2 出站 DEK 包装)。
	PlatformPublicKey string
	// GatewayBaseURL 网关基地址(DefaultTransport 使用)。
	GatewayBaseURL string
	// ExpiredSeconds 出站签名有效时长(秒),0 → 默认 1800,上限 86400。
	ExpiredSeconds int64
	// Transport 发送适配器;nil → DefaultTransport(http.Client 默认实例)。
	Transport Transport
}

Config 商户接入配置。密钥材料为字符串(PEM 或 Base64 单行,D12)。

type DefaultTransport

type DefaultTransport struct {
	// HTTPClient 为 nil 时使用 http.DefaultClient。
	HTTPClient *http.Client
	// BaseURL 网关基地址;draft.Path 拼接其上。为空时 draft.Path 须为完整 URL。
	BaseURL string
}

DefaultTransport 默认 net/http 适配器。

func (DefaultTransport) Send

Send 实现 Transport:构建 http.Request 并发送,读取响应体。

type Error

type Error struct {
	Code    ErrorCode
	Message string
}

Error 是 SDK 的统一错误模型:Code 可编程处理,Message 为对外语义。 验签/解密类错误的 Message 恒为固定模糊文案。

func (*Error) Error

func (e *Error) Error() string

Error 实现 error 接口。

type ErrorCode

type ErrorCode string

ErrorCode 是稳定的公共错误码契约(商户可编程处理)。 分类依据 crypto-strategy-spec §10.2:鉴权前可判定的公开协议知识 → 明确; 依赖密钥参与的判定 → 模糊(防 padding-oracle 式信息泄露)。

const (
	// CodeConfig 配置类(明确):密钥缺失、密钥解析失败、密钥与套件不符。
	CodeConfig ErrorCode = "CONFIG"
	// CodeSuiteParse 解析类(明确):securityReq 空值/格式/前缀错误。
	CodeSuiteParse ErrorCode = "SUITE_PARSE"
	// CodeSuiteUnsupported 支持类(明确):算法不在支持列表、跨族组合、长度非法。
	CodeSuiteUnsupported ErrorCode = "SUITE_UNSUPPORTED"
	// CodeProtocol 协议格式类(明确):x-wop-sign / digest 头 / L2 信封结构非法。
	CodeProtocol ErrorCode = "PROTOCOL"
	// CodeDigestMismatch 完整性类(明确):摘要与线上报文字节不符(D2)。
	CodeDigestMismatch ErrorCode = "DIGEST_MISMATCH"
	// CodeVerifyFailed 验签类(模糊):签名验证失败,对外不区分原因(I7)。
	CodeVerifyFailed ErrorCode = "VERIFY_FAILED"
	// CodeDecryptFailed 解密类(模糊):DEK 解包或 GCM 解密失败,对外不区分原因(I7)。
	CodeDecryptFailed ErrorCode = "DECRYPT_FAILED"
	// CodeAlgMismatch 一致性类(明确):dek alg 与套件族不符(公开映射知识,I3 允许提前拒)。
	CodeAlgMismatch ErrorCode = "ALG_MISMATCH"
)

type Family

type Family string

Family 是算法体系族(crypto-strategy-spec §2.2):国际(RSA)与国密(SM2)。

const (
	// FamilyRSA 国际算法体系族(RSA/SHA-256 生产线)。
	FamilyRSA Family = "RSA"
	// FamilySM2 国密算法体系族(SM2/SM3/SM4 生产线)。
	FamilySM2 Family = "SM2"
)

type Level

type Level string

Level 是报文加密级别:L0 明文、L2 全文数字信封。

const (
	// Level0 明文级别(L0):不上数字信封,报文原样传输。
	Level0 Level = "L0"
	// Level2 全文数字信封级别(L2):随机 CEK + GCM 加密全文,CEK 经平台公钥包装。
	Level2 Level = "L2"
)

type RequestDraft

type RequestDraft struct {
	Method   string
	Path     string
	Headers  map[string]string
	WireBody []byte
}

RequestDraft 是协议核心产出的待发送请求:商户可直接消费自带 HTTP 栈, 或交给本 SDK Transport 发送。

type RequestOption

type RequestOption func(*RequestOptions)

RequestOption 单个选项。

func WithNonce

func WithNonce(nonce string) RequestOption

WithNonce 固定 nonce(重放/联调用)。

func WithRandom

func WithRandom(r io.Reader) RequestOption

WithRandom 注入确定性随机源(联调用;生产禁用——IV 复用即 I4 违规)。

func WithTimestamp

func WithTimestamp(ms int64) RequestOption

WithTimestamp 固定毫秒时间戳(重放/联调用)。

type RequestOptions

type RequestOptions struct {
	// TimestampMs 毫秒 Unix 时间戳;0 → 当前时间。
	TimestampMs int64
	// Nonce 防重放随机串;空 → CSPRNG 生成 32 位 hex。
	Nonce string
	// Random 随机源(nonce/CEK/IV 顺序消费);nil → crypto/rand。
	Random io.Reader
}

RequestOptions 是 BuildRequest 的可选项(测试确定性钩子)。

type Suite

type Suite struct {
	// contains filtered or unexported fields
}

Suite 是一次通信的算法套件上下文(spec §4.4 / §3.2 推导规则), 由 securityReq 一次性原子解析(I6),不可变值类型。

func ParseSuite

func ParseSuite(securityReq string) (Suite, error)

ParseSuite 从 securityReq 解析算法套件(F1)。 错误分类(spec §2.4):格式/前缀错误 → 解析类;算法不支持/跨族 → 支持类。 两者对外语义均明确。

func (Suite) Digest

func (s Suite) Digest(data []byte) []byte

Digest 按套件族计算摘要(④):RSA 族 → SHA-256,SM2 族 → SM3(I5)。

func (Suite) DigestTag

func (s Suite) DigestTag() string

DigestTag 返回 x-wop-content-digest 算法标签(④,D2)。

func (Suite) Family

func (s Suite) Family() Family

Family 返回算法体系族(RSA / SM2)。

func (Suite) IsSM2

func (s Suite) IsSM2() bool

IsSM2 报告是否为国密 SM2 族套件。

func (Suite) KeyBits

func (s Suite) KeyBits() int

KeyBits 返回 RSA 密钥位数(3072/4096);SM2 套件恒 0。

func (Suite) KeyWrapAlgorithm

func (s Suite) KeyWrapAlgorithm() string

KeyWrapAlgorithm 返回 DEK 非对称包装算法名(③)。

func (Suite) MessageAlgorithm

func (s Suite) MessageAlgorithm() string

MessageAlgorithm 返回 L2 报文对称算法名(②,dek alg 段)。

func (Suite) SecurityReq

func (s Suite) SecurityReq() string

SecurityReq 返回原始套件标识串。

func (Suite) SignAlgorithm

func (s Suite) SignAlgorithm() string

SignAlgorithm 返回推导后的签名算法名(①)。

type Transport

type Transport interface {
	Send(RequestDraft) (TransportResponse, error)
}

Transport 是可插拔 HTTP 适配层(spec §1.1 Q1:协议核心纯函数,传输可替换)。 商户自带栈时直接消费 RequestDraft,无需本接口。

func RoundTripperTransport

func RoundTripperTransport(rt http.RoundTripper, baseURL string) Transport

RoundTripperTransport 把任意 http.RoundTripper 桥接为 Transport (商户复用自带栈的连接池/中间件;baseURL 语义同 DefaultTransport)。

type TransportFunc

type TransportFunc func(RequestDraft) (TransportResponse, error)

TransportFunc 函数适配器(测试/自定义发送逻辑)。

func (TransportFunc) Send

Send 实现 Transport。

type TransportResponse

type TransportResponse struct {
	StatusCode int
	Headers    http.Header
	Body       []byte
}

TransportResponse 是适配层归一化的响应。

type VerifyResult

type VerifyResult struct {
	OK        bool
	Code      ErrorCode
	Reason    string
	Plaintext []byte
}

VerifyResult 是 F6 校验管线的结果:OK 为真时 Plaintext 携带 L2 解密后 明文(L0 即 wire body);失败时 Code/Reason 按错误分类总表对外 (验签/解密类模糊,其余明确,I7)。

Directories

Path Synopsis
tools
mutation command
Command mutation 是 wop-go-sdk 的变异测试引擎(PIT 不可用于 Go,本仓自研)。
Command mutation 是 wop-go-sdk 的变异测试引擎(PIT 不可用于 Go,本仓自研)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL