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 <aead@mail.de>
Co-authored-by: Klaus Post <klauspost@gmail.com>
This commit is contained in:
Andreas Auernhammer 2021-05-05 20:24:14 +02:00 committed by GitHub
parent 3a0e7347ca
commit 26f1fcab7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 229 additions and 94 deletions

View File

@ -1011,16 +1011,28 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
return return
} }
var reader io.Reader var (
var key []byte reader io.Reader
if crypto.SSEC.IsRequested(formValues) { keyID string
key []byte
kmsCtx crypto.Context
)
kind, _ := crypto.IsRequested(formValues)
switch kind {
case crypto.SSEC:
key, err = ParseSSECustomerHeader(formValues) key, err = ParseSSECustomerHeader(formValues)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return 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 { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return

View File

@ -18,6 +18,7 @@
package crypto package crypto
import ( import (
"encoding/base64"
"net/http" "net/http"
"sort" "sort"
"testing" "testing"
@ -96,27 +97,27 @@ var kmsParseHTTPTests = []struct {
{Header: http.Header{ {Header: http.Header{
"X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "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-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 }, ShouldFail: false}, // 3
{Header: http.Header{ {Header: http.Header{
"X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "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-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 }, ShouldFail: false}, // 4
{Header: http.Header{ {Header: http.Header{
"X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "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-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 }, ShouldFail: false}, // 5
{Header: http.Header{ {Header: http.Header{
"X-Amz-Server-Side-Encryption": []string{"AES256"}, "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-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 }, ShouldFail: true}, // 6
{Header: http.Header{ {Header: http.Header{
"X-Amz-Server-Side-Encryption": []string{"aws:kms"}, "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-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 }, ShouldFail: true}, // 7
} }

View File

@ -69,8 +69,13 @@ func (ssekms) ParseHTTP(h http.Header) (string, Context, error) {
var ctx Context var ctx Context
if context, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok { if context, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok {
b, err := base64.StdEncoding.DecodeString(context[0])
if err != nil {
return "", nil, err
}
var json = jsoniter.ConfigCompatibleWithStandardLibrary 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 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 // 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 // both into the metadata as well. It allocates a new metadata map if metadata
// is nil. // 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 { if sealedKey.Algorithm != SealAlgorithm {
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) 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[MetaAlgorithm] = sealedKey.Algorithm
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) 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. if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
metadata[MetaKeyID] = keyID metadata[MetaKeyID] = keyID
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey) metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)

View File

@ -26,6 +26,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"path" "path"
@ -36,6 +37,7 @@ import (
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/fips" "github.com/minio/minio/pkg/fips"
"github.com/minio/minio/pkg/kms"
"github.com/minio/sio" "github.com/minio/sio"
) )
@ -116,11 +118,71 @@ func ParseSSECustomerHeader(header http.Header) (key []byte, err error) {
} }
// This function rotates old to new key. // This function rotates old to new key.
func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map[string]string) error { func rotateKey(oldKey []byte, newKeyID string, newKey []byte, bucket, object string, metadata map[string]string, ctx crypto.Context) error {
switch { kind, _ := crypto.IsEncrypted(metadata)
default: switch kind {
return errObjectTampered case crypto.S3:
case crypto.SSEC.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, 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) sealedKey, err := crypto.SSEC.ParseMetadata(metadata)
if err != nil { if err != nil {
return err 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) sealedKey = objectKey.Seal(newKey, sealedKey.IV, crypto.SSEC.String(), bucket, object)
crypto.SSEC.CreateMetadata(metadata, sealedKey) crypto.SSEC.CreateMetadata(metadata, sealedKey)
return nil return nil
case crypto.S3.IsEncrypted(metadata): default:
if GlobalKMS == nil { return errObjectTampered
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
} }
} }
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 var sealedKey crypto.SealedKey
if sseS3 { switch kind {
case crypto.S3:
if GlobalKMS == nil { if GlobalKMS == nil {
return crypto.ObjectKey{}, errKMSNotConfigured 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 { if err != nil {
return crypto.ObjectKey{}, err 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) sealedKey = objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey) crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey)
return objectKey, nil 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) objectKey := crypto.GenerateKey(key, rand.Reader)
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.SSEC.String(), bucket, object) sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.SSEC.String(), bucket, object)
crypto.SSEC.CreateMetadata(metadata, sealedKey) crypto.SSEC.CreateMetadata(metadata, sealedKey)
return objectKey, nil return objectKey, nil
default:
return crypto.ObjectKey{}, fmt.Errorf("encryption type '%v' not supported", kind)
}
} }
func newEncryptReader(content io.Reader, key []byte, bucket, object string, metadata map[string]string, sseS3 bool) (io.Reader, crypto.ObjectKey, error) { 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(key, bucket, object, metadata, sseS3) objectEncryptionKey, err := newEncryptMetadata(kind, keyID, key, bucket, object, metadata, ctx)
if err != nil { if err != nil {
return nil, crypto.ObjectKey{}, err return nil, crypto.ObjectKey{}, err
} }
@ -208,14 +279,23 @@ func newEncryptReader(content io.Reader, key []byte, bucket, object string, meta
func setEncryptionMetadata(r *http.Request, bucket, object string, metadata map[string]string) (err error) { func setEncryptionMetadata(r *http.Request, bucket, object string, metadata map[string]string) (err error) {
var ( 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) key, err = ParseSSECustomerRequest(r)
if err != nil { 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 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 // with the client provided key. It also marks the object as client-side-encrypted
// and sets the correct headers. // 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) { 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 { if r.ContentLength > encryptBufferThreshold {
// The encryption reads in blocks of 64KB. // The encryption reads in blocks of 64KB.
// We add a buffer on bigger files to reduce the number of syscalls upstream. // We add a buffer on bigger files to reduce the number of syscalls upstream.
content = bufio.NewReaderSize(content, encryptBufferSize) content = bufio.NewReaderSize(content, encryptBufferSize)
} }
var key []byte var (
if crypto.SSEC.IsRequested(r.Header) { key []byte
var err error keyID string
ctx crypto.Context
err error
)
kind, _ := crypto.IsRequested(r.Header)
if kind == crypto.SSEC {
key, err = ParseSSECustomerRequest(r) key, err = ParseSSECustomerRequest(r)
if err != nil { if err != nil {
return nil, crypto.ObjectKey{}, err 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) { 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 // 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 // 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 // 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:] 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 // disallow X-Amz-Server-Side-Encryption header on HEAD and GET
switch r.Method { switch r.Method {
case http.MethodGet, http.MethodHead: case http.MethodGet, http.MethodHead:
if crypto.S3.IsRequested(headers) { if crypto.S3.IsRequested(headers) || crypto.S3KMS.IsRequested(headers) {
return false, errInvalidEncryptionParameters 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 { if _, err = info.DecryptedSize(); err != nil {
return encrypted, err return encrypted, err
} }

View File

@ -481,6 +481,11 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
case crypto.S3: case crypto.S3:
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 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: case crypto.SSEC:
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) 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 { switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
case crypto.S3: case crypto.S3:
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 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: case crypto.SSEC:
// Validate the SSE-C Key set in the header. // Validate the SSE-C Key set in the header.
if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { 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 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 _, ok := crypto.IsRequested(r.Header); ok {
if globalIsGateway { if globalIsGateway {
if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { 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) _, err = globalBucketSSEConfigSys.Get(dstBucket)
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { 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 var srcOpts, dstOpts ObjectOptions
@ -1114,14 +1119,18 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
} }
var oldKey, newKey []byte var oldKey, newKey []byte
var newKeyID string
var kmsCtx crypto.Context
var objEncKey crypto.ObjectKey var objEncKey crypto.ObjectKey
sseCopyKMS := crypto.S3KMS.IsEncrypted(srcInfo.UserDefined)
sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined) sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined)
sseCopyC := crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header) sseCopyC := crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header)
sseC := crypto.SSEC.IsRequested(r.Header) sseC := crypto.SSEC.IsRequested(r.Header)
sseS3 := crypto.S3.IsRequested(r.Header) sseS3 := crypto.S3.IsRequested(r.Header)
sseKMS := crypto.S3KMS.IsRequested(r.Header)
isSourceEncrypted := sseCopyC || sseCopyS3 isSourceEncrypted := sseCopyC || sseCopyS3 || sseCopyKMS
isTargetEncrypted := sseC || sseS3 isTargetEncrypted := sseC || sseS3 || sseKMS
if sseC { if sseC {
newKey, err = ParseSSECustomerRequest(r) newKey, err = ParseSSECustomerRequest(r)
@ -1130,6 +1139,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return 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 // If src == dst and either
// - the object is encrypted using SSE-C and two different SSE-C keys are present // - 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, newKeyID, newKey, srcBucket, srcObject, encMetadata, kmsCtx); err != nil {
if err = rotateKey(oldKey, newKey, srcBucket, srcObject, encMetadata); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@ -1187,7 +1202,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
if isTargetEncrypted { if isTargetEncrypted {
var encReader io.Reader 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 { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
@ -1415,11 +1431,6 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return 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 _, ok := crypto.IsRequested(r.Header); ok {
if globalIsGateway { if globalIsGateway {
if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { 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) _, err = globalBucketSSEConfigSys.Get(bucket)
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { 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 actualSize := size
@ -1688,6 +1699,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
case crypto.S3: case crypto.S3:
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
objInfo.ETag, _ = DecryptETag(objectEncryptionKey, ObjectInfo{ETag: objInfo.ETag}) 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: case crypto.SSEC:
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
@ -2027,11 +2046,6 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
return 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 _, ok := crypto.IsRequested(r.Header); ok {
if globalIsGateway { if globalIsGateway {
if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() {
@ -2063,7 +2077,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
_, err = globalBucketSSEConfigSys.Get(bucket) _, err = globalBucketSSEConfigSys.Get(bucket)
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { 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 // Validate storage class metadata if present
@ -2487,11 +2501,6 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
return 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 _, ok := crypto.IsRequested(r.Header); ok {
if globalIsGateway { if globalIsGateway {
if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() { if crypto.SSEC.IsRequested(r.Header) && !objectAPI.IsEncryptionSupported() {

View File

@ -28,7 +28,9 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/kms"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
) )
@ -236,9 +238,7 @@ func (config *TierConfigMgr) configReader() (*PutObjReader, *ObjectOptions, erro
// Encrypt json encoded tier configurations // Encrypt json encoded tier configurations
metadata := make(map[string]string) metadata := make(map[string]string)
sseS3 := true encBr, oek, err := newEncryptReader(hr, crypto.S3KMS, "", nil, minioMetaBucket, tierConfigPath, metadata, kms.Context{})
var extKey [32]byte
encBr, oek, err := newEncryptReader(hr, extKey[:], minioMetaBucket, tierConfigPath, metadata, sseS3)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -1203,7 +1203,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
// Check if bucket encryption is enabled // Check if bucket encryption is enabled
_, err = globalBucketSSEConfigSys.Get(bucket) _, err = globalBucketSSEConfigSys.Get(bucket)
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { 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 // 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 { switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
case crypto.S3: case crypto.S3:
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 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: case crypto.SSEC:
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) 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 { switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
case crypto.S3: case crypto.S3:
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 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: case crypto.SSEC:
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))