aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3_sse_kms_utils.go
blob: be6d726261e3124bc41f451a67165654d4fa0a53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package s3api

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"fmt"
	"strings"

	"github.com/seaweedfs/seaweedfs/weed/kms"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
)

// KMSDataKeyResult holds the result of data key generation
type KMSDataKeyResult struct {
	Response *kms.GenerateDataKeyResponse
	Block    cipher.Block
}

// generateKMSDataKey generates a new data encryption key using KMS
// This function encapsulates the common pattern used across all SSE-KMS functions
func generateKMSDataKey(keyID string, encryptionContext map[string]string) (*KMSDataKeyResult, error) {
	// Validate keyID to prevent injection attacks and malformed requests to KMS service
	if !isValidKMSKeyID(keyID) {
		return nil, fmt.Errorf("invalid KMS key ID format: key ID must be non-empty, without spaces or control characters")
	}

	// Validate encryption context to prevent malformed requests to KMS service
	if encryptionContext != nil {
		for key, value := range encryptionContext {
			// Validate context keys and values for basic security
			if strings.TrimSpace(key) == "" {
				return nil, fmt.Errorf("invalid encryption context: keys cannot be empty or whitespace-only")
			}
			if strings.ContainsAny(key, "\x00\n\r\t") || strings.ContainsAny(value, "\x00\n\r\t") {
				return nil, fmt.Errorf("invalid encryption context: keys and values cannot contain control characters")
			}
			// AWS KMS has limits on key/value lengths
			if len(key) > 2048 || len(value) > 2048 {
				return nil, fmt.Errorf("invalid encryption context: keys and values must be ≤ 2048 characters (key=%d, value=%d)", len(key), len(value))
			}
		}
		// AWS KMS has a limit on the total number of context pairs
		if len(encryptionContext) > s3_constants.MaxKMSEncryptionContextPairs {
			return nil, fmt.Errorf("invalid encryption context: cannot exceed %d key-value pairs, got %d", s3_constants.MaxKMSEncryptionContextPairs, len(encryptionContext))
		}
	}

	// Get KMS provider
	kmsProvider := kms.GetGlobalKMS()
	if kmsProvider == nil {
		return nil, fmt.Errorf("KMS is not configured")
	}

	// Create data key request
	generateDataKeyReq := &kms.GenerateDataKeyRequest{
		KeyID:             keyID,
		KeySpec:           kms.KeySpecAES256,
		EncryptionContext: encryptionContext,
	}

	// Generate the data key
	dataKeyResp, err := kmsProvider.GenerateDataKey(context.Background(), generateDataKeyReq)
	if err != nil {
		return nil, fmt.Errorf("failed to generate KMS data key: %v", err)
	}

	// Create AES cipher with the plaintext data key
	block, err := aes.NewCipher(dataKeyResp.Plaintext)
	if err != nil {
		// Clear sensitive data before returning error
		kms.ClearSensitiveData(dataKeyResp.Plaintext)
		return nil, fmt.Errorf("failed to create AES cipher: %v", err)
	}

	return &KMSDataKeyResult{
		Response: dataKeyResp,
		Block:    block,
	}, nil
}

// clearKMSDataKey safely clears sensitive data from a KMSDataKeyResult
func clearKMSDataKey(result *KMSDataKeyResult) {
	if result != nil && result.Response != nil {
		kms.ClearSensitiveData(result.Response.Plaintext)
	}
}

// createSSEKMSKey creates an SSEKMSKey struct from data key result and parameters
func createSSEKMSKey(result *KMSDataKeyResult, encryptionContext map[string]string, bucketKeyEnabled bool, iv []byte, chunkOffset int64) *SSEKMSKey {
	return &SSEKMSKey{
		KeyID:             result.Response.KeyID,
		EncryptedDataKey:  result.Response.CiphertextBlob,
		EncryptionContext: encryptionContext,
		BucketKeyEnabled:  bucketKeyEnabled,
		IV:                iv,
		ChunkOffset:       chunkOffset,
	}
}