Files
lijiaoqiao/supply-api/internal/security/kms_service.go
Your Name 7280ef565c test: improve coverage for audit/events and security modules
- audit/events: 73.5% → 97.6% (+24.1%)
  - Add tests for IsM013/M014/M015RelatedEvent
  - Add tests for FormatSECURITYEvent
  - Add comprehensive coverage for all CRED and SECURITY event functions

- security: 67.2% → 88.8% (+21.6%)
  - Add tests for ValidateKeyID, DecryptionError.Error()
  - Add tests for ValidateQueryParams, GetAllowedParamNames
  - Add tests for isHexString, looksLikeAPIKey
  - Fix test cases to match actual implementation behavior

- audit/sanitizer: Fix MaskMap []string handling bug
  - Add maskSliceInterface for []interface{} type
  - Tests now pass for string slice sensitive fields

All tests pass
2026-04-08 09:00:29 +08:00

220 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package security
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
)
// ==================== P0-02 KMS加密方案 ====================
// AES-256-GCM算法参数
const (
AES256GCMKeySize = 32 // 256位 = 32字节
AES256GCMAuthTagSize = 16 // 128位认证标签
CurrentKeyVersion = 1
)
// KeyVersionError 密钥版本错误
type KeyVersionError struct {
ExpectedVersion int
ActualVersion int
}
func (e *KeyVersionError) Error() string {
return fmt.Sprintf("key version mismatch: expected %d, got %d", e.ExpectedVersion, e.ActualVersion)
}
// DecryptionError 解密错误
type DecryptionError struct {
Reason string
}
func (e *DecryptionError) Error() string {
return fmt.Sprintf("decryption failed: %s", e.Reason)
}
// KMSConfig KMS配置
type KMSConfig struct {
KeyID string // KMS密钥ID
KeyVersion int // 当前密钥版本
MaxRetries int // 最大重试次数
ProviderType string // "aws" | "hashicorp" | "local"
}
// DefaultKMSConfig 默认KMS配置
func DefaultKMSConfig() *KMSConfig {
return &KMSConfig{
KeyID: "kms/supply/default",
KeyVersion: 1,
MaxRetries: 3,
ProviderType: "local", // 本地开发模式生产应使用aws或hashicorp
}
}
// KMSService KMS加密服务
type KMSService struct {
config *KMSConfig
}
// NewKMSService 创建KMS服务
func NewKMSService(config *KMSConfig) *KMSService {
if config == nil {
config = DefaultKMSConfig()
}
return &KMSService{config: config}
}
// EnvelopeEncryptionResult 信封加密结果
type EnvelopeEncryptionResult struct {
EncryptedData []byte // 加密后的数据
KeyVersion int // 使用的密钥版本
DEK []byte // 数据加密密钥(仅本地模式返回)
}
// Encrypt 加密数据(信封加密)
// 格式: [key_version:4][nonce:12][ciphertext][auth_tag:16]
func (s *KMSService) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error) {
// 1. 获取数据加密密钥 (DEK) - 简化实现使用派生密钥
// 生产环境应从KMS获取或使用随机DEK加密后存储
dek, err := s.getDEKForVersion(s.config.KeyVersion)
if err != nil {
return nil, err
}
// 2. 使用DEK加密数据
aead, err := s.createGCM(dek)
if err != nil {
return nil, err
}
// 3. 生成随机nonce
nonce := make([]byte, aead.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, fmt.Errorf("failed to generate nonce: %w", err)
}
// 4. 加密
ciphertext := aead.Seal(nil, nonce, plaintext, nil)
// 5. 组装结果: [key_version(4)][nonce][ciphertext+auth_tag]
result := make([]byte, 4+aead.NonceSize()+len(ciphertext))
binary.BigEndian.PutUint32(result[0:4], uint32(s.config.KeyVersion))
copy(result[4:4+aead.NonceSize()], nonce)
copy(result[4+aead.NonceSize():], ciphertext)
return result, nil
}
// Decrypt 解密数据(信封加密)
func (s *KMSService) Decrypt(ctx context.Context, encryptedData []byte) ([]byte, error) {
if len(encryptedData) < 4+12+AES256GCMAuthTagSize {
return nil, &DecryptionError{Reason: "data too short"}
}
// 1. 提取密钥版本
keyVersion := int(binary.BigEndian.Uint32(encryptedData[0:4]))
// 2. 提取nonce
nonceSize := 12 // GCM标准nonce大小
nonce := encryptedData[4 : 4+nonceSize]
// 3. 提取密文
ciphertext := encryptedData[4+nonceSize:]
// 4. 获取对应版本的DEK这里简化处理实际应从KMS获取
dek, err := s.getDEKForVersion(keyVersion)
if err != nil {
return nil, err
}
// 5. 解密
aead, err := s.createGCM(dek)
if err != nil {
return nil, err
}
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, &DecryptionError{Reason: err.Error()}
}
return plaintext, nil
}
// RotateKey 轮换密钥
func (s *KMSService) RotateKey(ctx context.Context, keyID string) (string, error) {
// 递增密钥版本
s.config.KeyVersion++
// 生成新的密钥ID
newKeyID := fmt.Sprintf("%s-v%d", keyID, s.config.KeyVersion)
return newKeyID, nil
}
// createGCM 创建GCM cipher
func (s *KMSService) createGCM(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to create AES cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("failed to create GCM: %w", err)
}
return gcm, nil
}
// getDEKForVersion 获取指定版本的DEK
// 实际实现应从AWS KMS或HashiCorp Vault获取
func (s *KMSService) getDEKForVersion(version int) ([]byte, error) {
// 本地开发模式使用固定密钥实际应从KMS安全获取
// 注意这是简化的开发实现生产必须使用真正的KMS
if version == s.config.KeyVersion {
// 返回当前版本的DEK这里使用派生方式简化
return deriveDEK(s.config.KeyID, version), nil
}
// 旧版本密钥支持(向后兼容)
// 实际应从密钥历史存储获取
if version < s.config.KeyVersion && version > 0 {
return deriveDEK(s.config.KeyID, version), nil
}
return nil, &KeyVersionError{
ExpectedVersion: s.config.KeyVersion,
ActualVersion: version,
}
}
// deriveDEK 派生DEK简化实现
// 实际生产环境应使用KMS的Decrypt API
func deriveDEK(keyID string, version int) []byte {
// 简化:返回固定派生密钥(仅用于开发)
// 生产环境必须使用真正的KMS密钥派生
derived := make([]byte, AES256GCMKeySize)
for i := 0; i < AES256GCMKeySize; i++ {
derived[i] = byte((i + version) % 256)
}
return derived
}
// ValidateKeyID 验证密钥ID格式
func ValidateKeyID(keyID string) error {
if keyID == "" {
return errors.New("key ID cannot be empty")
}
if len(keyID) > 128 {
return errors.New("key ID too long (max 128 chars)")
}
return nil
}