s3_sse_kms.go 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. package s3api
  2. import (
  3. "context"
  4. "crypto/aes"
  5. "crypto/cipher"
  6. "crypto/rand"
  7. "crypto/sha256"
  8. "encoding/base64"
  9. "encoding/hex"
  10. "encoding/json"
  11. "fmt"
  12. "io"
  13. "net/http"
  14. "regexp"
  15. "sort"
  16. "strings"
  17. "time"
  18. "github.com/seaweedfs/seaweedfs/weed/glog"
  19. "github.com/seaweedfs/seaweedfs/weed/kms"
  20. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  21. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  22. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  23. )
  24. // Compiled regex patterns for KMS key validation
  25. var (
  26. uuidRegex = regexp.MustCompile(`^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$`)
  27. arnRegex = regexp.MustCompile(`^arn:aws:kms:[a-z0-9-]+:\d{12}:(key|alias)/.+$`)
  28. )
  29. // SSEKMSKey contains the metadata for an SSE-KMS encrypted object
  30. type SSEKMSKey struct {
  31. KeyID string // The KMS key ID used
  32. EncryptedDataKey []byte // The encrypted data encryption key
  33. EncryptionContext map[string]string // The encryption context used
  34. BucketKeyEnabled bool // Whether S3 Bucket Keys are enabled
  35. IV []byte // The initialization vector for encryption
  36. ChunkOffset int64 // Offset of this chunk within the original part (for IV calculation)
  37. }
  38. // SSEKMSMetadata represents the metadata stored with SSE-KMS objects
  39. type SSEKMSMetadata struct {
  40. Algorithm string `json:"algorithm"` // "aws:kms"
  41. KeyID string `json:"keyId"` // KMS key identifier
  42. EncryptedDataKey string `json:"encryptedDataKey"` // Base64-encoded encrypted data key
  43. EncryptionContext map[string]string `json:"encryptionContext"` // Encryption context
  44. BucketKeyEnabled bool `json:"bucketKeyEnabled"` // S3 Bucket Key optimization
  45. IV string `json:"iv"` // Base64-encoded initialization vector
  46. PartOffset int64 `json:"partOffset"` // Offset within original multipart part (for IV calculation)
  47. }
  48. const (
  49. // Default data key size (256 bits)
  50. DataKeySize = 32
  51. )
  52. // Bucket key cache TTL (moved to be used with per-bucket cache)
  53. const BucketKeyCacheTTL = time.Hour
  54. // CreateSSEKMSEncryptedReader creates an encrypted reader using KMS envelope encryption
  55. func CreateSSEKMSEncryptedReader(r io.Reader, keyID string, encryptionContext map[string]string) (io.Reader, *SSEKMSKey, error) {
  56. return CreateSSEKMSEncryptedReaderWithBucketKey(r, keyID, encryptionContext, false)
  57. }
  58. // CreateSSEKMSEncryptedReaderWithBucketKey creates an encrypted reader with optional S3 Bucket Keys optimization
  59. func CreateSSEKMSEncryptedReaderWithBucketKey(r io.Reader, keyID string, encryptionContext map[string]string, bucketKeyEnabled bool) (io.Reader, *SSEKMSKey, error) {
  60. if bucketKeyEnabled {
  61. // Use S3 Bucket Keys optimization - try to get or create a bucket-level data key
  62. // Note: This is a simplified implementation. In practice, this would need
  63. // access to the bucket name and S3ApiServer instance for proper per-bucket caching.
  64. // For now, generate per-object keys (bucket key optimization disabled)
  65. glog.V(2).Infof("Bucket key optimization requested but not fully implemented yet - using per-object keys")
  66. bucketKeyEnabled = false
  67. }
  68. // Generate data key using common utility
  69. dataKeyResult, err := generateKMSDataKey(keyID, encryptionContext)
  70. if err != nil {
  71. return nil, nil, err
  72. }
  73. // Ensure we clear the plaintext data key from memory when done
  74. defer clearKMSDataKey(dataKeyResult)
  75. // Generate a random IV for CTR mode
  76. // Note: AES-CTR is used for object data encryption (not AES-GCM) because:
  77. // 1. CTR mode supports streaming encryption for large objects
  78. // 2. CTR mode supports range requests (seek to arbitrary positions)
  79. // 3. This matches AWS S3 and other S3-compatible implementations
  80. // The KMS data key encryption (separate layer) uses AES-GCM for authentication
  81. iv := make([]byte, s3_constants.AESBlockSize)
  82. if _, err := io.ReadFull(rand.Reader, iv); err != nil {
  83. return nil, nil, fmt.Errorf("failed to generate IV: %v", err)
  84. }
  85. // Create CTR mode cipher stream
  86. stream := cipher.NewCTR(dataKeyResult.Block, iv)
  87. // Create the SSE-KMS metadata using utility function
  88. sseKey := createSSEKMSKey(dataKeyResult, encryptionContext, bucketKeyEnabled, iv, 0)
  89. // The IV is stored in SSE key metadata, so the encrypted stream does not need to prepend the IV
  90. // This ensures correct Content-Length for clients
  91. encryptedReader := &cipher.StreamReader{S: stream, R: r}
  92. // Store IV in the SSE key for metadata storage
  93. sseKey.IV = iv
  94. return encryptedReader, sseKey, nil
  95. }
  96. // CreateSSEKMSEncryptedReaderWithBaseIV creates an SSE-KMS encrypted reader using a provided base IV
  97. // This is used for multipart uploads where all chunks need to use the same base IV
  98. func CreateSSEKMSEncryptedReaderWithBaseIV(r io.Reader, keyID string, encryptionContext map[string]string, bucketKeyEnabled bool, baseIV []byte) (io.Reader, *SSEKMSKey, error) {
  99. if err := ValidateIV(baseIV, "base IV"); err != nil {
  100. return nil, nil, err
  101. }
  102. // Generate data key using common utility
  103. dataKeyResult, err := generateKMSDataKey(keyID, encryptionContext)
  104. if err != nil {
  105. return nil, nil, err
  106. }
  107. // Ensure we clear the plaintext data key from memory when done
  108. defer clearKMSDataKey(dataKeyResult)
  109. // Use the provided base IV instead of generating a new one
  110. iv := make([]byte, s3_constants.AESBlockSize)
  111. copy(iv, baseIV)
  112. // Create CTR mode cipher stream
  113. stream := cipher.NewCTR(dataKeyResult.Block, iv)
  114. // Create the SSE-KMS metadata using utility function
  115. sseKey := createSSEKMSKey(dataKeyResult, encryptionContext, bucketKeyEnabled, iv, 0)
  116. // The IV is stored in SSE key metadata, so the encrypted stream does not need to prepend the IV
  117. // This ensures correct Content-Length for clients
  118. encryptedReader := &cipher.StreamReader{S: stream, R: r}
  119. // Store the base IV in the SSE key for metadata storage
  120. sseKey.IV = iv
  121. return encryptedReader, sseKey, nil
  122. }
  123. // CreateSSEKMSEncryptedReaderWithBaseIVAndOffset creates an SSE-KMS encrypted reader using a provided base IV and offset
  124. // This is used for multipart uploads where all chunks need unique IVs to prevent IV reuse vulnerabilities
  125. func CreateSSEKMSEncryptedReaderWithBaseIVAndOffset(r io.Reader, keyID string, encryptionContext map[string]string, bucketKeyEnabled bool, baseIV []byte, offset int64) (io.Reader, *SSEKMSKey, error) {
  126. if err := ValidateIV(baseIV, "base IV"); err != nil {
  127. return nil, nil, err
  128. }
  129. // Generate data key using common utility
  130. dataKeyResult, err := generateKMSDataKey(keyID, encryptionContext)
  131. if err != nil {
  132. return nil, nil, err
  133. }
  134. // Ensure we clear the plaintext data key from memory when done
  135. defer clearKMSDataKey(dataKeyResult)
  136. // Calculate unique IV using base IV and offset to prevent IV reuse in multipart uploads
  137. iv := calculateIVWithOffset(baseIV, offset)
  138. // Create CTR mode cipher stream
  139. stream := cipher.NewCTR(dataKeyResult.Block, iv)
  140. // Create the SSE-KMS metadata using utility function
  141. sseKey := createSSEKMSKey(dataKeyResult, encryptionContext, bucketKeyEnabled, iv, offset)
  142. // The IV is stored in SSE key metadata, so the encrypted stream does not need to prepend the IV
  143. // This ensures correct Content-Length for clients
  144. encryptedReader := &cipher.StreamReader{S: stream, R: r}
  145. return encryptedReader, sseKey, nil
  146. }
  147. // hashEncryptionContext creates a deterministic hash of the encryption context
  148. func hashEncryptionContext(encryptionContext map[string]string) string {
  149. if len(encryptionContext) == 0 {
  150. return "empty"
  151. }
  152. // Create a deterministic representation of the context
  153. hash := sha256.New()
  154. // Sort keys to ensure deterministic hash
  155. keys := make([]string, 0, len(encryptionContext))
  156. for k := range encryptionContext {
  157. keys = append(keys, k)
  158. }
  159. sort.Strings(keys)
  160. // Hash the sorted key-value pairs
  161. for _, k := range keys {
  162. hash.Write([]byte(k))
  163. hash.Write([]byte("="))
  164. hash.Write([]byte(encryptionContext[k]))
  165. hash.Write([]byte(";"))
  166. }
  167. return hex.EncodeToString(hash.Sum(nil))[:16] // Use first 16 chars for brevity
  168. }
  169. // getBucketDataKey retrieves or creates a cached bucket-level data key for SSE-KMS
  170. // This is a simplified implementation that demonstrates the per-bucket caching concept
  171. // In a full implementation, this would integrate with the actual bucket configuration system
  172. func getBucketDataKey(bucketName, keyID string, encryptionContext map[string]string, bucketCache *BucketKMSCache) (*kms.GenerateDataKeyResponse, error) {
  173. // Create context hash for cache key
  174. contextHash := hashEncryptionContext(encryptionContext)
  175. cacheKey := fmt.Sprintf("%s:%s", keyID, contextHash)
  176. // Try to get from cache first if cache is available
  177. if bucketCache != nil {
  178. if cacheEntry, found := bucketCache.Get(cacheKey); found {
  179. if dataKey, ok := cacheEntry.DataKey.(*kms.GenerateDataKeyResponse); ok {
  180. glog.V(3).Infof("Using cached bucket key for bucket %s, keyID %s", bucketName, keyID)
  181. return dataKey, nil
  182. }
  183. }
  184. }
  185. // Cache miss - generate new data key
  186. kmsProvider := kms.GetGlobalKMS()
  187. if kmsProvider == nil {
  188. return nil, fmt.Errorf("KMS is not configured")
  189. }
  190. dataKeyReq := &kms.GenerateDataKeyRequest{
  191. KeyID: keyID,
  192. KeySpec: kms.KeySpecAES256,
  193. EncryptionContext: encryptionContext,
  194. }
  195. ctx := context.Background()
  196. dataKeyResp, err := kmsProvider.GenerateDataKey(ctx, dataKeyReq)
  197. if err != nil {
  198. return nil, fmt.Errorf("failed to generate bucket data key: %v", err)
  199. }
  200. // Cache the data key for future use if cache is available
  201. if bucketCache != nil {
  202. bucketCache.Set(cacheKey, keyID, dataKeyResp, BucketKeyCacheTTL)
  203. glog.V(2).Infof("Generated and cached new bucket key for bucket %s, keyID %s", bucketName, keyID)
  204. } else {
  205. glog.V(2).Infof("Generated new bucket key for bucket %s, keyID %s (caching disabled)", bucketName, keyID)
  206. }
  207. return dataKeyResp, nil
  208. }
  209. // CreateSSEKMSEncryptedReaderForBucket creates an encrypted reader with bucket-specific caching
  210. // This method is part of S3ApiServer to access bucket configuration and caching
  211. func (s3a *S3ApiServer) CreateSSEKMSEncryptedReaderForBucket(r io.Reader, bucketName, keyID string, encryptionContext map[string]string, bucketKeyEnabled bool) (io.Reader, *SSEKMSKey, error) {
  212. var dataKeyResp *kms.GenerateDataKeyResponse
  213. var err error
  214. if bucketKeyEnabled {
  215. // Use S3 Bucket Keys optimization with persistent per-bucket caching
  216. bucketCache, err := s3a.getBucketKMSCache(bucketName)
  217. if err != nil {
  218. glog.V(2).Infof("Failed to get bucket KMS cache for %s, falling back to per-object key: %v", bucketName, err)
  219. bucketKeyEnabled = false
  220. } else {
  221. dataKeyResp, err = getBucketDataKey(bucketName, keyID, encryptionContext, bucketCache)
  222. if err != nil {
  223. // Fall back to per-object key generation if bucket key fails
  224. glog.V(2).Infof("Bucket key generation failed for bucket %s, falling back to per-object key: %v", bucketName, err)
  225. bucketKeyEnabled = false
  226. }
  227. }
  228. }
  229. if !bucketKeyEnabled {
  230. // Generate a per-object data encryption key using KMS
  231. kmsProvider := kms.GetGlobalKMS()
  232. if kmsProvider == nil {
  233. return nil, nil, fmt.Errorf("KMS is not configured")
  234. }
  235. dataKeyReq := &kms.GenerateDataKeyRequest{
  236. KeyID: keyID,
  237. KeySpec: kms.KeySpecAES256,
  238. EncryptionContext: encryptionContext,
  239. }
  240. ctx := context.Background()
  241. dataKeyResp, err = kmsProvider.GenerateDataKey(ctx, dataKeyReq)
  242. if err != nil {
  243. return nil, nil, fmt.Errorf("failed to generate data key: %v", err)
  244. }
  245. }
  246. // Ensure we clear the plaintext data key from memory when done
  247. defer kms.ClearSensitiveData(dataKeyResp.Plaintext)
  248. // Create AES cipher with the data key
  249. block, err := aes.NewCipher(dataKeyResp.Plaintext)
  250. if err != nil {
  251. return nil, nil, fmt.Errorf("failed to create AES cipher: %v", err)
  252. }
  253. // Generate a random IV for CTR mode
  254. iv := make([]byte, 16) // AES block size
  255. if _, err := io.ReadFull(rand.Reader, iv); err != nil {
  256. return nil, nil, fmt.Errorf("failed to generate IV: %v", err)
  257. }
  258. // Create CTR mode cipher stream
  259. stream := cipher.NewCTR(block, iv)
  260. // Create the encrypting reader
  261. sseKey := &SSEKMSKey{
  262. KeyID: keyID,
  263. EncryptedDataKey: dataKeyResp.CiphertextBlob,
  264. EncryptionContext: encryptionContext,
  265. BucketKeyEnabled: bucketKeyEnabled,
  266. IV: iv,
  267. }
  268. return &cipher.StreamReader{S: stream, R: r}, sseKey, nil
  269. }
  270. // getBucketKMSCache gets or creates the persistent KMS cache for a bucket
  271. func (s3a *S3ApiServer) getBucketKMSCache(bucketName string) (*BucketKMSCache, error) {
  272. // Get bucket configuration
  273. bucketConfig, errCode := s3a.getBucketConfig(bucketName)
  274. if errCode != s3err.ErrNone {
  275. if errCode == s3err.ErrNoSuchBucket {
  276. return nil, fmt.Errorf("bucket %s does not exist", bucketName)
  277. }
  278. return nil, fmt.Errorf("failed to get bucket config: %v", errCode)
  279. }
  280. // Initialize KMS cache if it doesn't exist
  281. if bucketConfig.KMSKeyCache == nil {
  282. bucketConfig.KMSKeyCache = NewBucketKMSCache(bucketName, BucketKeyCacheTTL)
  283. glog.V(3).Infof("Initialized new KMS cache for bucket %s", bucketName)
  284. }
  285. return bucketConfig.KMSKeyCache, nil
  286. }
  287. // CleanupBucketKMSCache performs cleanup of expired KMS keys for a specific bucket
  288. func (s3a *S3ApiServer) CleanupBucketKMSCache(bucketName string) int {
  289. bucketCache, err := s3a.getBucketKMSCache(bucketName)
  290. if err != nil {
  291. glog.V(3).Infof("Could not get KMS cache for bucket %s: %v", bucketName, err)
  292. return 0
  293. }
  294. cleaned := bucketCache.CleanupExpired()
  295. if cleaned > 0 {
  296. glog.V(2).Infof("Cleaned up %d expired KMS keys for bucket %s", cleaned, bucketName)
  297. }
  298. return cleaned
  299. }
  300. // CleanupAllBucketKMSCaches performs cleanup of expired KMS keys for all buckets
  301. func (s3a *S3ApiServer) CleanupAllBucketKMSCaches() int {
  302. totalCleaned := 0
  303. // Access the bucket config cache safely
  304. if s3a.bucketConfigCache != nil {
  305. s3a.bucketConfigCache.mutex.RLock()
  306. bucketNames := make([]string, 0, len(s3a.bucketConfigCache.cache))
  307. for bucketName := range s3a.bucketConfigCache.cache {
  308. bucketNames = append(bucketNames, bucketName)
  309. }
  310. s3a.bucketConfigCache.mutex.RUnlock()
  311. // Clean up each bucket's KMS cache
  312. for _, bucketName := range bucketNames {
  313. cleaned := s3a.CleanupBucketKMSCache(bucketName)
  314. totalCleaned += cleaned
  315. }
  316. }
  317. if totalCleaned > 0 {
  318. glog.V(2).Infof("Cleaned up %d expired KMS keys across %d bucket caches", totalCleaned, len(s3a.bucketConfigCache.cache))
  319. }
  320. return totalCleaned
  321. }
  322. // CreateSSEKMSDecryptedReader creates a decrypted reader using KMS envelope encryption
  323. func CreateSSEKMSDecryptedReader(r io.Reader, sseKey *SSEKMSKey) (io.Reader, error) {
  324. kmsProvider := kms.GetGlobalKMS()
  325. if kmsProvider == nil {
  326. return nil, fmt.Errorf("KMS is not configured")
  327. }
  328. // Decrypt the data encryption key using KMS
  329. decryptReq := &kms.DecryptRequest{
  330. CiphertextBlob: sseKey.EncryptedDataKey,
  331. EncryptionContext: sseKey.EncryptionContext,
  332. }
  333. ctx := context.Background()
  334. decryptResp, err := kmsProvider.Decrypt(ctx, decryptReq)
  335. if err != nil {
  336. return nil, fmt.Errorf("failed to decrypt data key: %v", err)
  337. }
  338. // Ensure we clear the plaintext data key from memory when done
  339. defer kms.ClearSensitiveData(decryptResp.Plaintext)
  340. // Verify the key ID matches (security check)
  341. if decryptResp.KeyID != sseKey.KeyID {
  342. return nil, fmt.Errorf("KMS key ID mismatch: expected %s, got %s", sseKey.KeyID, decryptResp.KeyID)
  343. }
  344. // Use the IV from the SSE key metadata, calculating offset if this is a chunked part
  345. if err := ValidateIV(sseKey.IV, "SSE key IV"); err != nil {
  346. return nil, fmt.Errorf("invalid IV in SSE key: %w", err)
  347. }
  348. // Calculate the correct IV for this chunk's offset within the original part
  349. var iv []byte
  350. if sseKey.ChunkOffset > 0 {
  351. iv = calculateIVWithOffset(sseKey.IV, sseKey.ChunkOffset)
  352. glog.Infof("Using calculated IV with offset %d for chunk decryption", sseKey.ChunkOffset)
  353. } else {
  354. iv = sseKey.IV
  355. // glog.Infof("Using base IV for chunk decryption (offset=0)")
  356. }
  357. // Create AES cipher with the decrypted data key
  358. block, err := aes.NewCipher(decryptResp.Plaintext)
  359. if err != nil {
  360. return nil, fmt.Errorf("failed to create AES cipher: %v", err)
  361. }
  362. // Create CTR mode cipher stream for decryption
  363. // Note: AES-CTR is used for object data decryption to match the encryption mode
  364. stream := cipher.NewCTR(block, iv)
  365. // Return the decrypted reader
  366. return &cipher.StreamReader{S: stream, R: r}, nil
  367. }
  368. // ParseSSEKMSHeaders parses SSE-KMS headers from an HTTP request
  369. func ParseSSEKMSHeaders(r *http.Request) (*SSEKMSKey, error) {
  370. sseAlgorithm := r.Header.Get(s3_constants.AmzServerSideEncryption)
  371. // Check if SSE-KMS is requested
  372. if sseAlgorithm == "" {
  373. return nil, nil // No SSE headers present
  374. }
  375. if sseAlgorithm != s3_constants.SSEAlgorithmKMS {
  376. return nil, fmt.Errorf("invalid SSE algorithm: %s", sseAlgorithm)
  377. }
  378. keyID := r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
  379. encryptionContextHeader := r.Header.Get(s3_constants.AmzServerSideEncryptionContext)
  380. bucketKeyEnabledHeader := r.Header.Get(s3_constants.AmzServerSideEncryptionBucketKeyEnabled)
  381. // Parse encryption context if provided
  382. var encryptionContext map[string]string
  383. if encryptionContextHeader != "" {
  384. // Decode base64-encoded JSON encryption context
  385. contextBytes, err := base64.StdEncoding.DecodeString(encryptionContextHeader)
  386. if err != nil {
  387. return nil, fmt.Errorf("invalid encryption context format: %v", err)
  388. }
  389. if err := json.Unmarshal(contextBytes, &encryptionContext); err != nil {
  390. return nil, fmt.Errorf("invalid encryption context JSON: %v", err)
  391. }
  392. }
  393. // Parse bucket key enabled flag
  394. bucketKeyEnabled := strings.ToLower(bucketKeyEnabledHeader) == "true"
  395. sseKey := &SSEKMSKey{
  396. KeyID: keyID,
  397. EncryptionContext: encryptionContext,
  398. BucketKeyEnabled: bucketKeyEnabled,
  399. }
  400. // Validate the parsed key including key ID format
  401. if err := ValidateSSEKMSKeyInternal(sseKey); err != nil {
  402. return nil, err
  403. }
  404. return sseKey, nil
  405. }
  406. // ValidateSSEKMSKey validates an SSE-KMS key configuration
  407. func ValidateSSEKMSKeyInternal(sseKey *SSEKMSKey) error {
  408. if err := ValidateSSEKMSKey(sseKey); err != nil {
  409. return err
  410. }
  411. // An empty key ID is valid and means the default KMS key should be used.
  412. if sseKey.KeyID != "" && !isValidKMSKeyID(sseKey.KeyID) {
  413. return fmt.Errorf("invalid KMS key ID format: %s", sseKey.KeyID)
  414. }
  415. return nil
  416. }
  417. // BuildEncryptionContext creates the encryption context for S3 objects
  418. func BuildEncryptionContext(bucketName, objectKey string, useBucketKey bool) map[string]string {
  419. return kms.BuildS3EncryptionContext(bucketName, objectKey, useBucketKey)
  420. }
  421. // parseEncryptionContext parses the user-provided encryption context from base64 JSON
  422. func parseEncryptionContext(contextHeader string) (map[string]string, error) {
  423. if contextHeader == "" {
  424. return nil, nil
  425. }
  426. // Decode base64
  427. contextBytes, err := base64.StdEncoding.DecodeString(contextHeader)
  428. if err != nil {
  429. return nil, fmt.Errorf("invalid base64 encoding in encryption context: %w", err)
  430. }
  431. // Parse JSON
  432. var context map[string]string
  433. if err := json.Unmarshal(contextBytes, &context); err != nil {
  434. return nil, fmt.Errorf("invalid JSON in encryption context: %w", err)
  435. }
  436. // Validate context keys and values
  437. for k, v := range context {
  438. if k == "" || v == "" {
  439. return nil, fmt.Errorf("encryption context keys and values cannot be empty")
  440. }
  441. // AWS KMS has limits on context key/value length (256 chars each)
  442. if len(k) > 256 || len(v) > 256 {
  443. return nil, fmt.Errorf("encryption context key or value too long (max 256 characters)")
  444. }
  445. }
  446. return context, nil
  447. }
  448. // SerializeSSEKMSMetadata serializes SSE-KMS metadata for storage in object metadata
  449. func SerializeSSEKMSMetadata(sseKey *SSEKMSKey) ([]byte, error) {
  450. if err := ValidateSSEKMSKey(sseKey); err != nil {
  451. return nil, err
  452. }
  453. metadata := &SSEKMSMetadata{
  454. Algorithm: s3_constants.SSEAlgorithmKMS,
  455. KeyID: sseKey.KeyID,
  456. EncryptedDataKey: base64.StdEncoding.EncodeToString(sseKey.EncryptedDataKey),
  457. EncryptionContext: sseKey.EncryptionContext,
  458. BucketKeyEnabled: sseKey.BucketKeyEnabled,
  459. IV: base64.StdEncoding.EncodeToString(sseKey.IV), // Store IV for decryption
  460. PartOffset: sseKey.ChunkOffset, // Store within-part offset
  461. }
  462. data, err := json.Marshal(metadata)
  463. if err != nil {
  464. return nil, fmt.Errorf("failed to marshal SSE-KMS metadata: %w", err)
  465. }
  466. glog.V(4).Infof("Serialized SSE-KMS metadata: keyID=%s, bucketKey=%t", sseKey.KeyID, sseKey.BucketKeyEnabled)
  467. return data, nil
  468. }
  469. // DeserializeSSEKMSMetadata deserializes SSE-KMS metadata from storage and reconstructs the SSE-KMS key
  470. func DeserializeSSEKMSMetadata(data []byte) (*SSEKMSKey, error) {
  471. if len(data) == 0 {
  472. return nil, fmt.Errorf("empty SSE-KMS metadata")
  473. }
  474. var metadata SSEKMSMetadata
  475. if err := json.Unmarshal(data, &metadata); err != nil {
  476. return nil, fmt.Errorf("failed to unmarshal SSE-KMS metadata: %w", err)
  477. }
  478. // Validate algorithm - be lenient with missing/empty algorithm for backward compatibility
  479. if metadata.Algorithm != "" && metadata.Algorithm != s3_constants.SSEAlgorithmKMS {
  480. return nil, fmt.Errorf("invalid SSE-KMS algorithm: %s", metadata.Algorithm)
  481. }
  482. // Set default algorithm if empty
  483. if metadata.Algorithm == "" {
  484. metadata.Algorithm = s3_constants.SSEAlgorithmKMS
  485. }
  486. // Decode the encrypted data key
  487. encryptedDataKey, err := base64.StdEncoding.DecodeString(metadata.EncryptedDataKey)
  488. if err != nil {
  489. return nil, fmt.Errorf("failed to decode encrypted data key: %w", err)
  490. }
  491. // Decode the IV
  492. var iv []byte
  493. if metadata.IV != "" {
  494. iv, err = base64.StdEncoding.DecodeString(metadata.IV)
  495. if err != nil {
  496. return nil, fmt.Errorf("failed to decode IV: %w", err)
  497. }
  498. }
  499. sseKey := &SSEKMSKey{
  500. KeyID: metadata.KeyID,
  501. EncryptedDataKey: encryptedDataKey,
  502. EncryptionContext: metadata.EncryptionContext,
  503. BucketKeyEnabled: metadata.BucketKeyEnabled,
  504. IV: iv, // Restore IV for decryption
  505. ChunkOffset: metadata.PartOffset, // Use stored within-part offset
  506. }
  507. glog.V(4).Infof("Deserialized SSE-KMS metadata: keyID=%s, bucketKey=%t", sseKey.KeyID, sseKey.BucketKeyEnabled)
  508. return sseKey, nil
  509. }
  510. // SSECMetadata represents SSE-C metadata for per-chunk storage (unified with SSE-KMS approach)
  511. type SSECMetadata struct {
  512. Algorithm string `json:"algorithm"` // SSE-C algorithm (always "AES256")
  513. IV string `json:"iv"` // Base64-encoded initialization vector for this chunk
  514. KeyMD5 string `json:"keyMD5"` // MD5 of the customer-provided key
  515. PartOffset int64 `json:"partOffset"` // Offset within original multipart part (for IV calculation)
  516. }
  517. // SerializeSSECMetadata serializes SSE-C metadata for storage in chunk metadata
  518. func SerializeSSECMetadata(iv []byte, keyMD5 string, partOffset int64) ([]byte, error) {
  519. if err := ValidateIV(iv, "IV"); err != nil {
  520. return nil, err
  521. }
  522. metadata := &SSECMetadata{
  523. Algorithm: s3_constants.SSEAlgorithmAES256,
  524. IV: base64.StdEncoding.EncodeToString(iv),
  525. KeyMD5: keyMD5,
  526. PartOffset: partOffset,
  527. }
  528. data, err := json.Marshal(metadata)
  529. if err != nil {
  530. return nil, fmt.Errorf("failed to marshal SSE-C metadata: %w", err)
  531. }
  532. glog.V(4).Infof("Serialized SSE-C metadata: keyMD5=%s, partOffset=%d", keyMD5, partOffset)
  533. return data, nil
  534. }
  535. // DeserializeSSECMetadata deserializes SSE-C metadata from chunk storage
  536. func DeserializeSSECMetadata(data []byte) (*SSECMetadata, error) {
  537. if len(data) == 0 {
  538. return nil, fmt.Errorf("empty SSE-C metadata")
  539. }
  540. var metadata SSECMetadata
  541. if err := json.Unmarshal(data, &metadata); err != nil {
  542. return nil, fmt.Errorf("failed to unmarshal SSE-C metadata: %w", err)
  543. }
  544. // Validate algorithm
  545. if metadata.Algorithm != s3_constants.SSEAlgorithmAES256 {
  546. return nil, fmt.Errorf("invalid SSE-C algorithm: %s", metadata.Algorithm)
  547. }
  548. // Validate IV
  549. if metadata.IV == "" {
  550. return nil, fmt.Errorf("missing IV in SSE-C metadata")
  551. }
  552. if _, err := base64.StdEncoding.DecodeString(metadata.IV); err != nil {
  553. return nil, fmt.Errorf("invalid base64 IV in SSE-C metadata: %w", err)
  554. }
  555. glog.V(4).Infof("Deserialized SSE-C metadata: keyMD5=%s, partOffset=%d", metadata.KeyMD5, metadata.PartOffset)
  556. return &metadata, nil
  557. }
  558. // AddSSEKMSResponseHeaders adds SSE-KMS response headers to an HTTP response
  559. func AddSSEKMSResponseHeaders(w http.ResponseWriter, sseKey *SSEKMSKey) {
  560. w.Header().Set(s3_constants.AmzServerSideEncryption, s3_constants.SSEAlgorithmKMS)
  561. w.Header().Set(s3_constants.AmzServerSideEncryptionAwsKmsKeyId, sseKey.KeyID)
  562. if len(sseKey.EncryptionContext) > 0 {
  563. // Encode encryption context as base64 JSON
  564. contextBytes, err := json.Marshal(sseKey.EncryptionContext)
  565. if err == nil {
  566. contextB64 := base64.StdEncoding.EncodeToString(contextBytes)
  567. w.Header().Set(s3_constants.AmzServerSideEncryptionContext, contextB64)
  568. } else {
  569. glog.Errorf("Failed to encode encryption context: %v", err)
  570. }
  571. }
  572. if sseKey.BucketKeyEnabled {
  573. w.Header().Set(s3_constants.AmzServerSideEncryptionBucketKeyEnabled, "true")
  574. }
  575. }
  576. // IsSSEKMSRequest checks if the request contains SSE-KMS headers
  577. func IsSSEKMSRequest(r *http.Request) bool {
  578. // If SSE-C headers are present, this is not an SSE-KMS request (they are mutually exclusive)
  579. if r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm) != "" {
  580. return false
  581. }
  582. // According to AWS S3 specification, SSE-KMS is only valid when the encryption header
  583. // is explicitly set to "aws:kms". The KMS key ID header alone is not sufficient.
  584. sseAlgorithm := r.Header.Get(s3_constants.AmzServerSideEncryption)
  585. return sseAlgorithm == s3_constants.SSEAlgorithmKMS
  586. }
  587. // IsSSEKMSEncrypted checks if the metadata indicates SSE-KMS encryption
  588. func IsSSEKMSEncrypted(metadata map[string][]byte) bool {
  589. if metadata == nil {
  590. return false
  591. }
  592. // The canonical way to identify an SSE-KMS encrypted object is by this header.
  593. if sseAlgorithm, exists := metadata[s3_constants.AmzServerSideEncryption]; exists {
  594. return string(sseAlgorithm) == s3_constants.SSEAlgorithmKMS
  595. }
  596. return false
  597. }
  598. // IsAnySSEEncrypted checks if metadata indicates any type of SSE encryption
  599. func IsAnySSEEncrypted(metadata map[string][]byte) bool {
  600. if metadata == nil {
  601. return false
  602. }
  603. // Check for any SSE type
  604. if IsSSECEncrypted(metadata) {
  605. return true
  606. }
  607. if IsSSEKMSEncrypted(metadata) {
  608. return true
  609. }
  610. // Check for SSE-S3
  611. if sseAlgorithm, exists := metadata[s3_constants.AmzServerSideEncryption]; exists {
  612. return string(sseAlgorithm) == s3_constants.SSEAlgorithmAES256
  613. }
  614. return false
  615. }
  616. // MapKMSErrorToS3Error maps KMS errors to appropriate S3 error codes
  617. func MapKMSErrorToS3Error(err error) s3err.ErrorCode {
  618. if err == nil {
  619. return s3err.ErrNone
  620. }
  621. // Check if it's a KMS error
  622. kmsErr, ok := err.(*kms.KMSError)
  623. if !ok {
  624. return s3err.ErrInternalError
  625. }
  626. switch kmsErr.Code {
  627. case kms.ErrCodeNotFoundException:
  628. return s3err.ErrKMSKeyNotFound
  629. case kms.ErrCodeAccessDenied:
  630. return s3err.ErrKMSAccessDenied
  631. case kms.ErrCodeKeyUnavailable:
  632. return s3err.ErrKMSDisabled
  633. case kms.ErrCodeInvalidKeyUsage:
  634. return s3err.ErrKMSAccessDenied
  635. case kms.ErrCodeInvalidCiphertext:
  636. return s3err.ErrKMSInvalidCiphertext
  637. default:
  638. glog.Errorf("Unmapped KMS error: %s - %s", kmsErr.Code, kmsErr.Message)
  639. return s3err.ErrInternalError
  640. }
  641. }
  642. // SSEKMSCopyStrategy represents different strategies for copying SSE-KMS encrypted objects
  643. type SSEKMSCopyStrategy int
  644. const (
  645. // SSEKMSCopyStrategyDirect - Direct chunk copy (same key, no re-encryption needed)
  646. SSEKMSCopyStrategyDirect SSEKMSCopyStrategy = iota
  647. // SSEKMSCopyStrategyDecryptEncrypt - Decrypt source and re-encrypt for destination
  648. SSEKMSCopyStrategyDecryptEncrypt
  649. )
  650. // String returns string representation of the strategy
  651. func (s SSEKMSCopyStrategy) String() string {
  652. switch s {
  653. case SSEKMSCopyStrategyDirect:
  654. return "Direct"
  655. case SSEKMSCopyStrategyDecryptEncrypt:
  656. return "DecryptEncrypt"
  657. default:
  658. return "Unknown"
  659. }
  660. }
  661. // GetSourceSSEKMSInfo extracts SSE-KMS information from source object metadata
  662. func GetSourceSSEKMSInfo(metadata map[string][]byte) (keyID string, isEncrypted bool) {
  663. if sseAlgorithm, exists := metadata[s3_constants.AmzServerSideEncryption]; exists && string(sseAlgorithm) == s3_constants.SSEAlgorithmKMS {
  664. if kmsKeyID, exists := metadata[s3_constants.AmzServerSideEncryptionAwsKmsKeyId]; exists {
  665. return string(kmsKeyID), true
  666. }
  667. return "", true // SSE-KMS with default key
  668. }
  669. return "", false
  670. }
  671. // CanDirectCopySSEKMS determines if we can directly copy chunks without decrypt/re-encrypt
  672. func CanDirectCopySSEKMS(srcMetadata map[string][]byte, destKeyID string) bool {
  673. srcKeyID, srcEncrypted := GetSourceSSEKMSInfo(srcMetadata)
  674. // Case 1: Source unencrypted, destination unencrypted -> Direct copy
  675. if !srcEncrypted && destKeyID == "" {
  676. return true
  677. }
  678. // Case 2: Source encrypted with same KMS key as destination -> Direct copy
  679. if srcEncrypted && destKeyID != "" {
  680. // Same key if key IDs match (empty means default key)
  681. return srcKeyID == destKeyID
  682. }
  683. // All other cases require decrypt/re-encrypt
  684. return false
  685. }
  686. // DetermineSSEKMSCopyStrategy determines the optimal copy strategy for SSE-KMS
  687. func DetermineSSEKMSCopyStrategy(srcMetadata map[string][]byte, destKeyID string) (SSEKMSCopyStrategy, error) {
  688. if CanDirectCopySSEKMS(srcMetadata, destKeyID) {
  689. return SSEKMSCopyStrategyDirect, nil
  690. }
  691. return SSEKMSCopyStrategyDecryptEncrypt, nil
  692. }
  693. // ParseSSEKMSCopyHeaders parses SSE-KMS headers from copy request
  694. func ParseSSEKMSCopyHeaders(r *http.Request) (destKeyID string, encryptionContext map[string]string, bucketKeyEnabled bool, err error) {
  695. // Check if this is an SSE-KMS request
  696. if !IsSSEKMSRequest(r) {
  697. return "", nil, false, nil
  698. }
  699. // Get destination KMS key ID
  700. destKeyID = r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
  701. // Validate key ID if provided
  702. if destKeyID != "" && !isValidKMSKeyID(destKeyID) {
  703. return "", nil, false, fmt.Errorf("invalid KMS key ID: %s", destKeyID)
  704. }
  705. // Parse encryption context if provided
  706. if contextHeader := r.Header.Get(s3_constants.AmzServerSideEncryptionContext); contextHeader != "" {
  707. contextBytes, decodeErr := base64.StdEncoding.DecodeString(contextHeader)
  708. if decodeErr != nil {
  709. return "", nil, false, fmt.Errorf("invalid encryption context encoding: %v", decodeErr)
  710. }
  711. if unmarshalErr := json.Unmarshal(contextBytes, &encryptionContext); unmarshalErr != nil {
  712. return "", nil, false, fmt.Errorf("invalid encryption context JSON: %v", unmarshalErr)
  713. }
  714. }
  715. // Parse bucket key enabled flag
  716. if bucketKeyHeader := r.Header.Get(s3_constants.AmzServerSideEncryptionBucketKeyEnabled); bucketKeyHeader != "" {
  717. bucketKeyEnabled = strings.ToLower(bucketKeyHeader) == "true"
  718. }
  719. return destKeyID, encryptionContext, bucketKeyEnabled, nil
  720. }
  721. // UnifiedCopyStrategy represents all possible copy strategies across encryption types
  722. type UnifiedCopyStrategy int
  723. const (
  724. // CopyStrategyDirect - Direct chunk copy (no encryption changes)
  725. CopyStrategyDirect UnifiedCopyStrategy = iota
  726. // CopyStrategyEncrypt - Encrypt during copy (plain → encrypted)
  727. CopyStrategyEncrypt
  728. // CopyStrategyDecrypt - Decrypt during copy (encrypted → plain)
  729. CopyStrategyDecrypt
  730. // CopyStrategyReencrypt - Decrypt and re-encrypt (different keys/methods)
  731. CopyStrategyReencrypt
  732. // CopyStrategyKeyRotation - Same object, different key (metadata-only update)
  733. CopyStrategyKeyRotation
  734. )
  735. // String returns string representation of the unified strategy
  736. func (s UnifiedCopyStrategy) String() string {
  737. switch s {
  738. case CopyStrategyDirect:
  739. return "Direct"
  740. case CopyStrategyEncrypt:
  741. return "Encrypt"
  742. case CopyStrategyDecrypt:
  743. return "Decrypt"
  744. case CopyStrategyReencrypt:
  745. return "Reencrypt"
  746. case CopyStrategyKeyRotation:
  747. return "KeyRotation"
  748. default:
  749. return "Unknown"
  750. }
  751. }
  752. // EncryptionState represents the encryption state of source and destination
  753. type EncryptionState struct {
  754. SrcSSEC bool
  755. SrcSSEKMS bool
  756. SrcSSES3 bool
  757. DstSSEC bool
  758. DstSSEKMS bool
  759. DstSSES3 bool
  760. SameObject bool
  761. }
  762. // IsSourceEncrypted returns true if source has any encryption
  763. func (e *EncryptionState) IsSourceEncrypted() bool {
  764. return e.SrcSSEC || e.SrcSSEKMS || e.SrcSSES3
  765. }
  766. // IsTargetEncrypted returns true if target should be encrypted
  767. func (e *EncryptionState) IsTargetEncrypted() bool {
  768. return e.DstSSEC || e.DstSSEKMS || e.DstSSES3
  769. }
  770. // DetermineUnifiedCopyStrategy determines the optimal copy strategy for all encryption types
  771. func DetermineUnifiedCopyStrategy(state *EncryptionState, srcMetadata map[string][]byte, r *http.Request) (UnifiedCopyStrategy, error) {
  772. // Key rotation: same object with different encryption
  773. if state.SameObject && state.IsSourceEncrypted() && state.IsTargetEncrypted() {
  774. // Check if it's actually a key change
  775. if state.SrcSSEC && state.DstSSEC {
  776. // SSE-C key rotation - need to compare keys
  777. return CopyStrategyKeyRotation, nil
  778. }
  779. if state.SrcSSEKMS && state.DstSSEKMS {
  780. // SSE-KMS key rotation - need to compare key IDs
  781. srcKeyID, _ := GetSourceSSEKMSInfo(srcMetadata)
  782. dstKeyID := r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
  783. if srcKeyID != dstKeyID {
  784. return CopyStrategyKeyRotation, nil
  785. }
  786. }
  787. }
  788. // Direct copy: no encryption changes
  789. if !state.IsSourceEncrypted() && !state.IsTargetEncrypted() {
  790. return CopyStrategyDirect, nil
  791. }
  792. // Same encryption type and key
  793. if state.SrcSSEKMS && state.DstSSEKMS {
  794. srcKeyID, _ := GetSourceSSEKMSInfo(srcMetadata)
  795. dstKeyID := r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
  796. if srcKeyID == dstKeyID {
  797. return CopyStrategyDirect, nil
  798. }
  799. }
  800. if state.SrcSSEC && state.DstSSEC {
  801. // For SSE-C, we'd need to compare the actual keys, but we can't do that securely
  802. // So we assume different keys and use reencrypt strategy
  803. return CopyStrategyReencrypt, nil
  804. }
  805. // Encrypt: plain → encrypted
  806. if !state.IsSourceEncrypted() && state.IsTargetEncrypted() {
  807. return CopyStrategyEncrypt, nil
  808. }
  809. // Decrypt: encrypted → plain
  810. if state.IsSourceEncrypted() && !state.IsTargetEncrypted() {
  811. return CopyStrategyDecrypt, nil
  812. }
  813. // Reencrypt: different encryption types or keys
  814. if state.IsSourceEncrypted() && state.IsTargetEncrypted() {
  815. return CopyStrategyReencrypt, nil
  816. }
  817. return CopyStrategyDirect, nil
  818. }
  819. // DetectEncryptionState analyzes the source metadata and request headers to determine encryption state
  820. func DetectEncryptionState(srcMetadata map[string][]byte, r *http.Request, srcPath, dstPath string) *EncryptionState {
  821. state := &EncryptionState{
  822. SrcSSEC: IsSSECEncrypted(srcMetadata),
  823. SrcSSEKMS: IsSSEKMSEncrypted(srcMetadata),
  824. SrcSSES3: IsSSES3EncryptedInternal(srcMetadata),
  825. DstSSEC: IsSSECRequest(r),
  826. DstSSEKMS: IsSSEKMSRequest(r),
  827. DstSSES3: IsSSES3RequestInternal(r),
  828. SameObject: srcPath == dstPath,
  829. }
  830. return state
  831. }
  832. // DetectEncryptionStateWithEntry analyzes the source entry and request headers to determine encryption state
  833. // This version can detect multipart encrypted objects by examining chunks
  834. func DetectEncryptionStateWithEntry(entry *filer_pb.Entry, r *http.Request, srcPath, dstPath string) *EncryptionState {
  835. state := &EncryptionState{
  836. SrcSSEC: IsSSECEncryptedWithEntry(entry),
  837. SrcSSEKMS: IsSSEKMSEncryptedWithEntry(entry),
  838. SrcSSES3: IsSSES3EncryptedInternal(entry.Extended),
  839. DstSSEC: IsSSECRequest(r),
  840. DstSSEKMS: IsSSEKMSRequest(r),
  841. DstSSES3: IsSSES3RequestInternal(r),
  842. SameObject: srcPath == dstPath,
  843. }
  844. return state
  845. }
  846. // IsSSEKMSEncryptedWithEntry detects SSE-KMS encryption from entry (including multipart objects)
  847. func IsSSEKMSEncryptedWithEntry(entry *filer_pb.Entry) bool {
  848. if entry == nil {
  849. return false
  850. }
  851. // Check object-level metadata first
  852. if IsSSEKMSEncrypted(entry.Extended) {
  853. return true
  854. }
  855. // Check for multipart SSE-KMS by examining chunks
  856. if len(entry.GetChunks()) > 0 {
  857. for _, chunk := range entry.GetChunks() {
  858. if chunk.GetSseType() == filer_pb.SSEType_SSE_KMS {
  859. return true
  860. }
  861. }
  862. }
  863. return false
  864. }
  865. // IsSSECEncryptedWithEntry detects SSE-C encryption from entry (including multipart objects)
  866. func IsSSECEncryptedWithEntry(entry *filer_pb.Entry) bool {
  867. if entry == nil {
  868. return false
  869. }
  870. // Check object-level metadata first
  871. if IsSSECEncrypted(entry.Extended) {
  872. return true
  873. }
  874. // Check for multipart SSE-C by examining chunks
  875. if len(entry.GetChunks()) > 0 {
  876. for _, chunk := range entry.GetChunks() {
  877. if chunk.GetSseType() == filer_pb.SSEType_SSE_C {
  878. return true
  879. }
  880. }
  881. }
  882. return false
  883. }
  884. // Helper functions for SSE-C detection are in s3_sse_c.go