thomas-vilte/mls-go
GitHub: thomas-vilte/mls-go
纯 Go 语言实现的 RFC 9420 MLS 协议库,为端到端加密群组消息提供前向保密和泄密后安全保障。
Stars: 3 | Forks: 0
# mls-go
[](https://pkg.go.dev/github.com/thomas-vilte/mls-go)
[](https://goreportcard.com/report/github.com/thomas-vilte/mls-go)
[](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依赖, 日志审计, 消息传递层安全, 漏洞扫描器, 程序破解, 端到端加密, 群组密钥交换, 请求拦截, 跨平台互操作性, 音视频通话加密