thomas-vilte/mls-go

GitHub: thomas-vilte/mls-go

纯 Go 语言实现的 RFC 9420 MLS 协议库,为端到端加密群组消息提供前向保密和泄密后安全保障。

Stars: 3 | Forks: 0

# mls-go [![Go 参考](https://pkg.go.dev/badge/github.com/thomas-vilte/mls-go.svg)](https://pkg.go.dev/github.com/thomas-vilte/mls-go) [![Go 报告卡](https://goreportcard.com/badge/github.com/thomas-vilte/mls-go)](https://goreportcard.com/report/github.com/thomas-vilte/mls-go) [![许可证:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 根据 [RFC 9420](https://www.rfc-editor.org/rfc/rfc9420) 实现的纯 Go 语言 Messaging Layer Security (MLS)。 **当前状态:** `v1.2.0` — 稳定版,已通过互操作性验证。 ## 为什么会有这个项目 我需要一个符合 RFC 9420 标准的 Go 语言 MLS 库,并且不依赖 CGO。现有的选项要么需要 C 绑定,要么功能不完整。因此我从零开始构建了这个库,并针对 [mlspp](https://github.com/cisco/mlspp) 和 [OpenMLS](https://github.com/openmls/openmls) 进行了互操作性测试,以确保实现的正确性。 如果你需要在 Go 中进行群组密钥交换:加密消息传递、E2EE 群组聊天、音视频通话加密(DAVE 协议),或者任何需要标准群组 ratchet 的协议,都可以使用它。 ## 概述 该库围绕 RFC 9420 规范进行组织: 主要包: | 包名 | 用途 | |-----------------|-----------------------------------------------------------------| | `mls` (根目录) | 高层线程安全的 `Client` API | | `group` | 底层群组生命周期、commits、proposals、Welcome | | `keypackages` | KeyPackage 的生成、验证和生命周期选项 | | `credentials` | BasicCredential 和 X.509 凭证支持 | | `ciphersuite` | AEAD、HPKE、HKDF、签名、哈希引用 | | `extensions` | 扩展类型(ExternalSenders、RequiredCapabilities 等) | | `framing` | MLSMessage、PublicMessage、PrivateMessage 的有线格式 | | `schedule` | 密钥调度和 MLS-Exporter (RFC 9420 §8) | | `secrettree` | 每发送者的秘密树 ratchets | | `treesync` | Ratchet tree 和 TreeKEM | | `storage` | 可插拔的存储接口 + 文件、内存、加密后端 | | `testing/mlstest` | 用于 MLS 场景的测试辅助工具 | ## 已实现功能 核心 RFC 9420 协议: - 群组生命周期:创建、加入(Welcome + External Join)、离开 - Proposals:Add、Update、Remove、PSK、ReInit — 包含 commit 和 Welcome - 消息保护:PrivateMessage(加密)和 PublicMessage(签名) - 泄露后安全:带有 parent-hash 验证的 UpdatePath - External Senders (RFC §12.1.8.1) - 通过 `CommitPendingProposalsStaged` / `ConfirmPendingCommit` 实现的暂存 commits (RFC §14) - 带有 nonce 安全 SecretTree 计数器的状态序列化 - 完整的 LeafNode 验证:生命周期、capabilities、extensions、credential 类型 - 每代(generation)重放保护 — 拒绝重复的代(generation)编号 - 强制执行 AEAD nonce 计数器限制(每个 epoch 中每个 sender 为 2³²−1) - Welcome 加入时验证 ratchet_tree 结构和 PSK 可用性 RFC 中所有枯燥的验证内容也已实现(varint 编码、required_capabilities 检查、parent-hash 链验证等)。互操作性测试验证了其针对 mlspp 和 OpenMLS 的正确性。 ## 互操作性 通过 Docker 针对其他 MLS 实现进行了测试: | 目标 | 套件 | 结果 | |----------------|---------|----------------------------------------------------------| | mls-go 自身 | 1, 2, 3 | 21/21 通过 | | mlspp 交叉 | 1, 2, 3 | 21/21 通过 | | OpenMLS 交叉 | 1, 2, 3 | 12/12 通过(子集;需要 sequential 模式) | 场景:`welcome_join`、`application`、`commit`、`external_join`、`external_proposals`、`reinit`、`branch`。 ### OpenMLS 注意事项 OpenMLS 的交叉互操作性是**实验性**的,仅限于部分配置 (`welcome_join`、`application`、`external_join`、`deep_random`)。OpenMLS Docker 镜像跟踪上游 HEAD,没有固定版本,因此在上游发生更改后结果可能会有 偏差。如果 OpenMLS 交叉套件失败,请先检查错误是否源自 OpenMLS 的互操作性 客户端(例如,key-store 查找失败),然后再假设是 mls-go 出现了回归。 有关支持的子集和已知的未实现 OpenMLS 处理程序的详细信息,请参阅 `interop/README.md`。 ## 快速开始 推荐的入口点是 `mls.Client` API。 ``` package main import ( "context" "fmt" "log" mls "github.com/thomas-vilte/mls-go" "github.com/thomas-vilte/mls-go/ciphersuite" ) func main() { ctx := context.Background() cs := ciphersuite.MLS128DHKEMP256 alice, err := mls.NewClient([]byte("alice"), cs) if err != nil { log.Fatal(err) } bob, err := mls.NewClient([]byte("bob"), cs) if err != nil { log.Fatal(err) } bobKP, _ := bob.FreshKeyPackageBytes(ctx) groupID, _ := alice.CreateGroup(ctx) _, welcome, _ := alice.InviteMember(ctx, groupID, bobKP) bob.JoinGroup(ctx, welcome) ciphertext, _ := alice.SendMessage(ctx, groupID, []byte("hello")) msg, _ := bob.ReceiveMessage(ctx, groupID, ciphertext) fmt.Println(string(msg.Plaintext)) // hello } ``` ## Client API ``` // Identity client.Epoch(ctx, groupID) // current epoch number client.OwnLeafIndex(ctx, groupID) // my position in the ratchet tree client.ListMembers(ctx, groupID) // active members with identity + signing key // Membership client.CreateGroup(ctx) client.InviteMember(ctx, groupID, memberKPBytes) // → commit, welcome client.JoinGroup(ctx, welcomeBytes) // → groupID client.ExternalJoin(ctx, groupInfoBytes) // → groupID, commit client.RemoveMember(ctx, groupID, memberIdentity) // → commit client.LeaveGroup(ctx, groupID) // local-only state cleanup // Proposals (batch flow) client.ProposeAddMember(ctx, groupID, memberKPBytes) // → signed PublicMessage client.ProposeRemoveMember(ctx, groupID, memberIdentity) client.CommitPendingProposals(ctx, groupID) // → commit, welcome (auto-merge) client.CancelPendingProposals(ctx, groupID) // discard without committing // RFC §14 staged commit (DS conflict-safe) handle, _ := client.CommitPendingProposalsStaged(ctx, groupID) // generate only, no state change welcome, _ := client.ConfirmPendingCommit(ctx, handle) // DS accepted → merge + welcome _ = client.DiscardPendingCommit(ctx, handle) // DS rejected → rollback // Maintenance client.SelfUpdate(ctx, groupID) // rotate leaf encryption key // Messaging client.SendMessage(ctx, groupID, plaintext) // → ciphertext client.SendMessage(ctx, groupID, plaintext, mls.WithAAD(aad)) // with authenticated data client.ReceiveMessage(ctx, groupID, ciphertext) // → ReceivedMessage // Crypto material client.Export(ctx, groupID, label, context, length) // MLS-Exporter client.EpochAuthenticator(ctx, groupID) client.GroupInfo(ctx, groupID) // signed GroupInfo bytes // Process incoming client.ProcessCommit(ctx, groupID, commitBytes) ``` ### 选项 ``` mls.NewClient(identity, cs, mls.WithStorage(groupStorage, keyStore), // durable storage mls.WithCredentialValidator(validator), // allowlist / cert policy mls.WithX509Credential(certDER, privKey), // X.509 instead of Basic mls.WithPaddingSize(32), // AEAD padding in bytes mls.WithCacheStrategy(mls.CacheAlways), // keep state in memory mls.WithEventHandler(func(e mls.GroupEvent) { // lifecycle callbacks // EventMemberJoined, EventMemberRemoved, EventEpochAdvanced, // EventMessageReceived, EventSelfUpdated }), ) ``` ## KeyPackage 选项 ``` // Default: now-1h / now+83d (interop-safe margin) kp, priv, err := keypackages.Generate(credWithKey, cs) // Custom window kp, priv, err := keypackages.Generate(credWithKey, cs, keypackages.WithLifetime(notBefore, notAfter)) // No expiry (not_before=0, not_after=2^64-1) kp, priv, err := keypackages.Generate(credWithKey, cs, keypackages.InfiniteLifetime()) ``` ## 底层 API 对于高级用例(自定义有线协议、外部 commits、群组检查),请直接使用 `group.Group`: ``` g, err := group.NewGroup(groupID, cs, kp, kpPriv, group.WithExtensions(extensions)) g.Export("My App v1", senderIDBytes, 16) // derive sender key g.EpochAuthenticator() // authentication tag g.RevokeProposal(ref) // remove in-flight proposal g.MarshalState() / group.UnmarshalGroupState() // persist / restore ``` ## 存储 ``` // In-memory (tests / demos) store := memorystore.NewStore() // File-backed (durable) store, err := filestore.NewStore("/var/lib/myapp/mls") // Encrypted file-backed (recommended for production) encStore, err := storage.NewEncryptedStore(store, encryptionKey) client, err := mls.NewClient(identity, cs, mls.WithStorage(encStore, encStore)) ``` ## 构建和测试 ``` go build ./... go test ./... go test -race ./... golangci-lint run ./... ``` ## 互操作性测试 ``` # 本地更改后构建服务器镜像 docker compose -f docker/docker-compose.yml build mls-go # Self-interop(所有 suite 并行,约 8 分钟) ./docker/run-interop.sh self # 针对 mlspp 的 Cross-interop ./docker/run-interop.sh cross # 针对 OpenMLS 的 Cross-interop CROSS_TARGET=openmls ./docker/run-interop.sh cross # 单个 suite SUITES="2" ./docker/run-interop.sh self # Stress 模式(包含 deep_random,耗时更长) RUN_STRESS=1 ./docker/run-interop.sh self ``` ## 安全性 有关部署注意事项、状态加密指南和已知限制,请参阅 [SECURITY.md](SECURITY.md)。 ## 集成指南 有关存储模式、交付服务架构和多设备考量,请参阅 [INTEGRATION.md](INTEGRATION.md)。 ## 贡献 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。所有代码、注释、错误信息、测试和文档必须使用英文。 ## 许可证 MIT。请参阅 [LICENSE](LICENSE)。
标签:DAVE协议, E2EE, EVTX分析, Go语言, HPKE, MLS协议, RFC 9420, X.509证书, 前向保密, 加密, 即时通讯, 后妥协安全, 安全协议, 密码学, 开源库, 手动系统调用, 搜索引擎爬虫, 无CGO依赖, 日志审计, 消息传递层安全, 漏洞扫描器, 程序破解, 端到端加密, 群组密钥交换, 请求拦截, 跨平台互操作性, 音视频通话加密