From 871b450dbdd74eb19d8024e2bfc0ba42889dc18a Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Thu, 4 Feb 2021 00:19:08 +0100 Subject: [PATCH] crypto: add support for decrypting SSE-KMS metadata (#11415) This commit refactors the SSE implementation and add S3-compatible SSE-KMS context handling. SSE-KMS differs from SSE-S3 in two main aspects: 1. The client can request a particular key and specify a KMS context as part of the request. 2. The ETag of an SSE-KMS encrypted object is not the MD5 sum of the object content. This commit only focuses on the 1st aspect. A client can send an optional SSE context when using SSE-KMS. This context is remembered by the S3 server such that the client does not have to specify the context again (during multipart PUT / GET / HEAD ...). The crypto. context also includes the bucket/object name to prevent renaming objects at the backend. Now, AWS S3 behaves as following: - If the user does not provide a SSE-KMS context it does not store one - resp. does not include the SSE-KMS context header in the response (e.g. HEAD). - If the user specifies a SSE-KMS context without the bucket/object name then AWS stores the exact context the client provided but adds the bucket/object name internally. The response contains the KMS context without the bucket/object name. - If the user specifies a SSE-KMS context with the bucket/object name then AWS again stores the exact context provided by the client. The response contains the KMS context with the bucket/object name. This commit implements this behavior w.r.t. SSE-KMS. However, as of now, no such object can be created since the server rejects SSE-KMS encryption requests. This commit is one stepping stone for SSE-KMS support. Co-authored-by: Harshavardhana --- cmd/crypto/metadata.go | 44 ++++++++++++++------ cmd/crypto/metadata_test.go | 6 +-- cmd/crypto/sse-kms.go | 46 ++++++++++++--------- cmd/crypto/sse-s3.go | 6 --- cmd/disk-cache-utils.go | 6 ++- cmd/encryption-v1.go | 55 ++++++++++--------------- cmd/encryption-v1_test.go | 2 +- cmd/object-api-utils.go | 6 +-- cmd/object-handlers.go | 80 +++++++++++++++++-------------------- cmd/web-handlers.go | 28 ++++++------- 10 files changed, 141 insertions(+), 138 deletions(-) diff --git a/cmd/crypto/metadata.go b/cmd/crypto/metadata.go index da72c0c4a..9ef8fba53 100644 --- a/cmd/crypto/metadata.go +++ b/cmd/crypto/metadata.go @@ -44,6 +44,15 @@ const ( // MetaDataEncryptionKey is the sealed data encryption key (DEK) received from // the KMS. MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key" + + // MetaContext is the KMS context provided by a client when encrypting an + // object with SSE-KMS. A client may not send a context in which case the + // MetaContext will not be present. + // MetaContext only contains the bucket/object name if the client explicitly + // added it. However, when decrypting an object the bucket/object name must + // be part of the object. Therefore, the bucket/object name must be added + // to the context, if not present, whenever a decryption is performed. + MetaContext = "X-Minio-Internal-Server-Side-Encryption-Context" ) // IsMultiPart returns true if the object metadata indicates @@ -109,26 +118,35 @@ func IsSourceEncrypted(metadata map[string]string) bool { // // IsEncrypted only checks whether the metadata contains at least // one entry indicating SSE-C or SSE-S3. -func IsEncrypted(metadata map[string]string) bool { - if _, ok := metadata[MetaIV]; ok { - return true +func IsEncrypted(metadata map[string]string) (Type, bool) { + if S3KMS.IsEncrypted(metadata) { + return S3KMS, true } - if _, ok := metadata[MetaAlgorithm]; ok { - return true + if S3.IsEncrypted(metadata) { + return S3, true + } + if SSEC.IsEncrypted(metadata) { + return SSEC, true } if IsMultiPart(metadata) { - return true + return nil, true } - if S3.IsEncrypted(metadata) { - return true + if _, ok := metadata[MetaIV]; ok { + return nil, true } - if SSEC.IsEncrypted(metadata) { - return true + if _, ok := metadata[MetaAlgorithm]; ok { + return nil, true } - if S3KMS.IsEncrypted(metadata) { - return true + if _, ok := metadata[MetaKeyID]; ok { + return nil, true } - return false + if _, ok := metadata[MetaDataEncryptionKey]; ok { + return nil, true + } + if _, ok := metadata[MetaContext]; ok { + return nil, true + } + return nil, false } // CreateMultipartMetadata adds the multipart flag entry to metadata diff --git a/cmd/crypto/metadata_test.go b/cmd/crypto/metadata_test.go index 437c1eecf..d3dcb729d 100644 --- a/cmd/crypto/metadata_test.go +++ b/cmd/crypto/metadata_test.go @@ -59,7 +59,7 @@ var isEncryptedTests = []struct { func TestIsEncrypted(t *testing.T) { for i, test := range isEncryptedTests { - if isEncrypted := IsEncrypted(test.Metadata); isEncrypted != test.Encrypted { + if _, isEncrypted := IsEncrypted(test.Metadata); isEncrypted != test.Encrypted { t.Errorf("Test %d: got '%v' - want '%v'", i, isEncrypted, test.Encrypted) } } @@ -74,8 +74,8 @@ var s3IsEncryptedTests = []struct { {Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2 {Encrypted: false, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3 {Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4 - {Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5 - {Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6 + {Encrypted: false, Metadata: map[string]string{MetaKeyID: ""}}, // 5 + {Encrypted: false, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 } diff --git a/cmd/crypto/sse-kms.go b/cmd/crypto/sse-kms.go index f8a06a39b..272651dfb 100644 --- a/cmd/crypto/sse-kms.go +++ b/cmd/crypto/sse-kms.go @@ -82,12 +82,6 @@ func (ssekms) IsEncrypted(metadata map[string]string) bool { if _, ok := metadata[MetaSealedKeyKMS]; ok { return true } - if _, ok := metadata[MetaKeyID]; ok { - return true - } - if _, ok := metadata[MetaDataEncryptionKey]; ok { - return true - } return false } @@ -95,11 +89,14 @@ func (ssekms) IsEncrypted(metadata map[string]string) bool { // from the metadata using KMS and returns the decrypted object // key. func (s3 ssekms) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { - keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata) + keyID, kmsKey, sealedKey, ctx, err := s3.ParseMetadata(metadata) if err != nil { return key, err } - unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)}) + if _, ok := ctx[bucket]; !ok { + ctx[bucket] = path.Join(bucket, object) + } + unsealKey, err := kms.UnsealKey(keyID, kmsKey, ctx) if err != nil { return key, err } @@ -147,19 +144,19 @@ func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey [] // KMS data key it returns both. If the metadata does not contain neither a // KMS master key ID nor a sealed KMS data key it returns an empty keyID and // KMS data key. Otherwise, it returns an error. -func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) { +func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, ctx Context, err error) { // Extract all required values from object metadata b64IV, ok := metadata[MetaIV] if !ok { - return keyID, kmsKey, sealedKey, errMissingInternalIV + return keyID, kmsKey, sealedKey, ctx, errMissingInternalIV } algorithm, ok := metadata[MetaAlgorithm] if !ok { - return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm + return keyID, kmsKey, sealedKey, ctx, errMissingInternalSealAlgorithm } b64SealedKey, ok := metadata[MetaSealedKeyKMS] if !ok { - return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3") + return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal sealed key for SSE-S3") } // There are two possibilites: @@ -169,33 +166,44 @@ func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey [] keyID, idPresent := metadata[MetaKeyID] b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey] if !idPresent && kmsKeyPresent { - return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3") + return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3") } if idPresent && !kmsKeyPresent { - return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3") + return keyID, kmsKey, sealedKey, ctx, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3") } // Check whether all extracted values are well-formed iv, err := base64.StdEncoding.DecodeString(b64IV) if err != nil || len(iv) != 32 { - return keyID, kmsKey, sealedKey, errInvalidInternalIV + return keyID, kmsKey, sealedKey, ctx, errInvalidInternalIV } if algorithm != SealAlgorithm { - return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm + return keyID, kmsKey, sealedKey, ctx, errInvalidInternalSealAlgorithm } encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) if err != nil || len(encryptedKey) != 64 { - return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid") + return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed key for SSE-KMS is invalid") } if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key. kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) if err != nil { - return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid") + return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed KMS data key for SSE-KMS is invalid") + } + } + b64Ctx, ok := metadata[MetaContext] + if ok { + b, err := base64.StdEncoding.DecodeString(b64Ctx) + if err != nil { + return keyID, kmsKey, sealedKey, ctx, Errorf("The internal KMS context is not base64-encoded") + } + var json = jsoniter.ConfigCompatibleWithStandardLibrary + if err = json.Unmarshal(b, ctx); err != nil { + return keyID, kmsKey, sealedKey, ctx, Errorf("The internal sealed KMS context is invalid") } } sealedKey.Algorithm = algorithm copy(sealedKey.IV[:], iv) copy(sealedKey.Key[:], encryptedKey) - return keyID, kmsKey, sealedKey, nil + return keyID, kmsKey, sealedKey, ctx, nil } diff --git a/cmd/crypto/sse-s3.go b/cmd/crypto/sse-s3.go index 9471744f3..223694c0d 100644 --- a/cmd/crypto/sse-s3.go +++ b/cmd/crypto/sse-s3.go @@ -62,12 +62,6 @@ func (sses3) IsEncrypted(metadata map[string]string) bool { if _, ok := metadata[MetaSealedKeyS3]; ok { return true } - if _, ok := metadata[MetaKeyID]; ok { - return true - } - if _, ok := metadata[MetaDataEncryptionKey]; ok { - return true - } return false } diff --git a/cmd/disk-cache-utils.go b/cmd/disk-cache-utils.go index 9d0b186e2..01a67edda 100644 --- a/cmd/disk-cache-utils.go +++ b/cmd/disk-cache-utils.go @@ -173,7 +173,11 @@ func backendDownError(err error) bool { // IsCacheable returns if the object should be saved in the cache. func (o ObjectInfo) IsCacheable() bool { - return !crypto.IsEncrypted(o.UserDefined) || globalCacheKMS != nil + if globalCacheKMS != nil { + return true + } + _, ok := crypto.IsEncrypted(o.UserDefined) + return !ok } // reads file cached on disk from offset upto length diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 7cf19bc51..6de14b42e 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -247,56 +247,45 @@ func EncryptRequest(content io.Reader, r *http.Request, bucket, object string, m } func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) { - switch { - default: - return nil, errObjectTampered - case crypto.S3.IsEncrypted(metadata) && isCacheEncrypted(metadata): - if globalCacheKMS == nil { - return nil, errKMSNotConfigured + switch kind, _ := crypto.IsEncrypted(metadata); kind { + case crypto.S3: + var KMS crypto.KMS = GlobalKMS + if isCacheEncrypted(metadata) { + KMS = globalCacheKMS } - keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata) - if err != nil { - return nil, err + if KMS == nil { + return nil, errKMSNotConfigured } - extKey, err := globalCacheKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)}) + objectKey, err := crypto.S3.UnsealObjectKey(KMS, metadata, bucket, object) if err != nil { return nil, err } - var objectKey crypto.ObjectKey - if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil { - return nil, err - } return objectKey[:], nil - case crypto.S3.IsEncrypted(metadata): + case crypto.S3KMS: if GlobalKMS == nil { return nil, errKMSNotConfigured } - keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata) - + objectKey, err := crypto.S3KMS.UnsealObjectKey(GlobalKMS, metadata, bucket, object) if err != nil { return nil, err } - extKey, err := GlobalKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)}) - if err != nil { - return nil, err - } - var objectKey crypto.ObjectKey - if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil { - return nil, err - } return objectKey[:], nil - case crypto.SSEC.IsEncrypted(metadata): - var extKey [32]byte - copy(extKey[:], key) + case crypto.SSEC: sealedKey, err := crypto.SSEC.ParseMetadata(metadata) if err != nil { return nil, err } - var objectKey crypto.ObjectKey + var ( + objectKey crypto.ObjectKey + extKey [32]byte + ) + copy(extKey[:], key) if err = objectKey.Unseal(extKey, sealedKey, crypto.SSEC.String(), bucket, object); err != nil { return nil, err } return objectKey[:], nil + default: + return nil, errObjectTampered } } @@ -516,7 +505,7 @@ func (d *DecryptBlocksReader) Read(p []byte) (int, error) { // It returns an error if the object is not encrypted or marked as encrypted // but has an invalid size. func (o *ObjectInfo) DecryptedSize() (int64, error) { - if !crypto.IsEncrypted(o.UserDefined) { + if _, ok := crypto.IsEncrypted(o.UserDefined); !ok { return 0, errors.New("Cannot compute decrypted size of an unencrypted object") } if !isEncryptedMultipart(*o) { @@ -649,7 +638,7 @@ func tryDecryptETag(key []byte, encryptedETag string, ssec bool) string { // requested range starts, along with the DARE sequence number within // that part. For single part objects, the partStart will be 0. func (o *ObjectInfo) GetDecryptedRange(rs *HTTPRangeSpec) (encOff, encLength, skipLen int64, seqNumber uint32, partStart int, err error) { - if !crypto.IsEncrypted(o.UserDefined) { + if _, ok := crypto.IsEncrypted(o.UserDefined); !ok { err = errors.New("Object is not encrypted") return } @@ -796,7 +785,7 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e } } - encrypted = crypto.IsEncrypted(info.UserDefined) + _, encrypted = crypto.IsEncrypted(info.UserDefined) if !encrypted && crypto.SSEC.IsRequested(headers) && r.Header.Get(xhttp.AmzCopySource) == "" { return false, errInvalidEncryptionParameters } @@ -818,7 +807,7 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e return encrypted, err } - if crypto.IsEncrypted(info.UserDefined) && !crypto.IsMultiPart(info.UserDefined) { + if _, ok := crypto.IsEncrypted(info.UserDefined); ok && !crypto.IsMultiPart(info.UserDefined) { info.ETag = getDecryptedETag(headers, *info, false) } } diff --git a/cmd/encryption-v1_test.go b/cmd/encryption-v1_test.go index 7dc7e4227..91a6a8cfb 100644 --- a/cmd/encryption-v1_test.go +++ b/cmd/encryption-v1_test.go @@ -129,7 +129,7 @@ func TestDecryptObjectInfo(t *testing.T) { for i, test := range decryptObjectInfoTests { if encrypted, err := DecryptObjectInfo(&test.info, test.request); err != test.expErr { t.Errorf("Test %d: Decryption returned wrong error code: got %d , want %d", i, err, test.expErr) - } else if enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted { + } else if _, enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted { t.Errorf("Test %d: Decryption thinks object is encrypted but it is not", i) } else if !encrypted && enc != encrypted { t.Errorf("Test %d: Decryption thinks object is not encrypted but it is", i) diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go index 61c4c8b1e..0a0c10a8d 100644 --- a/cmd/object-api-utils.go +++ b/cmd/object-api-utils.go @@ -404,7 +404,7 @@ func (o ObjectInfo) IsCompressedOK() (bool, error) { // GetActualETag - returns the actual etag of the stored object // decrypts SSE objects. func (o ObjectInfo) GetActualETag(h http.Header) string { - if !crypto.IsEncrypted(o.UserDefined) { + if _, ok := crypto.IsEncrypted(o.UserDefined); !ok { return o.ETag } return getDecryptedETag(h, o, false) @@ -423,7 +423,7 @@ func (o ObjectInfo) GetActualSize() (int64, error) { } return size, nil } - if crypto.IsEncrypted(o.UserDefined) { + if _, ok := crypto.IsEncrypted(o.UserDefined); ok { return o.DecryptedSize() } @@ -600,7 +600,7 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, opts ObjectOptions, cl } }() - isEncrypted := crypto.IsEncrypted(oi.UserDefined) + _, isEncrypted := crypto.IsEncrypted(oi.UserDefined) isCompressed, err := oi.IsCompressedOK() if err != nil { return nil, 0, 0, err diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index e46af885c..fc8a8a4a2 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -265,19 +265,17 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r // Set encryption response headers if objectAPI.IsEncryptionSupported() { - if crypto.IsEncrypted(objInfo.UserDefined) { - switch { - case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) - case crypto.SSEC.IsEncrypted(objInfo.UserDefined): - // Validate the SSE-C Key set in the header. - if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) + switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { + case crypto.S3: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.SSEC: + // Validate the SSE-C Key set in the header. + if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return } + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } @@ -450,14 +448,12 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req // Set encryption response headers if objectAPI.IsEncryptionSupported() { - if crypto.IsEncrypted(objInfo.UserDefined) { - switch { - case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) - case crypto.SSEC.IsEncrypted(objInfo.UserDefined): - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) - } + switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { + case crypto.S3: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.SSEC: + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } @@ -654,19 +650,17 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re // Set encryption response headers if objectAPI.IsEncryptionSupported() { - if crypto.IsEncrypted(objInfo.UserDefined) { - switch { - case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) - case crypto.SSEC.IsEncrypted(objInfo.UserDefined): - // Validate the SSE-C Key set in the header. - if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { - writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) - return - } - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) + switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { + case crypto.S3: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.SSEC: + // Validate the SSE-C Key set in the header. + if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { + writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) + return } + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } @@ -1047,7 +1041,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re var encMetadata = make(map[string]string) if objectAPI.IsEncryptionSupported() { // Encryption parameters not applicable for this object. - if !crypto.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header) { + if _, ok := crypto.IsEncrypted(srcInfo.UserDefined); ok && crypto.SSECopy.IsRequested(r.Header) { writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) return } @@ -1584,13 +1578,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req return } - switch { - case crypto.IsEncrypted(objInfo.UserDefined): - switch { - case crypto.S3.IsEncrypted(objInfo.UserDefined): + switch kind, encrypted := crypto.IsEncrypted(objInfo.UserDefined); { + case encrypted: + switch kind { + case crypto.S3: w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) objInfo.ETag, _ = DecryptETag(objectEncryptionKey, ObjectInfo{ETag: objInfo.ETag}) - case crypto.SSEC.IsEncrypted(objInfo.UserDefined): + case crypto.SSEC: w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) @@ -1912,7 +1906,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt srcInfo := gr.ObjInfo actualPartSize := srcInfo.Size - if crypto.IsEncrypted(srcInfo.UserDefined) { + if _, ok := crypto.IsEncrypted(srcInfo.UserDefined); ok { actualPartSize, err = srcInfo.GetActualSize() if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) @@ -2010,7 +2004,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt rawReader := srcInfo.Reader pReader := NewPutObjReader(rawReader, nil, nil) - isEncrypted := crypto.IsEncrypted(mi.UserDefined) + _, isEncrypted := crypto.IsEncrypted(mi.UserDefined) var objectEncryptionKey crypto.ObjectKey if objectAPI.IsEncryptionSupported() && isEncrypted { if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) { @@ -2250,7 +2244,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http rawReader := hashReader pReader := NewPutObjReader(rawReader, nil, nil) - isEncrypted := crypto.IsEncrypted(mi.UserDefined) + _, isEncrypted := crypto.IsEncrypted(mi.UserDefined) var objectEncryptionKey crypto.ObjectKey if objectAPI.IsEncryptionSupported() && isEncrypted { if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) { @@ -2416,7 +2410,7 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht } var ssec bool - if objectAPI.IsEncryptionSupported() && crypto.IsEncrypted(listPartsInfo.UserDefined) { + if _, ok := crypto.IsEncrypted(listPartsInfo.UserDefined); ok && objectAPI.IsEncryptionSupported() { var key []byte if crypto.SSEC.IsEncrypted(listPartsInfo.UserDefined) { ssec = true @@ -2580,7 +2574,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } - if crypto.IsEncrypted(mi.UserDefined) { + if _, ok := crypto.IsEncrypted(mi.UserDefined); ok { var key []byte isEncrypted = true ssec = crypto.SSEC.IsEncrypted(mi.UserDefined) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index ab408075c..adee040ca 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -1289,14 +1289,12 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { return } if objectAPI.IsEncryptionSupported() { - if crypto.IsEncrypted(objInfo.UserDefined) { - switch { - case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) - case crypto.SSEC.IsRequested(r.Header): - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) - } + switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { + case crypto.S3: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.SSEC: + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } if mustReplicate { @@ -1448,14 +1446,12 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { // Set encryption response headers if objectAPI.IsEncryptionSupported() { - if crypto.IsEncrypted(objInfo.UserDefined) { - switch { - case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) - case crypto.SSEC.IsEncrypted(objInfo.UserDefined): - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) - w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) - } + switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { + case crypto.S3: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.SSEC: + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } }