mirror of https://github.com/minio/minio.git
add SSE-KMS support and use SSE-KMS for auto encryption (#12237)
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>
This commit is contained in:
parent
989e394a32
commit
af0c65be93
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 create a copy
|
||||
// of the client provided context and add the bucket
|
||||
// key, if not present.
|
||||
var kmsCtx = kms.Context{}
|
||||
for k, v := range ctx {
|
||||
kmsCtx[k] = v
|
||||
}
|
||||
if _, ok := kmsCtx[bucket]; !ok {
|
||||
kmsCtx[bucket] = path.Join(bucket, object)
|
||||
}
|
||||
newKey, err := GlobalKMS.GenerateKey(newKeyID, kmsCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 create a copy
|
||||
// of the client provided context and add the bucket
|
||||
// key, if not present.
|
||||
var kmsCtx = kms.Context{}
|
||||
for k, v := range ctx {
|
||||
kmsCtx[k] = v
|
||||
}
|
||||
if _, ok := kmsCtx[bucket]; !ok {
|
||||
kmsCtx[bucket] = path.Join(bucket, object)
|
||||
}
|
||||
key, err := GlobalKMS.GenerateKey(keyID, kmsCtx)
|
||||
if err != nil {
|
||||
return crypto.ObjectKey{}, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -29,7 +29,9 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
)
|
||||
|
||||
//go:generate msgp -file $GOFILE
|
||||
|
@ -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.S3, "", nil, minioMetaBucket, tierConfigPath, metadata, kms.Context{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue