From 26f1fcab7d294eddfd3fb6b355c5c1e378abe0d5 Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Wed, 5 May 2021 20:24:14 +0200 Subject: [PATCH] add SSE-KMS support and use SSE-KMS for auto encryption (#11767) This commit adds basic SSE-KMS support. Now, a client can specify the SSE-KMS headers (algorithm, optional key-id, optional context) such that the object gets encrypted using the SSE-KMS method. Further, auto-encryption now defaults to SSE-KMS. This commit does not try to do any refactoring and instead tries to implement SSE-KMS as a minimal change to the code base. However, refactoring the entire crypto-related code is planned - but needs a separate effort. Signed-off-by: Andreas Auernhammer Co-authored-by: Klaus Post --- cmd/bucket-handlers.go | 20 +++- cmd/crypto/header_test.go | 11 ++- cmd/crypto/sse-kms.go | 13 ++- cmd/encryption-v1.go | 196 ++++++++++++++++++++++++++++---------- cmd/object-handlers.go | 65 +++++++------ cmd/tier.go | 6 +- cmd/web-handlers.go | 12 ++- 7 files changed, 229 insertions(+), 94 deletions(-) diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index d9d96f605..34097daf3 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -1011,16 +1011,28 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) return } - var reader io.Reader - var key []byte - if crypto.SSEC.IsRequested(formValues) { + var ( + reader io.Reader + keyID string + key []byte + kmsCtx crypto.Context + ) + kind, _ := crypto.IsRequested(formValues) + switch kind { + case crypto.SSEC: key, err = ParseSSECustomerHeader(formValues) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } + case crypto.S3KMS: + keyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(formValues) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return + } } - reader, objectEncryptionKey, err = newEncryptReader(hashReader, key, bucket, object, metadata, crypto.S3.IsRequested(formValues)) + reader, objectEncryptionKey, err = newEncryptReader(hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return diff --git a/cmd/crypto/header_test.go b/cmd/crypto/header_test.go index 876a316f0..bbd1646b2 100644 --- a/cmd/crypto/header_test.go +++ b/cmd/crypto/header_test.go @@ -18,6 +18,7 @@ package crypto import ( + "encoding/base64" "net/http" "sort" "testing" @@ -96,27 +97,27 @@ var kmsParseHTTPTests = []struct { {Header: http.Header{ "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, - "X-Amz-Server-Side-Encryption-Context": []string{"{}"}, + "X-Amz-Server-Side-Encryption-Context": []string{base64.StdEncoding.EncodeToString([]byte("{}"))}, }, ShouldFail: false}, // 3 {Header: http.Header{ "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, - "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"}, + "X-Amz-Server-Side-Encryption-Context": []string{base64.StdEncoding.EncodeToString([]byte(`{"bucket": "some-bucket"}`))}, }, ShouldFail: false}, // 4 {Header: http.Header{ "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, - "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"}, + "X-Amz-Server-Side-Encryption-Context": []string{base64.StdEncoding.EncodeToString([]byte(`{"bucket": "some-bucket"}`))}, }, ShouldFail: false}, // 5 {Header: http.Header{ "X-Amz-Server-Side-Encryption": []string{"AES256"}, "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, - "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"}, + "X-Amz-Server-Side-Encryption-Context": []string{base64.StdEncoding.EncodeToString([]byte(`{"bucket": "some-bucket"}`))}, }, ShouldFail: true}, // 6 {Header: http.Header{ "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, - "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\""}, // invalid JSON + "X-Amz-Server-Side-Encryption-Context": []string{base64.StdEncoding.EncodeToString([]byte(`{"bucket": "some-bucket"`))}, // invalid JSON }, ShouldFail: true}, // 7 } diff --git a/cmd/crypto/sse-kms.go b/cmd/crypto/sse-kms.go index da99c7618..ab6ab75df 100644 --- a/cmd/crypto/sse-kms.go +++ b/cmd/crypto/sse-kms.go @@ -69,8 +69,13 @@ func (ssekms) ParseHTTP(h http.Header) (string, Context, error) { var ctx Context if context, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok { + b, err := base64.StdEncoding.DecodeString(context[0]) + if err != nil { + return "", nil, err + } + var json = jsoniter.ConfigCompatibleWithStandardLibrary - if err := json.Unmarshal([]byte(context[0]), &ctx); err != nil { + if err := json.Unmarshal(b, &ctx); err != nil { return "", nil, err } } @@ -109,7 +114,7 @@ func (s3 ssekms) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, ob // the modified metadata. If the keyID and the kmsKey is not empty it encodes // both into the metadata as well. It allocates a new metadata map if metadata // is nil. -func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string { +func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey, ctx Context) map[string]string { if sealedKey.Algorithm != SealAlgorithm { logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) } @@ -132,6 +137,10 @@ func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey [] metadata[MetaAlgorithm] = sealedKey.Algorithm metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) + if len(ctx) > 0 { + b, _ := ctx.MarshalText() + metadata[MetaContext] = base64.StdEncoding.EncodeToString(b) + } if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key. metadata[MetaKeyID] = keyID metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey) diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 04c709d99..db8407bd3 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -26,6 +26,7 @@ import ( "encoding/binary" "encoding/hex" "errors" + "fmt" "io" "net/http" "path" @@ -36,6 +37,7 @@ import ( xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/fips" + "github.com/minio/minio/pkg/kms" "github.com/minio/sio" ) @@ -116,11 +118,71 @@ func ParseSSECustomerHeader(header http.Header) (key []byte, err error) { } // This function rotates old to new key. -func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map[string]string) error { - switch { - default: - return errObjectTampered - case crypto.SSEC.IsEncrypted(metadata): +func rotateKey(oldKey []byte, newKeyID string, newKey []byte, bucket, object string, metadata map[string]string, ctx crypto.Context) error { + kind, _ := crypto.IsEncrypted(metadata) + switch kind { + case crypto.S3: + if GlobalKMS == nil { + return errKMSNotConfigured + } + keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata) + if err != nil { + return err + } + oldKey, err := GlobalKMS.DecryptKey(keyID, kmsKey, kms.Context{bucket: path.Join(bucket, object)}) + if err != nil { + return err + } + var objectKey crypto.ObjectKey + if err = objectKey.Unseal(oldKey, sealedKey, crypto.S3.String(), bucket, object); err != nil { + return err + } + + newKey, err := GlobalKMS.GenerateKey("", kms.Context{bucket: path.Join(bucket, object)}) + if err != nil { + return err + } + sealedKey = objectKey.Seal(newKey.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object) + crypto.S3.CreateMetadata(metadata, newKey.KeyID, newKey.Ciphertext, sealedKey) + return nil + case crypto.S3KMS: + if GlobalKMS == nil { + return errKMSNotConfigured + } + objectKey, err := crypto.S3KMS.UnsealObjectKey(GlobalKMS, metadata, bucket, object) + if err != nil { + return err + } + + if len(ctx) == 0 { + _, _, _, ctx, err = crypto.S3KMS.ParseMetadata(metadata) + if err != nil { + return err + } + } + + // If the context does not contain the bucket key + // we must add it for key generation. However, + // the context must be stored exactly like the + // client provided it. Therefore, we delete the + // bucket key, if added by us, after generating + // the key. + _, ctxContainsBucket := ctx[bucket] + if !ctxContainsBucket { + ctx[bucket] = path.Join(bucket, object) + } + newKey, err := GlobalKMS.GenerateKey(newKeyID, ctx) + if err != nil { + return err + } + if !ctxContainsBucket { + delete(ctx, bucket) + } + + sealedKey := objectKey.Seal(newKey.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3KMS.String(), bucket, object) + crypto.S3KMS.CreateMetadata(metadata, newKey.KeyID, newKey.Ciphertext, sealedKey, ctx) + return nil + case crypto.SSEC: sealedKey, err := crypto.SSEC.ParseMetadata(metadata) if err != nil { return err @@ -140,40 +202,19 @@ func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map sealedKey = objectKey.Seal(newKey, sealedKey.IV, crypto.SSEC.String(), bucket, object) crypto.SSEC.CreateMetadata(metadata, sealedKey) return nil - case crypto.S3.IsEncrypted(metadata): - if GlobalKMS == nil { - return errKMSNotConfigured - } - keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata) - if err != nil { - return err - } - oldKey, err := GlobalKMS.DecryptKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)}) - if err != nil { - return err - } - var objectKey crypto.ObjectKey - if err = objectKey.Unseal(oldKey, sealedKey, crypto.S3.String(), bucket, object); err != nil { - return err - } - - newKey, err := GlobalKMS.GenerateKey("", crypto.Context{bucket: path.Join(bucket, object)}) - if err != nil { - return err - } - sealedKey = objectKey.Seal(newKey.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object) - crypto.S3.CreateMetadata(metadata, newKey.KeyID, newKey.Ciphertext, sealedKey) - return nil + default: + return errObjectTampered } } -func newEncryptMetadata(key []byte, bucket, object string, metadata map[string]string, sseS3 bool) (crypto.ObjectKey, error) { +func newEncryptMetadata(kind crypto.Type, keyID string, key []byte, bucket, object string, metadata map[string]string, ctx kms.Context) (crypto.ObjectKey, error) { var sealedKey crypto.SealedKey - if sseS3 { + switch kind { + case crypto.S3: if GlobalKMS == nil { return crypto.ObjectKey{}, errKMSNotConfigured } - key, err := GlobalKMS.GenerateKey("", crypto.Context{bucket: path.Join(bucket, object)}) + key, err := GlobalKMS.GenerateKey("", kms.Context{bucket: path.Join(bucket, object)}) if err != nil { return crypto.ObjectKey{}, err } @@ -182,15 +223,45 @@ func newEncryptMetadata(key []byte, bucket, object string, metadata map[string]s sealedKey = objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object) crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey) return objectKey, nil + case crypto.S3KMS: + if GlobalKMS == nil { + return crypto.ObjectKey{}, errKMSNotConfigured + } + + // If the context does not contain the bucket key + // we must add it for key generation. However, + // the context must be stored exactly like the + // client provided it. Therefore, we delete the + // bucket key, if added by us, after generating + // the key. + _, ctxContainsBucket := ctx[bucket] + if !ctxContainsBucket { + ctx[bucket] = path.Join(bucket, object) + } + key, err := GlobalKMS.GenerateKey(keyID, ctx) + if err != nil { + return crypto.ObjectKey{}, err + } + if !ctxContainsBucket { + delete(ctx, bucket) + } + + objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader) + sealedKey = objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3KMS.String(), bucket, object) + crypto.S3KMS.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey, ctx) + return objectKey, nil + case crypto.SSEC: + objectKey := crypto.GenerateKey(key, rand.Reader) + sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.SSEC.String(), bucket, object) + crypto.SSEC.CreateMetadata(metadata, sealedKey) + return objectKey, nil + default: + return crypto.ObjectKey{}, fmt.Errorf("encryption type '%v' not supported", kind) } - objectKey := crypto.GenerateKey(key, rand.Reader) - sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.SSEC.String(), bucket, object) - crypto.SSEC.CreateMetadata(metadata, sealedKey) - return objectKey, nil } -func newEncryptReader(content io.Reader, key []byte, bucket, object string, metadata map[string]string, sseS3 bool) (io.Reader, crypto.ObjectKey, error) { - objectEncryptionKey, err := newEncryptMetadata(key, bucket, object, metadata, sseS3) +func newEncryptReader(content io.Reader, kind crypto.Type, keyID string, key []byte, bucket, object string, metadata map[string]string, ctx crypto.Context) (io.Reader, crypto.ObjectKey, error) { + objectEncryptionKey, err := newEncryptMetadata(kind, keyID, key, bucket, object, metadata, ctx) if err != nil { return nil, crypto.ObjectKey{}, err } @@ -207,15 +278,24 @@ func newEncryptReader(content io.Reader, key []byte, bucket, object string, meta // SSE-S3 func setEncryptionMetadata(r *http.Request, bucket, object string, metadata map[string]string) (err error) { var ( - key []byte + key []byte + keyID string + ctx crypto.Context ) - if crypto.SSEC.IsRequested(r.Header) { + kind, _ := crypto.IsRequested(r.Header) + switch kind { + case crypto.SSEC: key, err = ParseSSECustomerRequest(r) if err != nil { - return + return err + } + case crypto.S3KMS: + keyID, ctx, err = crypto.S3KMS.ParseHTTP(r.Header) + if err != nil { + return err } } - _, err = newEncryptMetadata(key, bucket, object, metadata, crypto.S3.IsRequested(r.Header)) + _, err = newEncryptMetadata(kind, keyID, key, bucket, object, metadata, ctx) return } @@ -223,24 +303,32 @@ func setEncryptionMetadata(r *http.Request, bucket, object string, metadata map[ // with the client provided key. It also marks the object as client-side-encrypted // and sets the correct headers. func EncryptRequest(content io.Reader, r *http.Request, bucket, object string, metadata map[string]string) (io.Reader, crypto.ObjectKey, error) { - if crypto.S3.IsRequested(r.Header) && crypto.SSEC.IsRequested(r.Header) { - return nil, crypto.ObjectKey{}, crypto.ErrIncompatibleEncryptionMethod - } if r.ContentLength > encryptBufferThreshold { // The encryption reads in blocks of 64KB. // We add a buffer on bigger files to reduce the number of syscalls upstream. content = bufio.NewReaderSize(content, encryptBufferSize) } - var key []byte - if crypto.SSEC.IsRequested(r.Header) { - var err error + var ( + key []byte + keyID string + ctx crypto.Context + err error + ) + kind, _ := crypto.IsRequested(r.Header) + if kind == crypto.SSEC { key, err = ParseSSECustomerRequest(r) if err != nil { return nil, crypto.ObjectKey{}, err } } - return newEncryptReader(content, key, bucket, object, metadata, crypto.S3.IsRequested(r.Header)) + if kind == crypto.S3KMS { + keyID, ctx, err = crypto.S3KMS.ParseHTTP(r.Header) + if err != nil { + return nil, crypto.ObjectKey{}, err + } + } + return newEncryptReader(content, kind, keyID, key, bucket, object, metadata, ctx) } func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) { @@ -590,7 +678,7 @@ func getDecryptedETag(headers http.Header, objInfo ObjectInfo, copySource bool) // Since server side copy with same source and dest just replaces the ETag, we save // encrypted content MD5Sum as ETag for both SSE-C and SSE-S3, we standardize the ETag // encryption across SSE-C and SSE-S3, and only return last 32 bytes for SSE-C - if crypto.SSEC.IsEncrypted(objInfo.UserDefined) && !copySource { + if (crypto.SSEC.IsEncrypted(objInfo.UserDefined) || crypto.S3KMS.IsEncrypted(objInfo.UserDefined)) && !copySource { return objInfo.ETag[len(objInfo.ETag)-32:] } @@ -774,7 +862,7 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e // disallow X-Amz-Server-Side-Encryption header on HEAD and GET switch r.Method { case http.MethodGet, http.MethodHead: - if crypto.S3.IsRequested(headers) { + if crypto.S3.IsRequested(headers) || crypto.S3KMS.IsRequested(headers) { return false, errInvalidEncryptionParameters } } @@ -797,6 +885,12 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e } } + if crypto.S3KMS.IsEncrypted(info.UserDefined) && r.Header.Get(xhttp.AmzCopySource) == "" { + if crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers) { + return encrypted, errEncryptedObject + } + } + if _, err = info.DecryptedSize(); err != nil { return encrypted, err } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 7269e303f..517eb7042 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -481,6 +481,11 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { case crypto.S3: w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.S3KMS: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) + if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok { + w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx) + } case crypto.SSEC: w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) @@ -705,6 +710,11 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { case crypto.S3: w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.S3KMS: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) + if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok { + w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx) + } 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 { @@ -869,11 +879,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re return } - if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) - return - } - if _, ok := crypto.IsRequested(r.Header); ok { if globalIsGateway { if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { @@ -957,7 +962,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re _, err = globalBucketSSEConfigSys.Get(dstBucket) // This request header needs to be set prior to setting ObjectOptions if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { - r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) } var srcOpts, dstOpts ObjectOptions @@ -1114,14 +1119,18 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re } var oldKey, newKey []byte + var newKeyID string + var kmsCtx crypto.Context var objEncKey crypto.ObjectKey + sseCopyKMS := crypto.S3KMS.IsEncrypted(srcInfo.UserDefined) sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined) sseCopyC := crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header) sseC := crypto.SSEC.IsRequested(r.Header) sseS3 := crypto.S3.IsRequested(r.Header) + sseKMS := crypto.S3KMS.IsRequested(r.Header) - isSourceEncrypted := sseCopyC || sseCopyS3 - isTargetEncrypted := sseC || sseS3 + isSourceEncrypted := sseCopyC || sseCopyS3 || sseCopyKMS + isTargetEncrypted := sseC || sseS3 || sseKMS if sseC { newKey, err = ParseSSECustomerRequest(r) @@ -1130,6 +1139,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re return } } + if crypto.S3KMS.IsRequested(r.Header) { + newKeyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(r.Header) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return + } + } // If src == dst and either // - the object is encrypted using SSE-C and two different SSE-C keys are present @@ -1149,8 +1165,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re } } - // In case of SSE-S3 oldKey and newKey aren't used - the KMS manages the keys. - if err = rotateKey(oldKey, newKey, srcBucket, srcObject, encMetadata); err != nil { + if err = rotateKey(oldKey, newKeyID, newKey, srcBucket, srcObject, encMetadata, kmsCtx); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } @@ -1187,7 +1202,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re if isTargetEncrypted { var encReader io.Reader - encReader, objEncKey, err = newEncryptReader(srcInfo.Reader, newKey, dstBucket, dstObject, encMetadata, sseS3) + kind, _ := crypto.IsRequested(r.Header) + encReader, objEncKey, err = newEncryptReader(srcInfo.Reader, kind, newKeyID, newKey, dstBucket, dstObject, encMetadata, kmsCtx) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return @@ -1415,11 +1431,6 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req return } - if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) - return - } - if _, ok := crypto.IsRequested(r.Header); ok { if globalIsGateway { if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { @@ -1557,7 +1568,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req _, err = globalBucketSSEConfigSys.Get(bucket) // This request header needs to be set prior to setting ObjectOptions if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { - r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) } actualSize := size @@ -1688,6 +1699,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req case crypto.S3: w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) objInfo.ETag, _ = DecryptETag(objectEncryptionKey, ObjectInfo{ETag: objInfo.ETag}) + case crypto.S3KMS: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) + if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok { + w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx) + } + if len(objInfo.ETag) >= 32 && strings.Count(objInfo.ETag, "-") != 1 { + objInfo.ETag = objInfo.ETag[len(objInfo.ETag)-32:] + } case crypto.SSEC: w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) @@ -2027,11 +2046,6 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r return } - if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) - return - } - if _, ok := crypto.IsRequested(r.Header); ok { if globalIsGateway { if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { @@ -2063,7 +2077,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r _, err = globalBucketSSEConfigSys.Get(bucket) // This request header needs to be set prior to setting ObjectOptions if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { - r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) } // Validate storage class metadata if present @@ -2487,11 +2501,6 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http return } - if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) - return - } - if _, ok := crypto.IsRequested(r.Header); ok { if globalIsGateway { if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { diff --git a/cmd/tier.go b/cmd/tier.go index b98251c31..33781a5b2 100644 --- a/cmd/tier.go +++ b/cmd/tier.go @@ -28,7 +28,9 @@ import ( "strings" "sync" + "github.com/minio/minio/cmd/crypto" "github.com/minio/minio/pkg/hash" + "github.com/minio/minio/pkg/kms" "github.com/minio/minio/pkg/madmin" ) @@ -236,9 +238,7 @@ func (config *TierConfigMgr) configReader() (*PutObjReader, *ObjectOptions, erro // Encrypt json encoded tier configurations metadata := make(map[string]string) - sseS3 := true - var extKey [32]byte - encBr, oek, err := newEncryptReader(hr, extKey[:], minioMetaBucket, tierConfigPath, metadata, sseS3) + encBr, oek, err := newEncryptReader(hr, crypto.S3KMS, "", nil, minioMetaBucket, tierConfigPath, metadata, kms.Context{}) if err != nil { return nil, nil, err } diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 0ddde9487..db042d6bb 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -1203,7 +1203,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { // Check if bucket encryption is enabled _, err = globalBucketSSEConfigSys.Get(bucket) if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { - r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) } // Require Content-Length to be set in the request @@ -1333,6 +1333,11 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { case crypto.S3: w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.S3KMS: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) + if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok { + w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx) + } case crypto.SSEC: w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) @@ -1494,6 +1499,11 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { case crypto.S3: w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) + case crypto.S3KMS: + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS) + if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok { + w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx) + } case crypto.SSEC: w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))