5759c1862e
M0-M7 已完成:核心网关(身份/RBAC/TOTP/OIDC/SAML/Provider/配额/路由/内容策略/审计/定价)+ 资源市场(MCP/Skills/数字员工)。 含 22 个 PostgreSQL 迁移、管理端/门户端前端源码、OpenAPI 契约、部署 compose。 Co-Authored-By: Claude <noreply@anthropic.com>
101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
package cryptox
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Cipher interface {
|
|
Encrypt([]byte) ([]byte, int, error)
|
|
Decrypt([]byte, int) ([]byte, error)
|
|
}
|
|
|
|
type Keyring struct {
|
|
activeVersion int
|
|
ciphers map[int]*AESGCM
|
|
}
|
|
|
|
func NewKeyring(activeKey string, activeVersion int, encodedKeyring, purpose string) (*Keyring, error) {
|
|
if activeVersion < 1 || purpose == "" {
|
|
return nil, errors.New("encryption version and purpose are required")
|
|
}
|
|
encoded := make(map[int]string)
|
|
if strings.TrimSpace(encodedKeyring) != "" {
|
|
var values map[string]string
|
|
if err := json.Unmarshal([]byte(encodedKeyring), &values); err != nil {
|
|
return nil, fmt.Errorf("encryption keyring must be a JSON object: %w", err)
|
|
}
|
|
for rawVersion, key := range values {
|
|
version, err := strconv.Atoi(rawVersion)
|
|
if err != nil || version < 1 || strings.TrimSpace(key) == "" {
|
|
return nil, fmt.Errorf("invalid encryption keyring version %q", rawVersion)
|
|
}
|
|
encoded[version] = strings.TrimSpace(key)
|
|
}
|
|
}
|
|
if strings.TrimSpace(activeKey) != "" {
|
|
if existing, ok := encoded[activeVersion]; ok && existing != strings.TrimSpace(activeKey) {
|
|
return nil, fmt.Errorf("active encryption key version %d is configured twice with different values", activeVersion)
|
|
}
|
|
encoded[activeVersion] = strings.TrimSpace(activeKey)
|
|
}
|
|
if len(encoded) == 0 {
|
|
return nil, nil
|
|
}
|
|
if _, ok := encoded[activeVersion]; !ok {
|
|
return nil, fmt.Errorf("active encryption key version %d is not loaded", activeVersion)
|
|
}
|
|
keyring := &Keyring{activeVersion: activeVersion, ciphers: make(map[int]*AESGCM, len(encoded))}
|
|
for version, key := range encoded {
|
|
cipher, err := NewAESGCM(key, version, purpose)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encryption key version %d: %w", version, err)
|
|
}
|
|
keyring.ciphers[version] = cipher
|
|
}
|
|
return keyring, nil
|
|
}
|
|
|
|
func (k *Keyring) Encrypt(plaintext []byte) ([]byte, int, error) {
|
|
if k == nil {
|
|
return nil, 0, ErrKeyUnavailable
|
|
}
|
|
return k.ciphers[k.activeVersion].Encrypt(plaintext)
|
|
}
|
|
|
|
func (k *Keyring) Decrypt(encrypted []byte, version int) ([]byte, error) {
|
|
if k == nil {
|
|
return nil, ErrKeyUnavailable
|
|
}
|
|
cipher, ok := k.ciphers[version]
|
|
if !ok {
|
|
return nil, fmt.Errorf("encryption key version %d is not loaded", version)
|
|
}
|
|
return cipher.Decrypt(encrypted, version)
|
|
}
|
|
|
|
func (k *Keyring) ActiveVersion() int {
|
|
if k == nil {
|
|
return 0
|
|
}
|
|
return k.activeVersion
|
|
}
|
|
|
|
func (k *Keyring) Versions() []int {
|
|
if k == nil {
|
|
return nil
|
|
}
|
|
versions := make([]int, 0, len(k.ciphers))
|
|
for version := range k.ciphers {
|
|
versions = append(versions, version)
|
|
}
|
|
sort.Ints(versions)
|
|
return versions
|
|
}
|
|
|
|
var _ Cipher = (*Keyring)(nil)
|