s3_sse_c.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. package s3api
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "crypto/md5"
  6. "crypto/rand"
  7. "encoding/base64"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "github.com/seaweedfs/seaweedfs/weed/glog"
  13. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  14. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  15. )
  16. // SSECCopyStrategy represents different strategies for copying SSE-C objects
  17. type SSECCopyStrategy int
  18. const (
  19. // SSECCopyStrategyDirect indicates the object can be copied directly without decryption
  20. SSECCopyStrategyDirect SSECCopyStrategy = iota
  21. // SSECCopyStrategyDecryptEncrypt indicates the object must be decrypted then re-encrypted
  22. SSECCopyStrategyDecryptEncrypt
  23. )
  24. const (
  25. // SSE-C constants
  26. SSECustomerAlgorithmAES256 = s3_constants.SSEAlgorithmAES256
  27. SSECustomerKeySize = 32 // 256 bits
  28. )
  29. // SSE-C related errors
  30. var (
  31. ErrInvalidRequest = errors.New("invalid request")
  32. ErrInvalidEncryptionAlgorithm = errors.New("invalid encryption algorithm")
  33. ErrInvalidEncryptionKey = errors.New("invalid encryption key")
  34. ErrSSECustomerKeyMD5Mismatch = errors.New("customer key MD5 mismatch")
  35. ErrSSECustomerKeyMissing = errors.New("customer key missing")
  36. ErrSSECustomerKeyNotNeeded = errors.New("customer key not needed")
  37. )
  38. // SSECustomerKey represents a customer-provided encryption key for SSE-C
  39. type SSECustomerKey struct {
  40. Algorithm string
  41. Key []byte
  42. KeyMD5 string
  43. }
  44. // IsSSECRequest checks if the request contains SSE-C headers
  45. func IsSSECRequest(r *http.Request) bool {
  46. // If SSE-KMS headers are present, this is not an SSE-C request (they are mutually exclusive)
  47. sseAlgorithm := r.Header.Get(s3_constants.AmzServerSideEncryption)
  48. if sseAlgorithm == "aws:kms" || r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId) != "" {
  49. return false
  50. }
  51. return r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm) != ""
  52. }
  53. // IsSSECEncrypted checks if the metadata indicates SSE-C encryption
  54. func IsSSECEncrypted(metadata map[string][]byte) bool {
  55. if metadata == nil {
  56. return false
  57. }
  58. // Check for SSE-C specific metadata keys
  59. if _, exists := metadata[s3_constants.AmzServerSideEncryptionCustomerAlgorithm]; exists {
  60. return true
  61. }
  62. if _, exists := metadata[s3_constants.AmzServerSideEncryptionCustomerKeyMD5]; exists {
  63. return true
  64. }
  65. return false
  66. }
  67. // validateAndParseSSECHeaders does the core validation and parsing logic
  68. func validateAndParseSSECHeaders(algorithm, key, keyMD5 string) (*SSECustomerKey, error) {
  69. if algorithm == "" && key == "" && keyMD5 == "" {
  70. return nil, nil // No SSE-C headers
  71. }
  72. if algorithm == "" || key == "" || keyMD5 == "" {
  73. return nil, ErrInvalidRequest
  74. }
  75. if algorithm != SSECustomerAlgorithmAES256 {
  76. return nil, ErrInvalidEncryptionAlgorithm
  77. }
  78. // Decode and validate key
  79. keyBytes, err := base64.StdEncoding.DecodeString(key)
  80. if err != nil {
  81. return nil, ErrInvalidEncryptionKey
  82. }
  83. if len(keyBytes) != SSECustomerKeySize {
  84. return nil, ErrInvalidEncryptionKey
  85. }
  86. // Validate key MD5 (base64-encoded MD5 of the raw key bytes; case-sensitive)
  87. sum := md5.Sum(keyBytes)
  88. expectedMD5 := base64.StdEncoding.EncodeToString(sum[:])
  89. // Debug logging for MD5 validation
  90. glog.V(4).Infof("SSE-C MD5 validation: provided='%s', expected='%s', keyBytes=%x", keyMD5, expectedMD5, keyBytes)
  91. if keyMD5 != expectedMD5 {
  92. glog.Errorf("SSE-C MD5 mismatch: provided='%s', expected='%s'", keyMD5, expectedMD5)
  93. return nil, ErrSSECustomerKeyMD5Mismatch
  94. }
  95. return &SSECustomerKey{
  96. Algorithm: algorithm,
  97. Key: keyBytes,
  98. KeyMD5: keyMD5,
  99. }, nil
  100. }
  101. // ValidateSSECHeaders validates SSE-C headers in the request
  102. func ValidateSSECHeaders(r *http.Request) error {
  103. algorithm := r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm)
  104. key := r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerKey)
  105. keyMD5 := r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerKeyMD5)
  106. _, err := validateAndParseSSECHeaders(algorithm, key, keyMD5)
  107. return err
  108. }
  109. // ParseSSECHeaders parses and validates SSE-C headers from the request
  110. func ParseSSECHeaders(r *http.Request) (*SSECustomerKey, error) {
  111. algorithm := r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm)
  112. key := r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerKey)
  113. keyMD5 := r.Header.Get(s3_constants.AmzServerSideEncryptionCustomerKeyMD5)
  114. return validateAndParseSSECHeaders(algorithm, key, keyMD5)
  115. }
  116. // ParseSSECCopySourceHeaders parses and validates SSE-C copy source headers from the request
  117. func ParseSSECCopySourceHeaders(r *http.Request) (*SSECustomerKey, error) {
  118. algorithm := r.Header.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerAlgorithm)
  119. key := r.Header.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKey)
  120. keyMD5 := r.Header.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKeyMD5)
  121. return validateAndParseSSECHeaders(algorithm, key, keyMD5)
  122. }
  123. // CreateSSECEncryptedReader creates a new encrypted reader for SSE-C
  124. // Returns the encrypted reader and the IV for metadata storage
  125. func CreateSSECEncryptedReader(r io.Reader, customerKey *SSECustomerKey) (io.Reader, []byte, error) {
  126. if customerKey == nil {
  127. return r, nil, nil
  128. }
  129. // Create AES cipher
  130. block, err := aes.NewCipher(customerKey.Key)
  131. if err != nil {
  132. return nil, nil, fmt.Errorf("failed to create AES cipher: %v", err)
  133. }
  134. // Generate random IV
  135. iv := make([]byte, s3_constants.AESBlockSize)
  136. if _, err := io.ReadFull(rand.Reader, iv); err != nil {
  137. return nil, nil, fmt.Errorf("failed to generate IV: %v", err)
  138. }
  139. // Create CTR mode cipher
  140. stream := cipher.NewCTR(block, iv)
  141. // The IV is stored in metadata, so the encrypted stream does not need to prepend the IV
  142. // This ensures correct Content-Length for clients
  143. encryptedReader := &cipher.StreamReader{S: stream, R: r}
  144. return encryptedReader, iv, nil
  145. }
  146. // CreateSSECDecryptedReader creates a new decrypted reader for SSE-C
  147. // The IV comes from metadata, not from the encrypted data stream
  148. func CreateSSECDecryptedReader(r io.Reader, customerKey *SSECustomerKey, iv []byte) (io.Reader, error) {
  149. if customerKey == nil {
  150. return r, nil
  151. }
  152. // IV must be provided from metadata
  153. if err := ValidateIV(iv, "IV"); err != nil {
  154. return nil, fmt.Errorf("invalid IV from metadata: %w", err)
  155. }
  156. // Create AES cipher
  157. block, err := aes.NewCipher(customerKey.Key)
  158. if err != nil {
  159. return nil, fmt.Errorf("failed to create AES cipher: %v", err)
  160. }
  161. // Create CTR mode cipher using the IV from metadata
  162. stream := cipher.NewCTR(block, iv)
  163. return &cipher.StreamReader{S: stream, R: r}, nil
  164. }
  165. // CreateSSECEncryptedReaderWithOffset creates an encrypted reader with a specific counter offset
  166. // This is used for chunk-level encryption where each chunk needs a different counter position
  167. func CreateSSECEncryptedReaderWithOffset(r io.Reader, customerKey *SSECustomerKey, iv []byte, counterOffset uint64) (io.Reader, error) {
  168. if customerKey == nil {
  169. return r, nil
  170. }
  171. // Create AES cipher
  172. block, err := aes.NewCipher(customerKey.Key)
  173. if err != nil {
  174. return nil, fmt.Errorf("failed to create AES cipher: %v", err)
  175. }
  176. // Create CTR mode cipher with offset
  177. stream := createCTRStreamWithOffset(block, iv, counterOffset)
  178. return &cipher.StreamReader{S: stream, R: r}, nil
  179. }
  180. // CreateSSECDecryptedReaderWithOffset creates a decrypted reader with a specific counter offset
  181. func CreateSSECDecryptedReaderWithOffset(r io.Reader, customerKey *SSECustomerKey, iv []byte, counterOffset uint64) (io.Reader, error) {
  182. if customerKey == nil {
  183. return r, nil
  184. }
  185. // Create AES cipher
  186. block, err := aes.NewCipher(customerKey.Key)
  187. if err != nil {
  188. return nil, fmt.Errorf("failed to create AES cipher: %v", err)
  189. }
  190. // Create CTR mode cipher with offset
  191. stream := createCTRStreamWithOffset(block, iv, counterOffset)
  192. return &cipher.StreamReader{S: stream, R: r}, nil
  193. }
  194. // createCTRStreamWithOffset creates a CTR stream positioned at a specific counter offset
  195. func createCTRStreamWithOffset(block cipher.Block, iv []byte, counterOffset uint64) cipher.Stream {
  196. // Create a copy of the IV to avoid modifying the original
  197. offsetIV := make([]byte, len(iv))
  198. copy(offsetIV, iv)
  199. // Calculate the counter offset in blocks (AES block size is 16 bytes)
  200. blockOffset := counterOffset / 16
  201. // Add the block offset to the counter portion of the IV
  202. // In AES-CTR, the last 8 bytes of the IV are typically used as the counter
  203. addCounterToIV(offsetIV, blockOffset)
  204. return cipher.NewCTR(block, offsetIV)
  205. }
  206. // addCounterToIV adds a counter value to the IV (treating last 8 bytes as big-endian counter)
  207. func addCounterToIV(iv []byte, counter uint64) {
  208. // Use the last 8 bytes as a big-endian counter
  209. for i := 7; i >= 0; i-- {
  210. carry := counter & 0xff
  211. iv[len(iv)-8+i] += byte(carry)
  212. if iv[len(iv)-8+i] >= byte(carry) {
  213. break // No overflow
  214. }
  215. counter >>= 8
  216. }
  217. }
  218. // GetSourceSSECInfo extracts SSE-C information from source object metadata
  219. func GetSourceSSECInfo(metadata map[string][]byte) (algorithm string, keyMD5 string, isEncrypted bool) {
  220. if alg, exists := metadata[s3_constants.AmzServerSideEncryptionCustomerAlgorithm]; exists {
  221. algorithm = string(alg)
  222. }
  223. if md5, exists := metadata[s3_constants.AmzServerSideEncryptionCustomerKeyMD5]; exists {
  224. keyMD5 = string(md5)
  225. }
  226. isEncrypted = algorithm != "" && keyMD5 != ""
  227. return
  228. }
  229. // CanDirectCopySSEC determines if we can directly copy chunks without decrypt/re-encrypt
  230. func CanDirectCopySSEC(srcMetadata map[string][]byte, copySourceKey *SSECustomerKey, destKey *SSECustomerKey) bool {
  231. _, srcKeyMD5, srcEncrypted := GetSourceSSECInfo(srcMetadata)
  232. // Case 1: Source unencrypted, destination unencrypted -> Direct copy
  233. if !srcEncrypted && destKey == nil {
  234. return true
  235. }
  236. // Case 2: Source encrypted, same key for decryption and destination -> Direct copy
  237. if srcEncrypted && copySourceKey != nil && destKey != nil {
  238. // Same key if MD5 matches exactly (base64 encoding is case-sensitive)
  239. return copySourceKey.KeyMD5 == srcKeyMD5 &&
  240. destKey.KeyMD5 == srcKeyMD5
  241. }
  242. // All other cases require decrypt/re-encrypt
  243. return false
  244. }
  245. // Note: SSECCopyStrategy is defined above
  246. // DetermineSSECCopyStrategy determines the optimal copy strategy
  247. func DetermineSSECCopyStrategy(srcMetadata map[string][]byte, copySourceKey *SSECustomerKey, destKey *SSECustomerKey) (SSECCopyStrategy, error) {
  248. _, srcKeyMD5, srcEncrypted := GetSourceSSECInfo(srcMetadata)
  249. // Validate source key if source is encrypted
  250. if srcEncrypted {
  251. if copySourceKey == nil {
  252. return SSECCopyStrategyDecryptEncrypt, ErrSSECustomerKeyMissing
  253. }
  254. if copySourceKey.KeyMD5 != srcKeyMD5 {
  255. return SSECCopyStrategyDecryptEncrypt, ErrSSECustomerKeyMD5Mismatch
  256. }
  257. } else if copySourceKey != nil {
  258. // Source not encrypted but copy source key provided
  259. return SSECCopyStrategyDecryptEncrypt, ErrSSECustomerKeyNotNeeded
  260. }
  261. if CanDirectCopySSEC(srcMetadata, copySourceKey, destKey) {
  262. return SSECCopyStrategyDirect, nil
  263. }
  264. return SSECCopyStrategyDecryptEncrypt, nil
  265. }
  266. // MapSSECErrorToS3Error maps SSE-C custom errors to S3 API error codes
  267. func MapSSECErrorToS3Error(err error) s3err.ErrorCode {
  268. switch err {
  269. case ErrInvalidEncryptionAlgorithm:
  270. return s3err.ErrInvalidEncryptionAlgorithm
  271. case ErrInvalidEncryptionKey:
  272. return s3err.ErrInvalidEncryptionKey
  273. case ErrSSECustomerKeyMD5Mismatch:
  274. return s3err.ErrSSECustomerKeyMD5Mismatch
  275. case ErrSSECustomerKeyMissing:
  276. return s3err.ErrSSECustomerKeyMissing
  277. case ErrSSECustomerKeyNotNeeded:
  278. return s3err.ErrSSECustomerKeyNotNeeded
  279. default:
  280. return s3err.ErrInvalidRequest
  281. }
  282. }