尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Podman 中的 Safe JSON:go-jose 严格 JSON 解析器源码剖析(fork 自 Go encoding/json)

Podman 中的 Safe JSON:go-jose 严格 JSON 解析器源码剖析(fork 自 Go encoding/json) Podman 中的 Safe JSONgo-jose 严格 JSON 解析器源码剖析fork 自 Go encoding/json【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podmango-jose 是 JOSEJWS/JWE/JWK规范的 Go 实现而 Podman 通过 vendor 目录将其连同专属 JSON 解析器一同纳入仓库vendor/github.com/go-jose/go-jose/v4/。这个名为Safe JSON的包README.md并非普通依赖它是 fork 自 Go 1.6encoding/json、为 JOSE 消息解析做了两处严格化改造的专用编解码器。读完本文你将理解 go-jose 为何拒绝标准库的宽松 JSON 行为、Safe JSON 的大小写敏感匹配与重复键拒绝在源码中如何落地以及 Podman 中以签名/密钥为核心的代码如何消费这个包。一、为什么 go-jose 需要一个安全的 JSON 解析器JOSE 系列规范JSON Web Signature、JSON Web Encryption、JSON Web Key的核心载荷——JWS 的受保护头protected header、JWE 的加密头、JWK 的公钥/私钥结构——全部以 JSON 对象形式存在。这些 JSON 对象通常随后会被 base64url 编码并参与签名或加密运算。这意味着同一个 JSON 对象在不同编程语言的实现中必须被解析成完全相同的结果否则同样的消息在不同语言里会得到不同语义畸形或含歧义的 JSON 不应被尽力容忍因为一次宽松解析可能悄悄改变一条 JOSE 消息的含义从而造成安全漏洞。而 Go 标准库encoding/json在反序列化对象成员时采用大小写不敏感的匹配策略并且在解析到重复键时采取后者覆盖前者的静默行为。这两种宽松行为对于通用业务开发是便利但对于必须保证跨语言一致性的密码学消息格式却是隐患。这正是 go-jose 维护一个自家 JSON fork 的根本原因。Safe JSON 基于 Go 1.6 的encoding/json做了且只做了两处行为改动对象成员名匹配改为大小写敏感——避免 go-jose 与其他语言实现的 JOSE 库对消息的解释产生差异反序列化对象时检测并拒绝重复键——面对畸形数据时宁可立即拒绝也不尝试修复。仓库中该目录共 7 个文件decode.go解码核心、encode.go编码核心、scanner.go词法状态机、indent.go缩进格式化、stream.go流式解码、tags.gostruct tag 解析以及 README.md 和 LICENSE。二、改动一大小写敏感的成员名匹配标准库encoding/json解析对象到 struct 时会用字段名或jsontag 指定的名字进行匹配且优先精确匹配、接受大小写不敏感匹配即foldName逻辑。Safe JSON 删除了后者。在 decode.go 的object()方法中键与 struct 字段的匹配是一段纯粹的字节相等比较// decode.go, object() 中查找与 key 对应的 struct 字段 var f *field fields : cachedTypeFields(v.Type()) for i : range fields { ff : fields[i] if bytes.Equal(ff.nameBytes, []byte(key)) { f ff break } }cachedTypeFields负责把 struct 的字段名和jsontag 预解析成field结构包含name与预计算的nameBytes见 encode.go 中的fillField随后直接用bytes.Equal做逐字节精确比较。任何大小写差异都视为不匹配该键被当作无对应字段而忽略f nil时跳过写入。对比一个实际场景某 JWS 头部包含{alg:RS256,ALG:ES256}。在标准库下ALG会以大小写不敏感方式命中alg字段并覆盖其值最终解析结果取决于键的先后顺序——这在不同语言的 JOSE 实现间无法保证一致而在 Safe JSON 下只有精确的alg会命中字段ALG被丢弃解析结果确定无疑。对签名验证而言这种确定无疑正是正确性的前提。值得注意这条改动只影响对象到 struct的路径。反序列化到map[string]interface{}时 JSON 键本来就必须逐字保留decode.go 的objectInterface()直接把键写入 map因此不受大小写策略影响跨语言一致性在 map 场景天然成立。三、改动二检测并拒绝重复键标准库遇到{a:1,a:2}时默认后者覆盖前者不报错。Safe JSON 则在struct 和 map 两条解码路径上都做了重复键检测一旦发现即抛出错误。struct 路径object()// decode.go: 检查重复键 _, ok keys[key] if !ok { keys[key] true } else { d.error(fmt.Errorf(json: duplicate key %s in object, key)) }map 路径objectInterface()也完全一致地维护了一个map[string]bool键集合检测到重复同样报错// decode.go: objectInterface() 中的重复键检查 _, ok keys[key] if !ok { keys[key] true } else { d.error(fmt.Errorf(json: duplicate key %s in object, key)) }两处错误信息统一为json: duplicate key %s in object且经由decodeState.error()以 panic 形式抛出、再由decodeState.unmarshal()顶部的recover()还原为普通 error 返回给调用方。这种panic 中断 recover 收口的机制与标准库一脉相承好处是一旦发现重复键就立即终止整个反序列化而不是把对象解析到一半再返回错误——避免调用方拿到半成品数据结构。四、解码链路与 API 面源码级解剖4.1 Unmarshal 入口先整体校验再逐值解码// decode.go func Unmarshal(data []byte, v interface{}) error { // Check for well-formedness. // Avoids filling out half a data structure // before discovering a JSON syntax error. var d decodeState err : checkValid(data, d.scan) if err ! nil { return err } d.init(data) return d.unmarshal(v) }checkValid定义在 scanner.go它驱动一个手写状态机scanner对整段输入做一次完整的词法预扫描任何语法错误如未闭合的字符串、非法数字、尾随垃圾都会在填充任何目标数据结构之前被拦截// scanner.go func checkValid(data []byte, scan *scanner) error { scan.reset() for _, c : range data { scan.bytes if scan.step(scan, c) scanError { return scan.err } } if scan.eof() scanError { return scan.err } return nil }这保证了两个安全属性其一畸形输入绝不会留下半填充的结构体其二词法错误SyntaxError含出错偏移量Offset与类型错误UnmarshalTypeError含Value、Type、Offset字段被明确区分。4.2 数字解码策略NumberUnmarshalType与标准库不同Safe JSON 暴露了一个扩展点允许调用方选择数字落入interface{}时的类型// decode.go type NumberUnmarshalType int const ( // unmarshal a JSON number into an interface{} as a float64 UnmarshalFloat NumberUnmarshalType iota // unmarshal a JSON number into an interface{} as a json.Number UnmarshalJSONNumber // unmarshal a JSON number into an interface{} as a int64 // if value is an integer otherwise float64 UnmarshalIntOrFloat )UnmarshalFloat默认数字一律解析为float64与标准库一致UnmarshalJSONNumber保留数字字面量原文为Number类型type Number string并配有Float64()、Int64()转换方法以及isValidNumber语法校验decode.goUnmarshalIntOrFloat先尝试strconv.ParseInt失败再按浮点解析若浮点值无小数部分仍回退为int64。实际转换由convertNumber()完成这对 JWK 中诸如n模数、e指数等大整数字段的精确表示很有价值——UnmarshalJSONNumber可以避免大整数被 float64 精度截断。4.3 编码侧Marshal / MarshalIndent / HTMLEscape编码侧encode.go保持标准库语义Marshal(v)/MarshalIndent(v, prefix, indent)递归编码支持Marshaler/encoding.TextMarshaler接口、omitempty、,string等 struct tag 选项// Field appears in JSON as key myName and // the field is omitted from the object if its value is empty Field int json:myName,omitempty // The string option signals that a field is stored as JSON inside // a JSON-encoded string Int64String int64 json:,string[]byte编码为 base64 字符串nil slice 编码为null字符串编码时对、、转义为\u003c、\u003e、\u0026并对 U2028/U2029 无条件转义防 JSONP/浏览器注入另提供显式的HTMLEscape帮助函数map 键在编码时按字符串排序保证输出确定性。五、go-jose v4 如何消费 Safe JSONSafe JSON 的严格性最终服务于 go-jose 自身的 JOSE 消息序列化。在 encoding.go 中所有已知良好对象的序列化都走mustSerializeJSON// encoding.go func mustSerializeJSON(value interface{}) []byte { out, err : json.Marshal(value) if err ! nil { panic(err) } // We never want to serialize the top-level value null, since its not a // valid JOSE message. ... if string(out) null { panic(Tried to serialize a nil pointer.) } return out }encoding.go第 30 行github.com/go-jose/go-jose/v4/json显式 import 了这个 fork说明 go-jose 的消息头、JWK、JWS/JWE 内部结构的编解码全部经由 Safe JSON 完成。JWK 的读写同样依赖它。在 jwk.go 中// jwk.go: JWK 序列化与反序列化 return json.Marshal(raw) // L174 err json.Unmarshal(data, raw) // L182结合上一节的UnmarshalJSONNumber可以推断 JWK 的大数n/e/d等在需要精确保真时具备不丢失精度的解析选项。此外 jwe.go、jws.go、shared.go、asymmetric.go 等文件同样引用本包 API。六、Safe JSON 在 Podman 仓库中的位置Podman 通过 vendor 机制将该包固定为github.com/go-jose/go-jose/v4 v4.1.5见 go.mod 第 119 行标记为 indirect 依赖实际代码托管在vendor/github.com/go-jose/go-jose/v4/json/。这意味着每次构建 Podman 时使用的都是这份打了安全补丁的 JSON 实现不会因宿主环境 Go 版本升级而改变 JOSE 消息的解析语义任何经过 go-jose 的 JWS 验签、JWK 装载路径都继承了大小写敏感 拒绝重复键两条严格规则。开发者若要在 Podman 生态内排查签名/密钥相关的异常行为例如某个 JWS 头被拒绝、某个 JWK 解析报duplicate key错误应首先意识到解析行为来自这个 fork而不是标准库。例如看到错误json: duplicate key alg in object时即可定位到 decode.go 的重复键检测逻辑。七、总结Safe JSON 是安全优先在 JSON 解析层的典型实践行为Go 标准库 encoding/jsongo-jose Safe JSON对象成员名匹配大小写不敏感foldName大小写敏感字节相等重复键后者覆盖前者静默立即报错拒绝语法预检有有checkValid状态机数字解码扩展无仅 float64支持 Number / IntOrFloat / Float 三策略适用场景通用业务开发需跨语言一致性的 JOSE 消息处理对 Podman 而言这份 vendor 代码虽小却是签名与信任链安全的地基一次大小写匹配的分歧或一个被静默吞掉的重复键都可能让同一消息、不同语义成为现实。理解 Safe JSON 的两处改动及其在decode.go、scanner.go、encode.go中的实现是深入排查 Podman 签名/密钥链路问题的重要前提。【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表