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.
316 lines
9.0 KiB
316 lines
9.0 KiB
package s3api
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
mathrand "math/rand"
|
|
"net/http"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
)
|
|
|
|
// SSE-S3 uses AES-256 encryption with server-managed keys
|
|
const (
|
|
SSES3Algorithm = s3_constants.SSEAlgorithmAES256
|
|
SSES3KeySize = 32 // 256 bits
|
|
)
|
|
|
|
// SSES3Key represents a server-managed encryption key for SSE-S3
|
|
type SSES3Key struct {
|
|
Key []byte
|
|
KeyID string
|
|
Algorithm string
|
|
IV []byte // Initialization Vector for this key
|
|
}
|
|
|
|
// IsSSES3RequestInternal checks if the request specifies SSE-S3 encryption
|
|
func IsSSES3RequestInternal(r *http.Request) bool {
|
|
sseHeader := r.Header.Get(s3_constants.AmzServerSideEncryption)
|
|
result := sseHeader == SSES3Algorithm
|
|
|
|
// Debug: log header detection for SSE-S3 requests
|
|
if result {
|
|
glog.V(4).Infof("SSE-S3 detection: method=%s, header=%q, expected=%q, result=%t, copySource=%q", r.Method, sseHeader, SSES3Algorithm, result, r.Header.Get("X-Amz-Copy-Source"))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// IsSSES3EncryptedInternal checks if the object metadata indicates SSE-S3 encryption
|
|
func IsSSES3EncryptedInternal(metadata map[string][]byte) bool {
|
|
if sseAlgorithm, exists := metadata[s3_constants.AmzServerSideEncryption]; exists {
|
|
return string(sseAlgorithm) == SSES3Algorithm
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GenerateSSES3Key generates a new SSE-S3 encryption key
|
|
func GenerateSSES3Key() (*SSES3Key, error) {
|
|
key := make([]byte, SSES3KeySize)
|
|
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
|
return nil, fmt.Errorf("failed to generate SSE-S3 key: %w", err)
|
|
}
|
|
|
|
// Generate a key ID for tracking
|
|
keyID := fmt.Sprintf("sse-s3-key-%d", mathrand.Int63())
|
|
|
|
return &SSES3Key{
|
|
Key: key,
|
|
KeyID: keyID,
|
|
Algorithm: SSES3Algorithm,
|
|
}, nil
|
|
}
|
|
|
|
// CreateSSES3EncryptedReader creates an encrypted reader for SSE-S3
|
|
// Returns the encrypted reader and the IV for metadata storage
|
|
func CreateSSES3EncryptedReader(reader io.Reader, key *SSES3Key) (io.Reader, []byte, error) {
|
|
// Create AES cipher
|
|
block, err := aes.NewCipher(key.Key)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("create AES cipher: %w", err)
|
|
}
|
|
|
|
// Generate random IV
|
|
iv := make([]byte, aes.BlockSize)
|
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
return nil, nil, fmt.Errorf("generate IV: %w", err)
|
|
}
|
|
|
|
// Create CTR mode cipher
|
|
stream := cipher.NewCTR(block, iv)
|
|
|
|
// Return encrypted reader and IV separately for metadata storage
|
|
encryptedReader := &cipher.StreamReader{S: stream, R: reader}
|
|
|
|
return encryptedReader, iv, nil
|
|
}
|
|
|
|
// CreateSSES3DecryptedReader creates a decrypted reader for SSE-S3 using IV from metadata
|
|
func CreateSSES3DecryptedReader(reader io.Reader, key *SSES3Key, iv []byte) (io.Reader, error) {
|
|
// Create AES cipher
|
|
block, err := aes.NewCipher(key.Key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create AES cipher: %w", err)
|
|
}
|
|
|
|
// Create CTR mode cipher with the provided IV
|
|
stream := cipher.NewCTR(block, iv)
|
|
|
|
return &cipher.StreamReader{S: stream, R: reader}, nil
|
|
}
|
|
|
|
// GetSSES3Headers returns the headers for SSE-S3 encrypted objects
|
|
func GetSSES3Headers() map[string]string {
|
|
return map[string]string{
|
|
s3_constants.AmzServerSideEncryption: SSES3Algorithm,
|
|
}
|
|
}
|
|
|
|
// SerializeSSES3Metadata serializes SSE-S3 metadata for storage
|
|
func SerializeSSES3Metadata(key *SSES3Key) ([]byte, error) {
|
|
if err := ValidateSSES3Key(key); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// For SSE-S3, we typically don't store the actual key in metadata
|
|
// Instead, we store a key ID or reference that can be used to retrieve the key
|
|
// from a secure key management system
|
|
|
|
metadata := map[string]string{
|
|
"algorithm": key.Algorithm,
|
|
"keyId": key.KeyID,
|
|
}
|
|
|
|
// Include IV if present (needed for chunk-level decryption)
|
|
if key.IV != nil {
|
|
metadata["iv"] = base64.StdEncoding.EncodeToString(key.IV)
|
|
}
|
|
|
|
// Use JSON for proper serialization
|
|
data, err := json.Marshal(metadata)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal SSE-S3 metadata: %w", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// DeserializeSSES3Metadata deserializes SSE-S3 metadata from storage and retrieves the actual key
|
|
func DeserializeSSES3Metadata(data []byte, keyManager *SSES3KeyManager) (*SSES3Key, error) {
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("empty SSE-S3 metadata")
|
|
}
|
|
|
|
// Parse the JSON metadata to extract keyId
|
|
var metadata map[string]string
|
|
if err := json.Unmarshal(data, &metadata); err != nil {
|
|
return nil, fmt.Errorf("failed to parse SSE-S3 metadata: %w", err)
|
|
}
|
|
|
|
keyID, exists := metadata["keyId"]
|
|
if !exists {
|
|
return nil, fmt.Errorf("keyId not found in SSE-S3 metadata")
|
|
}
|
|
|
|
algorithm, exists := metadata["algorithm"]
|
|
if !exists {
|
|
algorithm = s3_constants.SSEAlgorithmAES256 // Default algorithm
|
|
}
|
|
|
|
// Retrieve the actual key using the keyId
|
|
if keyManager == nil {
|
|
return nil, fmt.Errorf("key manager is required for SSE-S3 key retrieval")
|
|
}
|
|
|
|
key, err := keyManager.GetOrCreateKey(keyID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve SSE-S3 key with ID %s: %w", keyID, err)
|
|
}
|
|
|
|
// Verify the algorithm matches
|
|
if key.Algorithm != algorithm {
|
|
return nil, fmt.Errorf("algorithm mismatch: expected %s, got %s", algorithm, key.Algorithm)
|
|
}
|
|
|
|
// Restore IV if present in metadata (for chunk-level decryption)
|
|
if ivStr, exists := metadata["iv"]; exists {
|
|
iv, err := base64.StdEncoding.DecodeString(ivStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode IV: %w", err)
|
|
}
|
|
key.IV = iv
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// SSES3KeyManager manages SSE-S3 encryption keys
|
|
type SSES3KeyManager struct {
|
|
// In a production system, this would interface with a secure key management system
|
|
keys map[string]*SSES3Key
|
|
}
|
|
|
|
// NewSSES3KeyManager creates a new SSE-S3 key manager
|
|
func NewSSES3KeyManager() *SSES3KeyManager {
|
|
return &SSES3KeyManager{
|
|
keys: make(map[string]*SSES3Key),
|
|
}
|
|
}
|
|
|
|
// GetOrCreateKey gets an existing key or creates a new one
|
|
func (km *SSES3KeyManager) GetOrCreateKey(keyID string) (*SSES3Key, error) {
|
|
if keyID == "" {
|
|
// Generate new key
|
|
return GenerateSSES3Key()
|
|
}
|
|
|
|
// Check if key exists
|
|
if key, exists := km.keys[keyID]; exists {
|
|
return key, nil
|
|
}
|
|
|
|
// Create new key
|
|
key, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key.KeyID = keyID
|
|
km.keys[keyID] = key
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// StoreKey stores a key in the manager
|
|
func (km *SSES3KeyManager) StoreKey(key *SSES3Key) {
|
|
km.keys[key.KeyID] = key
|
|
}
|
|
|
|
// GetKey retrieves a key by ID
|
|
func (km *SSES3KeyManager) GetKey(keyID string) (*SSES3Key, bool) {
|
|
key, exists := km.keys[keyID]
|
|
return key, exists
|
|
}
|
|
|
|
// Global SSE-S3 key manager instance
|
|
var globalSSES3KeyManager = NewSSES3KeyManager()
|
|
|
|
// GetSSES3KeyManager returns the global SSE-S3 key manager
|
|
func GetSSES3KeyManager() *SSES3KeyManager {
|
|
return globalSSES3KeyManager
|
|
}
|
|
|
|
// ProcessSSES3Request processes an SSE-S3 request and returns encryption metadata
|
|
func ProcessSSES3Request(r *http.Request) (map[string][]byte, error) {
|
|
if !IsSSES3RequestInternal(r) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Generate or retrieve encryption key
|
|
keyManager := GetSSES3KeyManager()
|
|
key, err := keyManager.GetOrCreateKey("")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get SSE-S3 key: %w", err)
|
|
}
|
|
|
|
// Serialize key metadata
|
|
keyData, err := SerializeSSES3Metadata(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("serialize SSE-S3 metadata: %w", err)
|
|
}
|
|
|
|
// Store key in manager
|
|
keyManager.StoreKey(key)
|
|
|
|
// Return metadata
|
|
metadata := map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte(SSES3Algorithm),
|
|
s3_constants.SeaweedFSSSES3Key: keyData,
|
|
}
|
|
|
|
return metadata, nil
|
|
}
|
|
|
|
// GetSSES3KeyFromMetadata extracts SSE-S3 key from object metadata
|
|
func GetSSES3KeyFromMetadata(metadata map[string][]byte, keyManager *SSES3KeyManager) (*SSES3Key, error) {
|
|
keyData, exists := metadata[s3_constants.SeaweedFSSSES3Key]
|
|
if !exists {
|
|
return nil, fmt.Errorf("SSE-S3 key not found in metadata")
|
|
}
|
|
|
|
return DeserializeSSES3Metadata(keyData, keyManager)
|
|
}
|
|
|
|
// CreateSSES3EncryptedReaderWithBaseIV creates an encrypted reader using a base IV for multipart upload consistency.
|
|
// The returned IV is the offset-derived IV, calculated from the input baseIV and offset.
|
|
func CreateSSES3EncryptedReaderWithBaseIV(reader io.Reader, key *SSES3Key, baseIV []byte, offset int64) (io.Reader, []byte /* derivedIV */, error) {
|
|
// Validate key to prevent panics and security issues
|
|
if key == nil {
|
|
return nil, nil, fmt.Errorf("SSES3Key is nil")
|
|
}
|
|
if key.Key == nil || len(key.Key) != SSES3KeySize {
|
|
return nil, nil, fmt.Errorf("invalid SSES3Key: must be %d bytes, got %d", SSES3KeySize, len(key.Key))
|
|
}
|
|
if err := ValidateSSES3Key(key); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
block, err := aes.NewCipher(key.Key)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("create AES cipher: %w", err)
|
|
}
|
|
|
|
// Calculate the proper IV with offset to ensure unique IV per chunk/part
|
|
// This prevents the severe security vulnerability of IV reuse in CTR mode
|
|
iv := calculateIVWithOffset(baseIV, offset)
|
|
|
|
stream := cipher.NewCTR(block, iv)
|
|
encryptedReader := &cipher.StreamReader{S: stream, R: reader}
|
|
return encryptedReader, iv, nil
|
|
}
|