Initial commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package idutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sony/sonyflake"
|
||||
)
|
||||
|
||||
// SnowflakeGenerator 雪花算法 ID 生成器(单例)
|
||||
type SnowflakeGenerator struct {
|
||||
flake *sonyflake.Sonyflake
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// globalGen 全局生成器实例
|
||||
var (
|
||||
globalGen *SnowflakeGenerator
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// GetGenerator 获取全局雪花算法生成器(线程安全)
|
||||
func GetGenerator() (*SnowflakeGenerator, error) {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
globalGen, err = NewSnowflakeGenerator()
|
||||
})
|
||||
return globalGen, err
|
||||
}
|
||||
|
||||
// NewSnowflakeGenerator 创建新的雪花算法生成器
|
||||
func NewSnowflakeGenerator() (*SnowflakeGenerator, error) {
|
||||
// Sonyflake 配置
|
||||
st := sonyflake.Settings{
|
||||
StartTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), // 自定义起始时间
|
||||
}
|
||||
|
||||
flake := sonyflake.NewSonyflake(st)
|
||||
if flake == nil {
|
||||
return nil, fmt.Errorf("snowflake generator creation failed")
|
||||
}
|
||||
|
||||
return &SnowflakeGenerator{
|
||||
flake: flake,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NextID 生成下一个 ID(uint64)
|
||||
func (g *SnowflakeGenerator) NextID() (uint64, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
id, err := g.flake.NextID()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to generate snowflake id: %w", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// NextString 生成字符串格式 ID(便于 JSON 传输)
|
||||
func (g *SnowflakeGenerator) NextString() (string, error) {
|
||||
id, err := g.NextID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d", id), nil
|
||||
}
|
||||
|
||||
// ParseID 从字符串解析 ID
|
||||
func ParseID(s string) (uint64, error) {
|
||||
var id uint64
|
||||
_, err := fmt.Sscanf(s, "%d", &id)
|
||||
return id, err
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package meshseed
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// deriveKey 派生 AES-256-GCM 密钥
|
||||
// 密钥派生规则:SHA256("meshray-ddns" || NetworkSecret) → 32 字节
|
||||
func deriveKey(networkSecret string) []byte {
|
||||
hash := sha256.Sum256([]byte("meshray-ddns" + networkSecret))
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
// Encrypt 加密 MeshSeed
|
||||
// 流程:签名 → AES-256-GCM 加密 → Base64URL 编码
|
||||
func Encrypt(seed *MeshSeed, networkSecret string) (string, error) {
|
||||
// 1. 签名(如果还没有签名)
|
||||
if len(seed.Signature) == 0 {
|
||||
// 注意:这里需要 Ed25519 私钥进行签名
|
||||
// 由于 MeshSeed 已经包含签名,我们假设签名已经完成
|
||||
return "", fmt.Errorf("MeshSeed 未签名")
|
||||
}
|
||||
|
||||
// 2. 序列化 MeshSeed
|
||||
plaintext, err := json.Marshal(seed)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("序列化 MeshSeed 失败:%w", err)
|
||||
}
|
||||
|
||||
// 3. 派生加密密钥
|
||||
key := deriveKey(networkSecret)
|
||||
|
||||
// 4. 创建 AES cipher
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建 AES cipher 失败:%w", err)
|
||||
}
|
||||
|
||||
// 5. 创建 GCM
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建 GCM 失败:%w", err)
|
||||
}
|
||||
|
||||
// 6. 生成随机 Nonce
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", fmt.Errorf("生成 Nonce 失败:%w", err)
|
||||
}
|
||||
|
||||
// 7. 加密
|
||||
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
|
||||
|
||||
// 8. Base64URL 编码
|
||||
encoded := base64.URLEncoding.EncodeToString(ciphertext)
|
||||
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
// Decrypt 解密 MeshSeed
|
||||
// 流程:Base64URL 解码 → AES-256-GCM 解密 → Ed25519 验签
|
||||
func Decrypt(encrypted string, networkSecret string) (*MeshSeed, error) {
|
||||
// 1. Base64URL 解码
|
||||
ciphertext, err := base64.URLEncoding.DecodeString(encrypted)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Base64URL 解码失败:%w", err)
|
||||
}
|
||||
|
||||
// 2. 派生解密密钥
|
||||
key := deriveKey(networkSecret)
|
||||
|
||||
// 3. 创建 AES cipher
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建 AES cipher 失败:%w", err)
|
||||
}
|
||||
|
||||
// 4. 创建 GCM
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建 GCM 失败:%w", err)
|
||||
}
|
||||
|
||||
// 5. 检查密文长度
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(ciphertext) < nonceSize {
|
||||
return nil, fmt.Errorf("密文长度不足")
|
||||
}
|
||||
|
||||
// 6. 分离 Nonce 和密文
|
||||
nonce, ciphertextBytes := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||
|
||||
// 7. 解密
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertextBytes, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解密失败:%w", err)
|
||||
}
|
||||
|
||||
// 8. 反序列化 MeshSeed
|
||||
var seed MeshSeed
|
||||
if err := json.Unmarshal(plaintext, &seed); err != nil {
|
||||
return nil, fmt.Errorf("反序列化 MeshSeed 失败:%w", err)
|
||||
}
|
||||
|
||||
return &seed, nil
|
||||
}
|
||||
|
||||
// Sign 签名 MeshSeed
|
||||
// 使用 Ed25519 私钥对 MeshSeed 进行签名
|
||||
func Sign(seed *MeshSeed, privateKey ed25519.PrivateKey) error {
|
||||
// 1. 临时清除签名字段
|
||||
originalSignature := seed.Signature
|
||||
seed.Signature = nil
|
||||
|
||||
// 2. 序列化 MeshSeed(不含签名)
|
||||
data, err := json.Marshal(seed)
|
||||
if err != nil {
|
||||
seed.Signature = originalSignature
|
||||
return fmt.Errorf("序列化 MeshSeed 失败:%w", err)
|
||||
}
|
||||
|
||||
// 3. Ed25519 签名
|
||||
signature := ed25519.Sign(privateKey, data)
|
||||
|
||||
// 4. 设置签名
|
||||
seed.Signature = signature
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify 验证 MeshSeed 签名
|
||||
// 使用 Ed25519 公钥验证 MeshSeed 签名
|
||||
// 可选参数 publicKey:如果为 nil,则从 seed.IssuerNodeID 解析公钥
|
||||
func Verify(seed *MeshSeed, publicKey ed25519.PublicKey) error {
|
||||
// 1. 检查签名是否存在
|
||||
if len(seed.Signature) == 0 {
|
||||
return fmt.Errorf("MeshSeed 无签名")
|
||||
}
|
||||
|
||||
// 2. 如果未提供公钥,从 IssuerNodeID 解析
|
||||
if publicKey == nil {
|
||||
if seed.IssuerNodeID == "" {
|
||||
return fmt.Errorf("无法验证签名:缺少公钥(IssuerNodeID 为空)")
|
||||
}
|
||||
// IssuerNodeID 是 hex 编码的 Ed25519 公钥(32 字节)
|
||||
pubKeyBytes, err := decodeIssuerNodeID(seed.IssuerNodeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析 IssuerNodeID 失败:%w", err)
|
||||
}
|
||||
if len(pubKeyBytes) != ed25519.PublicKeySize {
|
||||
return fmt.Errorf("无效的公钥长度:期望 %d 字节,实际 %d 字节",
|
||||
ed25519.PublicKeySize, len(pubKeyBytes))
|
||||
}
|
||||
publicKey = ed25519.PublicKey(pubKeyBytes)
|
||||
}
|
||||
|
||||
// 3. 临时保存签名
|
||||
signature := seed.Signature
|
||||
|
||||
// 4. 临时清除签名字段
|
||||
seed.Signature = nil
|
||||
|
||||
// 5. 序列化 MeshSeed(不含签名)
|
||||
data, err := json.Marshal(seed)
|
||||
if err != nil {
|
||||
seed.Signature = signature
|
||||
return fmt.Errorf("序列化 MeshSeed 失败:%w", err)
|
||||
}
|
||||
|
||||
// 6. 恢复签名
|
||||
seed.Signature = signature
|
||||
|
||||
// 7. Ed25519 验签
|
||||
if !ed25519.Verify(publicKey, data, signature) {
|
||||
return fmt.Errorf("签名验证失败:签名无效或数据被篡改")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeIssuerNodeID 解码 IssuerNodeID 为公钥字节
|
||||
// 支持两种格式:hex 编码(64 字符)或 Base64 编码
|
||||
func decodeIssuerNodeID(id string) ([]byte, error) {
|
||||
// 尝试 hex 解码(64 字符 = 32 字节)
|
||||
if len(id) == 64 {
|
||||
return hexDecodeString(id)
|
||||
}
|
||||
|
||||
// 尝试 Base64 解码
|
||||
return base64.StdEncoding.DecodeString(id)
|
||||
}
|
||||
|
||||
// hexDecodeString 解码 hex 字符串(辅助函数)
|
||||
func hexDecodeString(s string) ([]byte, error) {
|
||||
// 手动实现 hex 解码以避免导入 encoding/hex
|
||||
result := make([]byte, len(s)/2)
|
||||
for i := 0; i < len(s); i += 2 {
|
||||
b1, ok := hexCharToByte(s[i])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("无效的 hex 字符:%c", s[i])
|
||||
}
|
||||
b2, ok := hexCharToByte(s[i+1])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("无效的 hex 字符:%c", s[i+1])
|
||||
}
|
||||
result[i/2] = b1<<4 | b2
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// hexCharToByte 单个 hex 字符转字节
|
||||
func hexCharToByte(c byte) (byte, bool) {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0', true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10, true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateKeyPair 生成 Ed25519 密钥对
|
||||
func GenerateKeyPair() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("生成密钥对失败:%w", err)
|
||||
}
|
||||
return publicKey, privateKey, nil
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Package meshseed provides utilities for MeshSeed generation, verification, and DDNS sync.
|
||||
// This package can be compiled as a mobile static library.
|
||||
package meshseed
|
||||
@@ -0,0 +1,37 @@
|
||||
package meshseed
|
||||
|
||||
import "time"
|
||||
|
||||
// MeshSeed 组网凭证 - 按照 README 5.1 节定义
|
||||
type MeshSeed struct {
|
||||
// ===== 必选参数 =====
|
||||
NetworkName string `json:"networkName"` // 组网名称
|
||||
NetworkSecret string `json:"networkSecret"` // 高熵密钥,派生 NetworkID(绝不公开)
|
||||
Subnet string `json:"subnet"` // CIDR 格式,如 10.0.0.0/24
|
||||
IssuerNodeID string `json:"issuerNodeId"` // 签发者节点 ID(对应 Ed25519 公钥)
|
||||
|
||||
// ===== 可选参数 =====
|
||||
MaxUses int `json:"maxUses,omitempty"` // 最大使用次数(默认 10)
|
||||
ExpiresInHours int `json:"expiresInHours,omitempty"` // 有效期小时数(默认 168 = 7 天)
|
||||
Permissions uint32 `json:"permissions,omitempty"` // 权限位掩码
|
||||
BootstrapPeers []string `json:"bootstrapPeers,omitempty"` // 初始引导节点列表
|
||||
TURNServers []TURNRef `json:"turnServers,omitempty"` // TURN 服务器引用
|
||||
DDNSEnabled bool `json:"ddnsEnabled,omitempty"` // 是否允许 DDNS 同步
|
||||
UpdateVersion int `json:"updateVersion,omitempty"` // 配置版本号(默认 0)
|
||||
|
||||
// ===== 自动生成 =====
|
||||
SeedID string `json:"seedId"` // 16 字节随机 Base64
|
||||
NetworkID uint64 `json:"networkId"` // ❄️ 雪花算法 ID(原为 string,v2.1.0 改为 uint64)
|
||||
IssuedAt time.Time `json:"issuedAt"` // 签发时间戳
|
||||
ExpiresAt time.Time `json:"expiresAt"` // 过期时间戳
|
||||
Signature []byte `json:"signature"` // Ed25519 签名
|
||||
}
|
||||
|
||||
// TURNRef TURN 服务器引用
|
||||
type TURNRef struct {
|
||||
Address string `json:"address"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package shortid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// EncodeID 将 uint64 雪花 ID 编码为短字符串(推荐)
|
||||
// 使用 Base64 URL 安全编码,结果长度约 11 字符
|
||||
func EncodeID(id uint64) string {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, id)
|
||||
return base64.RawURLEncoding.EncodeToString(buf)
|
||||
}
|
||||
|
||||
// DecodeID 解码短字符串为 uint64
|
||||
func DecodeID(s string) (uint64, error) {
|
||||
data, err := base64.RawURLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(data) > 8 {
|
||||
return 0, errors.New("数据过长")
|
||||
}
|
||||
|
||||
// 补齐到 8 字节
|
||||
buf := make([]byte, 8)
|
||||
copy(buf[8-len(data):], data)
|
||||
return binary.BigEndian.Uint64(buf), nil
|
||||
}
|
||||
|
||||
// EncodeIDCompact 紧凑编码(去除前导零,适合小 ID)
|
||||
func EncodeIDCompact(id uint64) string {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, id)
|
||||
|
||||
// 去除前导零
|
||||
trimmed := bytes.TrimLeft(buf, "\x00")
|
||||
if len(trimmed) == 0 {
|
||||
trimmed = []byte{0}
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(trimmed)
|
||||
}
|
||||
|
||||
// DecodeIDCompact 解码紧凑编码
|
||||
func DecodeIDCompact(s string) (uint64, error) {
|
||||
data, err := base64.RawURLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 还原到 8 字节
|
||||
buf := make([]byte, 8)
|
||||
copy(buf[8-len(data):], data)
|
||||
return binary.BigEndian.Uint64(buf), nil
|
||||
}
|
||||
|
||||
// GenerateMeshSeedPrefix 生成 MeshSeed TXT 记录前缀
|
||||
// 格式:_meshray.{encoded_id}
|
||||
func GenerateMeshSeedPrefix(networkID uint64) string {
|
||||
shortID := EncodeID(networkID)
|
||||
return "_meshray." + shortID
|
||||
}
|
||||
|
||||
// ExtractNetworkID 从 TXT 记录前缀提取网络 ID
|
||||
func ExtractNetworkID(prefix string) (uint64, error) {
|
||||
// 格式:_meshray.{encoded_id}
|
||||
if len(prefix) <= 9 || prefix[:9] != "_meshray." {
|
||||
return 0, errors.New("非标准前缀格式")
|
||||
}
|
||||
|
||||
encodedID := prefix[9:] // 提取 _meshray. 后面的部分
|
||||
return DecodeID(encodedID)
|
||||
}
|
||||
Reference in New Issue
Block a user