You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							99 lines
						
					
					
						
							3.5 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							99 lines
						
					
					
						
							3.5 KiB
						
					
					
				| 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, | |
| 	} | |
| }
 |