aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3_sse_c.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/s3_sse_c.go')
-rw-r--r--weed/s3api/s3_sse_c.go194
1 files changed, 132 insertions, 62 deletions
diff --git a/weed/s3api/s3_sse_c.go b/weed/s3api/s3_sse_c.go
index 3e7d6fc02..7eb5cf474 100644
--- a/weed/s3api/s3_sse_c.go
+++ b/weed/s3api/s3_sse_c.go
@@ -1,7 +1,6 @@
package s3api
import (
- "bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
@@ -12,10 +11,21 @@ import (
"io"
"net/http"
+ "github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)
+// SSECCopyStrategy represents different strategies for copying SSE-C objects
+type SSECCopyStrategy int
+
+const (
+ // SSECCopyStrategyDirect indicates the object can be copied directly without decryption
+ SSECCopyStrategyDirect SSECCopyStrategy = iota
+ // SSECCopyStrategyDecryptEncrypt indicates the object must be decrypted then re-encrypted
+ SSECCopyStrategyDecryptEncrypt
+)
+
const (
// SSE-C constants
SSECustomerAlgorithmAES256 = "AES256"
@@ -40,19 +50,34 @@ type SSECustomerKey struct {
KeyMD5 string
}
-// SSECDecryptedReader wraps an io.Reader to provide SSE-C decryption
-type SSECDecryptedReader struct {
- reader io.Reader
- cipher cipher.Stream
- customerKey *SSECustomerKey
- first bool
-}
-
// IsSSECRequest checks if the request contains SSE-C headers
func IsSSECRequest(r *http.Request) bool {
+ // If SSE-KMS headers are present, this is not an SSE-C request (they are mutually exclusive)
+ sseAlgorithm := r.Header.Get(s3_constants.AmzServerSideEncryption)
+ if sseAlgorithm == "aws:kms" || r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId) != "" {
+ return false
+ }
+
return r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm) != ""
}
+// IsSSECEncrypted checks if the metadata indicates SSE-C encryption
+func IsSSECEncrypted(metadata map[string][]byte) bool {
+ if metadata == nil {
+ return false
+ }
+
+ // Check for SSE-C specific metadata keys
+ if _, exists := metadata[s3_constants.AmzServerSideEncryptionCustomerAlgorithm]; exists {
+ return true
+ }
+ if _, exists := metadata[s3_constants.AmzServerSideEncryptionCustomerKeyMD5]; exists {
+ return true
+ }
+
+ return false
+}
+
// validateAndParseSSECHeaders does the core validation and parsing logic
func validateAndParseSSECHeaders(algorithm, key, keyMD5 string) (*SSECustomerKey, error) {
if algorithm == "" && key == "" && keyMD5 == "" {
@@ -80,7 +105,12 @@ func validateAndParseSSECHeaders(algorithm, key, keyMD5 string) (*SSECustomerKey
// Validate key MD5 (base64-encoded MD5 of the raw key bytes; case-sensitive)
sum := md5.Sum(keyBytes)
expectedMD5 := base64.StdEncoding.EncodeToString(sum[:])
+
+ // Debug logging for MD5 validation
+ glog.V(4).Infof("SSE-C MD5 validation: provided='%s', expected='%s', keyBytes=%x", keyMD5, expectedMD5, keyBytes)
+
if keyMD5 != expectedMD5 {
+ glog.Errorf("SSE-C MD5 mismatch: provided='%s', expected='%s'", keyMD5, expectedMD5)
return nil, ErrSSECustomerKeyMD5Mismatch
}
@@ -120,76 +150,122 @@ func ParseSSECCopySourceHeaders(r *http.Request) (*SSECustomerKey, error) {
}
// CreateSSECEncryptedReader creates a new encrypted reader for SSE-C
-func CreateSSECEncryptedReader(r io.Reader, customerKey *SSECustomerKey) (io.Reader, error) {
+// Returns the encrypted reader and the IV for metadata storage
+func CreateSSECEncryptedReader(r io.Reader, customerKey *SSECustomerKey) (io.Reader, []byte, error) {
if customerKey == nil {
- return r, nil
+ return r, nil, nil
}
// Create AES cipher
block, err := aes.NewCipher(customerKey.Key)
if err != nil {
- return nil, fmt.Errorf("failed to create AES cipher: %v", err)
+ return nil, nil, fmt.Errorf("failed to create AES cipher: %v", err)
}
// Generate random IV
iv := make([]byte, AESBlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
- return nil, fmt.Errorf("failed to generate IV: %v", err)
+ return nil, nil, fmt.Errorf("failed to generate IV: %v", err)
}
// Create CTR mode cipher
stream := cipher.NewCTR(block, iv)
- // The encrypted stream is the IV (initialization vector) followed by the encrypted data.
- // The IV is randomly generated for each encryption operation and must be unique and unpredictable.
- // This is critical for the security of AES-CTR mode: reusing an IV with the same key breaks confidentiality.
- // By prepending the IV to the ciphertext, the decryptor can extract the IV to initialize the cipher.
- // Note: AES-CTR provides confidentiality only; use an additional MAC if integrity is required.
- // We model this with an io.MultiReader (IV first) and a cipher.StreamReader (encrypted payload).
- return io.MultiReader(bytes.NewReader(iv), &cipher.StreamReader{S: stream, R: r}), nil
+ // The IV is stored in metadata, so the encrypted stream does not need to prepend the IV
+ // This ensures correct Content-Length for clients
+ encryptedReader := &cipher.StreamReader{S: stream, R: r}
+
+ return encryptedReader, iv, nil
}
// CreateSSECDecryptedReader creates a new decrypted reader for SSE-C
-func CreateSSECDecryptedReader(r io.Reader, customerKey *SSECustomerKey) (io.Reader, error) {
+// The IV comes from metadata, not from the encrypted data stream
+func CreateSSECDecryptedReader(r io.Reader, customerKey *SSECustomerKey, iv []byte) (io.Reader, error) {
if customerKey == nil {
return r, nil
}
- return &SSECDecryptedReader{
- reader: r,
- customerKey: customerKey,
- cipher: nil, // Will be initialized when we read the IV
- first: true,
- }, nil
+ // IV must be provided from metadata
+ if len(iv) != AESBlockSize {
+ return nil, fmt.Errorf("invalid IV length: expected %d bytes, got %d", AESBlockSize, len(iv))
+ }
+
+ // Create AES cipher
+ block, err := aes.NewCipher(customerKey.Key)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create AES cipher: %v", err)
+ }
+
+ // Create CTR mode cipher using the IV from metadata
+ stream := cipher.NewCTR(block, iv)
+
+ return &cipher.StreamReader{S: stream, R: r}, nil
}
-// Read implements io.Reader for SSECDecryptedReader
-func (r *SSECDecryptedReader) Read(p []byte) (n int, err error) {
- if r.first {
- // First read: extract IV and initialize cipher
- r.first = false
- iv := make([]byte, AESBlockSize)
-
- // Read IV from the beginning of the data
- _, err = io.ReadFull(r.reader, iv)
- if err != nil {
- return 0, fmt.Errorf("failed to read IV: %v", err)
- }
+// CreateSSECEncryptedReaderWithOffset creates an encrypted reader with a specific counter offset
+// This is used for chunk-level encryption where each chunk needs a different counter position
+func CreateSSECEncryptedReaderWithOffset(r io.Reader, customerKey *SSECustomerKey, iv []byte, counterOffset uint64) (io.Reader, error) {
+ if customerKey == nil {
+ return r, nil
+ }
- // Create cipher with the extracted IV
- block, err := aes.NewCipher(r.customerKey.Key)
- if err != nil {
- return 0, fmt.Errorf("failed to create AES cipher: %v", err)
- }
- r.cipher = cipher.NewCTR(block, iv)
+ // Create AES cipher
+ block, err := aes.NewCipher(customerKey.Key)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create AES cipher: %v", err)
}
- // Decrypt data
- n, err = r.reader.Read(p)
- if n > 0 {
- r.cipher.XORKeyStream(p[:n], p[:n])
+ // Create CTR mode cipher with offset
+ stream := createCTRStreamWithOffset(block, iv, counterOffset)
+
+ return &cipher.StreamReader{S: stream, R: r}, nil
+}
+
+// CreateSSECDecryptedReaderWithOffset creates a decrypted reader with a specific counter offset
+func CreateSSECDecryptedReaderWithOffset(r io.Reader, customerKey *SSECustomerKey, iv []byte, counterOffset uint64) (io.Reader, error) {
+ if customerKey == nil {
+ return r, nil
+ }
+
+ // Create AES cipher
+ block, err := aes.NewCipher(customerKey.Key)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create AES cipher: %v", err)
+ }
+
+ // Create CTR mode cipher with offset
+ stream := createCTRStreamWithOffset(block, iv, counterOffset)
+
+ return &cipher.StreamReader{S: stream, R: r}, nil
+}
+
+// createCTRStreamWithOffset creates a CTR stream positioned at a specific counter offset
+func createCTRStreamWithOffset(block cipher.Block, iv []byte, counterOffset uint64) cipher.Stream {
+ // Create a copy of the IV to avoid modifying the original
+ offsetIV := make([]byte, len(iv))
+ copy(offsetIV, iv)
+
+ // Calculate the counter offset in blocks (AES block size is 16 bytes)
+ blockOffset := counterOffset / 16
+
+ // Add the block offset to the counter portion of the IV
+ // In AES-CTR, the last 8 bytes of the IV are typically used as the counter
+ addCounterToIV(offsetIV, blockOffset)
+
+ return cipher.NewCTR(block, offsetIV)
+}
+
+// addCounterToIV adds a counter value to the IV (treating last 8 bytes as big-endian counter)
+func addCounterToIV(iv []byte, counter uint64) {
+ // Use the last 8 bytes as a big-endian counter
+ for i := 7; i >= 0; i-- {
+ carry := counter & 0xff
+ iv[len(iv)-8+i] += byte(carry)
+ if iv[len(iv)-8+i] >= byte(carry) {
+ break // No overflow
+ }
+ counter >>= 8
}
- return n, err
}
// GetSourceSSECInfo extracts SSE-C information from source object metadata
@@ -224,13 +300,7 @@ func CanDirectCopySSEC(srcMetadata map[string][]byte, copySourceKey *SSECustomer
return false
}
-// SSECCopyStrategy represents the strategy for copying SSE-C objects
-type SSECCopyStrategy int
-
-const (
- SSECCopyDirect SSECCopyStrategy = iota // Direct chunk copy (fast)
- SSECCopyReencrypt // Decrypt and re-encrypt (slow)
-)
+// Note: SSECCopyStrategy is defined above
// DetermineSSECCopyStrategy determines the optimal copy strategy
func DetermineSSECCopyStrategy(srcMetadata map[string][]byte, copySourceKey *SSECustomerKey, destKey *SSECustomerKey) (SSECCopyStrategy, error) {
@@ -239,21 +309,21 @@ func DetermineSSECCopyStrategy(srcMetadata map[string][]byte, copySourceKey *SSE
// Validate source key if source is encrypted
if srcEncrypted {
if copySourceKey == nil {
- return SSECCopyReencrypt, ErrSSECustomerKeyMissing
+ return SSECCopyStrategyDecryptEncrypt, ErrSSECustomerKeyMissing
}
if copySourceKey.KeyMD5 != srcKeyMD5 {
- return SSECCopyReencrypt, ErrSSECustomerKeyMD5Mismatch
+ return SSECCopyStrategyDecryptEncrypt, ErrSSECustomerKeyMD5Mismatch
}
} else if copySourceKey != nil {
// Source not encrypted but copy source key provided
- return SSECCopyReencrypt, ErrSSECustomerKeyNotNeeded
+ return SSECCopyStrategyDecryptEncrypt, ErrSSECustomerKeyNotNeeded
}
if CanDirectCopySSEC(srcMetadata, copySourceKey, destKey) {
- return SSECCopyDirect, nil
+ return SSECCopyStrategyDirect, nil
}
- return SSECCopyReencrypt, nil
+ return SSECCopyStrategyDecryptEncrypt, nil
}
// MapSSECErrorToS3Error maps SSE-C custom errors to S3 API error codes