diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index f0fac4309..441db5189 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -748,7 +748,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) return } @@ -895,7 +895,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption { // This request header needs to be set prior to setting ObjectOptions if !crypto.SSEC.IsRequested(r.Header) { - r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) } } @@ -907,7 +907,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h return } if objectAPI.IsEncryptionSupported() { - if crypto.IsRequested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests + if _, ok := crypto.IsRequested(formValues); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests if crypto.SSECopy.IsRequested(r.Header) { writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) return diff --git a/cmd/bucket-lifecycle.go b/cmd/bucket-lifecycle.go index 3bdceee05..305c3ddb3 100644 --- a/cmd/bucket-lifecycle.go +++ b/cmd/bucket-lifecycle.go @@ -28,7 +28,6 @@ import ( miniogo "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/tags" - "github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" sse "github.com/minio/minio/pkg/bucket/encryption" @@ -547,7 +546,7 @@ func (r *RestoreObjectRequest) validate(ctx context.Context, objAPI ObjectLayer) if r.OutputLocation.S3.Prefix == "" { return fmt.Errorf("Prefix is a required parameter in OutputLocation") } - if r.OutputLocation.S3.Encryption.EncryptionType != crypto.SSEAlgorithmAES256 { + if r.OutputLocation.S3.Encryption.EncryptionType != xhttp.AmzEncryptionAES { return NotImplemented{} } } @@ -573,7 +572,7 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O } meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String() if rreq.OutputLocation.S3.Encryption.EncryptionType != "" { - meta[crypto.SSEHeader] = crypto.SSEAlgorithmAES256 + meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES } return ObjectOptions{ Versioned: globalBucketVersioningSys.Enabled(bucket), diff --git a/cmd/crypto/header.go b/cmd/crypto/header.go index 013128394..08acbf986 100644 --- a/cmd/crypto/header.go +++ b/cmd/crypto/header.go @@ -19,231 +19,62 @@ import ( "crypto/md5" "encoding/base64" "net/http" - "strings" - jsoniter "github.com/json-iterator/go" xhttp "github.com/minio/minio/cmd/http" ) -// SSEHeader is the general AWS SSE HTTP header key. -const SSEHeader = "X-Amz-Server-Side-Encryption" - -const ( - // SSEKmsID is the HTTP header key referencing the SSE-KMS - // key ID. - SSEKmsID = SSEHeader + "-Aws-Kms-Key-Id" - - // SSEKmsContext is the HTTP header key referencing the - // SSE-KMS encryption context. - SSEKmsContext = SSEHeader + "-Context" -) - -const ( - // SSECAlgorithm is the HTTP header key referencing - // the SSE-C algorithm. - SSECAlgorithm = SSEHeader + "-Customer-Algorithm" - - // SSECKey is the HTTP header key referencing the - // SSE-C client-provided key.. - SSECKey = SSEHeader + "-Customer-Key" - - // SSECKeyMD5 is the HTTP header key referencing - // the MD5 sum of the client-provided key. - SSECKeyMD5 = SSEHeader + "-Customer-Key-Md5" -) - -const ( - // SSECopyAlgorithm is the HTTP header key referencing - // the SSE-C algorithm for SSE-C copy requests. - SSECopyAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm" - - // SSECopyKey is the HTTP header key referencing the SSE-C - // client-provided key for SSE-C copy requests. - SSECopyKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key" - - // SSECopyKeyMD5 is the HTTP header key referencing the - // MD5 sum of the client key for SSE-C copy requests. - SSECopyKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5" -) - -const ( - // SSEAlgorithmAES256 is the only supported value for the SSE-S3 or SSE-C algorithm header. - // For SSE-S3 see: https://docs.aws.amazon.com/AmazonS3/latest/dev/SSEUsingRESTAPI.html - // For SSE-C see: https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html - SSEAlgorithmAES256 = "AES256" - - // SSEAlgorithmKMS is the value of 'X-Amz-Server-Side-Encryption' for SSE-KMS. - // See: https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html - SSEAlgorithmKMS = "aws:kms" -) - // RemoveSensitiveHeaders removes confidential encryption // information - e.g. the SSE-C key - from the HTTP headers. // It has the same semantics as RemoveSensitiveEntires. func RemoveSensitiveHeaders(h http.Header) { - h.Del(SSECKey) - h.Del(SSECopyKey) + h.Del(xhttp.AmzServerSideEncryptionCustomerKey) + h.Del(xhttp.AmzServerSideEncryptionCopyCustomerKey) h.Del(xhttp.AmzMetaUnencryptedContentLength) h.Del(xhttp.AmzMetaUnencryptedContentMD5) } -// IsRequested returns true if the HTTP headers indicates -// that any form server-side encryption (SSE-C, SSE-S3 or SSE-KMS) -// is requested. -func IsRequested(h http.Header) bool { - return S3.IsRequested(h) || SSEC.IsRequested(h) || SSECopy.IsRequested(h) || S3KMS.IsRequested(h) -} - -// S3 represents AWS SSE-S3. It provides functionality to handle -// SSE-S3 requests. -var S3 = s3{} - -type s3 struct{} - -// IsRequested returns true if the HTTP headers indicates that -// the S3 client requests SSE-S3. -func (s3) IsRequested(h http.Header) bool { - _, ok := h[SSEHeader] - return ok && strings.ToLower(h.Get(SSEHeader)) != SSEAlgorithmKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value -} - -// ParseHTTP parses the SSE-S3 related HTTP headers and checks -// whether they contain valid values. -func (s3) ParseHTTP(h http.Header) (err error) { - if h.Get(SSEHeader) != SSEAlgorithmAES256 { - err = ErrInvalidEncryptionMethod - } - return -} - -// S3KMS represents AWS SSE-KMS. It provides functionality to -// handle SSE-KMS requests. -var S3KMS = s3KMS{} - -type s3KMS struct{} - -// IsRequested returns true if the HTTP headers indicates that -// the S3 client requests SSE-KMS. -func (s3KMS) IsRequested(h http.Header) bool { - if _, ok := h[SSEKmsID]; ok { - return true - } - if _, ok := h[SSEKmsContext]; ok { - return true - } - if _, ok := h[SSEHeader]; ok { - return strings.ToUpper(h.Get(SSEHeader)) != SSEAlgorithmAES256 // Return only true if the SSE header is specified and does not contain the SSE-S3 value - } - return false -} - -// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID -// and context, if present, on success. -func (s3KMS) ParseHTTP(h http.Header) (string, interface{}, error) { - algorithm := h.Get(SSEHeader) - if algorithm != SSEAlgorithmKMS { - return "", nil, ErrInvalidEncryptionMethod - } - - contextStr, ok := h[SSEKmsContext] - if ok { - var context map[string]interface{} - var json = jsoniter.ConfigCompatibleWithStandardLibrary - if err := json.Unmarshal([]byte(contextStr[0]), &context); err != nil { - return "", nil, err - } - return h.Get(SSEKmsID), context, nil - } - return h.Get(SSEKmsID), nil, nil -} - var ( - // SSEC represents AWS SSE-C. It provides functionality to handle - // SSE-C requests. - SSEC = ssec{} - // SSECopy represents AWS SSE-C for copy requests. It provides // functionality to handle SSE-C copy requests. SSECopy = ssecCopy{} ) -type ssec struct{} type ssecCopy struct{} -// IsRequested returns true if the HTTP headers contains -// at least one SSE-C header. SSE-C copy headers are ignored. -func (ssec) IsRequested(h http.Header) bool { - if _, ok := h[SSECAlgorithm]; ok { - return true - } - if _, ok := h[SSECKey]; ok { - return true - } - if _, ok := h[SSECKeyMD5]; ok { - return true - } - return false -} - // IsRequested returns true if the HTTP headers contains // at least one SSE-C copy header. Regular SSE-C headers // are ignored. func (ssecCopy) IsRequested(h http.Header) bool { - if _, ok := h[SSECopyAlgorithm]; ok { + if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm]; ok { return true } - if _, ok := h[SSECopyKey]; ok { + if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKey]; ok { return true } - if _, ok := h[SSECopyKeyMD5]; ok { + if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5]; ok { return true } return false } -// ParseHTTP parses the SSE-C headers and returns the SSE-C client key -// on success. SSE-C copy headers are ignored. -func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) { - if h.Get(SSECAlgorithm) != SSEAlgorithmAES256 { - return key, ErrInvalidCustomerAlgorithm - } - if h.Get(SSECKey) == "" { - return key, ErrMissingCustomerKey - } - if h.Get(SSECKeyMD5) == "" { - return key, ErrMissingCustomerKeyMD5 - } - - clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECKey)) - if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long - return key, ErrInvalidCustomerKey - } - keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECKeyMD5)) - if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) { - return key, ErrCustomerKeyMD5Mismatch - } - copy(key[:], clientKey) - return key, nil -} - // ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key // on success. Regular SSE-C headers are ignored. func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) { - if h.Get(SSECopyAlgorithm) != SSEAlgorithmAES256 { + if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) != xhttp.AmzEncryptionAES { return key, ErrInvalidCustomerAlgorithm } - if h.Get(SSECopyKey) == "" { + if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) == "" { return key, ErrMissingCustomerKey } - if h.Get(SSECopyKeyMD5) == "" { + if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) == "" { return key, ErrMissingCustomerKeyMD5 } - clientKey, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKey)) + clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey)) if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long return key, ErrInvalidCustomerKey } - keyMD5, err := base64.StdEncoding.DecodeString(h.Get(SSECopyKeyMD5)) + keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5)) if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) { return key, ErrCustomerKeyMD5Mismatch } diff --git a/cmd/crypto/header_test.go b/cmd/crypto/header_test.go index 00fe33437..0c7c73412 100644 --- a/cmd/crypto/header_test.go +++ b/cmd/crypto/header_test.go @@ -18,26 +18,29 @@ import ( "net/http" "sort" "testing" + + xhttp "github.com/minio/minio/cmd/http" ) func TestIsRequested(t *testing.T) { for i, test := range kmsIsRequestedTests { - if got := IsRequested(test.Header) && S3KMS.IsRequested(test.Header); got != test.Expected { + _, got := IsRequested(test.Header) + got = got && S3KMS.IsRequested(test.Header) + if got != test.Expected { t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got) } } for i, test := range s3IsRequestedTests { - if got := IsRequested(test.Header) && S3.IsRequested(test.Header); got != test.Expected { + _, got := IsRequested(test.Header) + got = got && S3.IsRequested(test.Header) + if got != test.Expected { t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got) } } for i, test := range ssecIsRequestedTests { - if got := IsRequested(test.Header) && SSEC.IsRequested(test.Header); got != test.Expected { - t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got) - } - } - for i, test := range ssecCopyIsRequestedTests { - if got := IsRequested(test.Header) && SSECopy.IsRequested(test.Header); got != test.Expected { + _, got := IsRequested(test.Header) + got = got && SSEC.IsRequested(test.Header) + if got != test.Expected { t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got) } } @@ -131,11 +134,11 @@ var s3IsRequestedTests = []struct { Header http.Header Expected bool }{ - {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES256"}}, Expected: true}, // 0 - {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1 - {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2 - {Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3 - {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{SSEAlgorithmKMS}}, Expected: false}, // 4 + {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES256"}}, Expected: true}, // 0 + {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES-256"}}, Expected: true}, // 1 + {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2 + {Header: http.Header{"X-Amz-Server-Side-Encryptio": []string{"AES256"}}, Expected: false}, // 3 + {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{xhttp.AmzEncryptionKMS}}, Expected: false}, // 4 } func TestS3IsRequested(t *testing.T) { @@ -403,7 +406,7 @@ func TestSSECopyParse(t *testing.T) { if err == nil && key == zeroKey { t.Errorf("Test %d: parsed client key is zero key", i) } - if _, ok := test.Header[SSECKey]; ok { + if _, ok := test.Header[xhttp.AmzServerSideEncryptionCustomerKey]; ok { t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i) } } @@ -414,47 +417,47 @@ var removeSensitiveHeadersTests = []struct { }{ { Header: http.Header{ - SSECKey: []string{""}, - SSECopyKey: []string{""}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{""}, + xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{""}, }, ExpectedHeader: http.Header{}, }, { // Standard SSE-C request headers Header: http.Header{ - SSECAlgorithm: []string{SSEAlgorithmAES256}, - SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, }, ExpectedHeader: http.Header{ - SSECAlgorithm: []string{SSEAlgorithmAES256}, - SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, }, }, { // Standard SSE-C + SSE-C-copy request headers Header: http.Header{ - SSECAlgorithm: []string{SSEAlgorithmAES256}, - SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, - SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, }, ExpectedHeader: http.Header{ - SSECAlgorithm: []string{SSEAlgorithmAES256}, - SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, - SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, }, }, { // Standard SSE-C + metadata request headers Header: http.Header{ - SSECAlgorithm: []string{SSEAlgorithmAES256}, - SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, - "X-Amz-Meta-Test-1": []string{"Test-1"}, + xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + "X-Amz-Meta-Test-1": []string{"Test-1"}, }, ExpectedHeader: http.Header{ - SSECAlgorithm: []string{SSEAlgorithmAES256}, - SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, - "X-Amz-Meta-Test-1": []string{"Test-1"}, + xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, + "X-Amz-Meta-Test-1": []string{"Test-1"}, }, }, { // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w diff --git a/cmd/crypto/metadata.go b/cmd/crypto/metadata.go index 5887c7064..da72c0c4a 100644 --- a/cmd/crypto/metadata.go +++ b/cmd/crypto/metadata.go @@ -15,19 +15,42 @@ package crypto import ( - "context" - "encoding/base64" - "errors" - xhttp "github.com/minio/minio/cmd/http" - "github.com/minio/minio/cmd/logger" +) + +const ( + // MetaMultipart indicates that the object has been uploaded + // in multiple parts - via the S3 multipart API. + MetaMultipart = "X-Minio-Internal-Encrypted-Multipart" + + // MetaIV is the random initialization vector (IV) used for + // the MinIO-internal key derivation. + MetaIV = "X-Minio-Internal-Server-Side-Encryption-Iv" + + // MetaAlgorithm is the algorithm used to derive internal keys + // and encrypt the objects. + MetaAlgorithm = "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm" + + // MetaSealedKeySSEC is the sealed object encryption key in case of SSE-C. + MetaSealedKeySSEC = "X-Minio-Internal-Server-Side-Encryption-Sealed-Key" + // MetaSealedKeyS3 is the sealed object encryption key in case of SSE-S3 + MetaSealedKeyS3 = "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key" + // MetaSealedKeyKMS is the sealed object encryption key in case of SSE-KMS + MetaSealedKeyKMS = "X-Minio-Internal-Server-Side-Encryption-Kms-Sealed-Key" + + // MetaKeyID is the KMS master key ID used to generate/encrypt the data + // encryption key (DEK). + MetaKeyID = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id" + // MetaDataEncryptionKey is the sealed data encryption key (DEK) received from + // the KMS. + MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key" ) // IsMultiPart returns true if the object metadata indicates // that it was uploaded using some form of server-side-encryption // and the S3 multipart API. func IsMultiPart(metadata map[string]string) bool { - if _, ok := metadata[SSEMultipart]; ok { + if _, ok := metadata[MetaMultipart]; ok { return true } return false @@ -37,8 +60,8 @@ func IsMultiPart(metadata map[string]string) bool { // information - e.g. the SSE-C key - from the metadata map. // It has the same semantics as RemoveSensitiveHeaders. func RemoveSensitiveEntries(metadata map[string]string) { // The functions is tested in TestRemoveSensitiveHeaders for compatibility reasons - delete(metadata, SSECKey) - delete(metadata, SSECopyKey) + delete(metadata, xhttp.AmzServerSideEncryptionCustomerKey) + delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKey) delete(metadata, xhttp.AmzMetaUnencryptedContentLength) delete(metadata, xhttp.AmzMetaUnencryptedContentMD5) } @@ -46,31 +69,36 @@ func RemoveSensitiveEntries(metadata map[string]string) { // The functions is te // RemoveSSEHeaders removes all crypto-specific SSE // header entries from the metadata map. func RemoveSSEHeaders(metadata map[string]string) { - delete(metadata, SSEHeader) - delete(metadata, SSEKmsID) - delete(metadata, SSEKmsContext) - delete(metadata, SSECKeyMD5) - delete(metadata, SSECAlgorithm) + delete(metadata, xhttp.AmzServerSideEncryption) + delete(metadata, xhttp.AmzServerSideEncryptionKmsID) + delete(metadata, xhttp.AmzServerSideEncryptionKmsContext) + delete(metadata, xhttp.AmzServerSideEncryptionCustomerAlgorithm) + delete(metadata, xhttp.AmzServerSideEncryptionCustomerKey) + delete(metadata, xhttp.AmzServerSideEncryptionCustomerKeyMD5) + delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) + delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKey) + delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) } // RemoveInternalEntries removes all crypto-specific internal // metadata entries from the metadata map. func RemoveInternalEntries(metadata map[string]string) { - delete(metadata, SSEMultipart) - delete(metadata, SSEIV) - delete(metadata, SSESealAlgorithm) - delete(metadata, SSECSealedKey) - delete(metadata, S3SealedKey) - delete(metadata, S3KMSKeyID) - delete(metadata, S3KMSSealedKey) + delete(metadata, MetaMultipart) + delete(metadata, MetaAlgorithm) + delete(metadata, MetaIV) + delete(metadata, MetaSealedKeySSEC) + delete(metadata, MetaSealedKeyS3) + delete(metadata, MetaSealedKeyKMS) + delete(metadata, MetaKeyID) + delete(metadata, MetaDataEncryptionKey) } // IsSourceEncrypted returns true if the source is encrypted func IsSourceEncrypted(metadata map[string]string) bool { - if _, ok := metadata[SSECAlgorithm]; ok { + if _, ok := metadata[xhttp.AmzServerSideEncryptionCustomerAlgorithm]; ok { return true } - if _, ok := metadata[SSEHeader]; ok { + if _, ok := metadata[xhttp.AmzServerSideEncryption]; ok { return true } return false @@ -82,10 +110,10 @@ func IsSourceEncrypted(metadata map[string]string) bool { // IsEncrypted only checks whether the metadata contains at least // one entry indicating SSE-C or SSE-S3. func IsEncrypted(metadata map[string]string) bool { - if _, ok := metadata[SSEIV]; ok { + if _, ok := metadata[MetaIV]; ok { return true } - if _, ok := metadata[SSESealAlgorithm]; ok { + if _, ok := metadata[MetaAlgorithm]; ok { return true } if IsMultiPart(metadata) { @@ -97,28 +125,7 @@ func IsEncrypted(metadata map[string]string) bool { if SSEC.IsEncrypted(metadata) { return true } - return false -} - -// IsEncrypted returns true if the object metadata indicates -// that the object was uploaded using SSE-S3. -func (s3) IsEncrypted(metadata map[string]string) bool { - if _, ok := metadata[S3SealedKey]; ok { - return true - } - if _, ok := metadata[S3KMSKeyID]; ok { - return true - } - if _, ok := metadata[S3KMSSealedKey]; ok { - return true - } - return false -} - -// IsEncrypted returns true if the object metadata indicates -// that the object was uploaded using SSE-C. -func (ssec) IsEncrypted(metadata map[string]string) bool { - if _, ok := metadata[SSECSealedKey]; ok { + if S3KMS.IsEncrypted(metadata) { return true } return false @@ -129,159 +136,11 @@ func (ssec) IsEncrypted(metadata map[string]string) bool { // metadata is nil. func CreateMultipartMetadata(metadata map[string]string) map[string]string { if metadata == nil { - return map[string]string{SSEMultipart: ""} - } - metadata[SSEMultipart] = "" - return metadata -} - -// CreateMetadata encodes the sealed object key into the metadata and returns -// 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 (s3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string { - if sealedKey.Algorithm != SealAlgorithm { - logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) - } - - // There are two possibilites: - // - We use a KMS -> There must be non-empty key ID and a KMS data key. - // - We use a K/V -> There must be no key ID and no KMS data key. - // Otherwise, the caller has passed an invalid argument combination. - if keyID == "" && len(kmsKey) != 0 { - logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present")) - } - if keyID != "" && len(kmsKey) == 0 { - logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present")) - } - - if metadata == nil { - metadata = make(map[string]string, 5) - } - - metadata[SSESealAlgorithm] = sealedKey.Algorithm - metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) - metadata[S3SealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) - if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key. - metadata[S3KMSKeyID] = keyID - metadata[S3KMSSealedKey] = base64.StdEncoding.EncodeToString(kmsKey) - } - return metadata -} - -// ParseMetadata extracts all SSE-S3 related values from the object metadata -// and checks whether they are well-formed. It returns the sealed object key -// on success. If the metadata contains both, a KMS master key ID and a sealed -// KMS data key it returns both. If the metadata does not contain neither a -// KMS master key ID nor a sealed KMS data key it returns an empty keyID and -// KMS data key. Otherwise, it returns an error. -func (s3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) { - // Extract all required values from object metadata - b64IV, ok := metadata[SSEIV] - if !ok { - return keyID, kmsKey, sealedKey, errMissingInternalIV - } - algorithm, ok := metadata[SSESealAlgorithm] - if !ok { - return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm - } - b64SealedKey, ok := metadata[S3SealedKey] - if !ok { - return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3") - } - - // There are two possibilites: - // - We use a KMS -> There must be a key ID and a KMS data key. - // - We use a K/V -> There must be no key ID and no KMS data key. - // Otherwise, the metadata is corrupted. - keyID, idPresent := metadata[S3KMSKeyID] - b64KMSSealedKey, kmsKeyPresent := metadata[S3KMSSealedKey] - if !idPresent && kmsKeyPresent { - return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3") - } - if idPresent && !kmsKeyPresent { - return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3") - } - - // Check whether all extracted values are well-formed - var iv [32]byte - n, err := base64.StdEncoding.Decode(iv[:], []byte(b64IV)) - if err != nil || n != 32 { - return keyID, kmsKey, sealedKey, errInvalidInternalIV - } - if algorithm != SealAlgorithm { - return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm - } - var encryptedKey [64]byte - n, err = base64.StdEncoding.Decode(encryptedKey[:], []byte(b64SealedKey)) - if err != nil || n != 64 { - return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid") - } - if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key. - kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) - if err != nil { - return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid") - } - } - - sealedKey.Algorithm = algorithm - sealedKey.IV = iv - sealedKey.Key = encryptedKey - return keyID, kmsKey, sealedKey, nil -} - -// CreateMetadata encodes the sealed key into the metadata and returns the modified metadata. -// It allocates a new metadata map if metadata is nil. -func (ssec) CreateMetadata(metadata map[string]string, sealedKey SealedKey) map[string]string { - if sealedKey.Algorithm != SealAlgorithm { - logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-C", sealedKey.Algorithm)) - } - - if metadata == nil { - metadata = make(map[string]string, 3) + return map[string]string{MetaMultipart: ""} } - metadata[SSESealAlgorithm] = SealAlgorithm - metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) - metadata[SSECSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) + metadata[MetaMultipart] = "" return metadata } -// ParseMetadata extracts all SSE-C related values from the object metadata -// and checks whether they are well-formed. It returns the sealed object key -// on success. -func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err error) { - // Extract all required values from object metadata - b64IV, ok := metadata[SSEIV] - if !ok { - return sealedKey, errMissingInternalIV - } - algorithm, ok := metadata[SSESealAlgorithm] - if !ok { - return sealedKey, errMissingInternalSealAlgorithm - } - b64SealedKey, ok := metadata[SSECSealedKey] - if !ok { - return sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-C") - } - - // Check whether all extracted values are well-formed - iv, err := base64.StdEncoding.DecodeString(b64IV) - if err != nil || len(iv) != 32 { - return sealedKey, errInvalidInternalIV - } - if algorithm != SealAlgorithm && algorithm != InsecureSealAlgorithm { - return sealedKey, errInvalidInternalSealAlgorithm - } - encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) - if err != nil || len(encryptedKey) != 64 { - return sealedKey, Errorf("The internal sealed key for SSE-C is invalid") - } - - sealedKey.Algorithm = algorithm - copy(sealedKey.IV[:], iv) - copy(sealedKey.Key[:], encryptedKey) - return sealedKey, nil -} - // IsETagSealed returns true if the etag seems to be encrypted. func IsETagSealed(etag []byte) bool { return len(etag) > 16 } diff --git a/cmd/crypto/metadata_test.go b/cmd/crypto/metadata_test.go index 828eea0b1..437c1eecf 100644 --- a/cmd/crypto/metadata_test.go +++ b/cmd/crypto/metadata_test.go @@ -27,9 +27,9 @@ var isMultipartTests = []struct { Metadata map[string]string Multipart bool }{ - {Multipart: true, Metadata: map[string]string{SSEMultipart: ""}}, // 0 + {Multipart: true, Metadata: map[string]string{MetaMultipart: ""}}, // 0 {Multipart: true, Metadata: map[string]string{"X-Minio-Internal-Encrypted-Multipart": ""}}, // 1 - {Multipart: true, Metadata: map[string]string{SSEMultipart: "some-value"}}, // 2 + {Multipart: true, Metadata: map[string]string{MetaMultipart: "some-value"}}, // 2 {Multipart: false, Metadata: map[string]string{"": ""}}, // 3 {Multipart: false, Metadata: map[string]string{"X-Minio-Internal-EncryptedMultipart": ""}}, // 4 } @@ -46,13 +46,13 @@ var isEncryptedTests = []struct { Metadata map[string]string Encrypted bool }{ - {Encrypted: true, Metadata: map[string]string{SSEMultipart: ""}}, // 0 - {Encrypted: true, Metadata: map[string]string{SSEIV: ""}}, // 1 - {Encrypted: true, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 - {Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 - {Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4 - {Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 - {Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 + {Encrypted: true, Metadata: map[string]string{MetaMultipart: ""}}, // 0 + {Encrypted: true, Metadata: map[string]string{MetaIV: ""}}, // 1 + {Encrypted: true, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2 + {Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3 + {Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4 + {Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5 + {Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 } @@ -69,13 +69,13 @@ var s3IsEncryptedTests = []struct { Metadata map[string]string Encrypted bool }{ - {Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0 - {Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1 - {Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 - {Encrypted: false, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 - {Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4 - {Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 - {Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 + {Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0 + {Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1 + {Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2 + {Encrypted: false, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3 + {Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4 + {Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5 + {Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 } @@ -92,13 +92,13 @@ var ssecIsEncryptedTests = []struct { Metadata map[string]string Encrypted bool }{ - {Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0 - {Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1 - {Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 - {Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 - {Encrypted: false, Metadata: map[string]string{S3SealedKey: ""}}, // 4 - {Encrypted: false, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 - {Encrypted: false, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 + {Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0 + {Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1 + {Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2 + {Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3 + {Encrypted: false, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4 + {Encrypted: false, Metadata: map[string]string{MetaKeyID: ""}}, // 5 + {Encrypted: false, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 } @@ -121,65 +121,65 @@ var s3ParseMetadataTests = []struct { }{ {ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}}, // 0 { - ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, + ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{MetaIV: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 1 { ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-S3"), - Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, + Metadata: map[string]string{MetaIV: "", MetaAlgorithm: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 2 { ExpectedErr: Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3"), - Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSSealedKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, + Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaDataEncryptionKey: "IAAF0b=="}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 3 { ExpectedErr: Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3"), - Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: ""}, + Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 4 { ExpectedErr: errInvalidInternalIV, - Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: "", S3KMSSealedKey: ""}, + Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: "", MetaDataEncryptionKey: ""}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 5 { ExpectedErr: errInvalidInternalSealAlgorithm, Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: "", S3SealedKey: "", S3KMSKeyID: "", S3KMSSealedKey: "", + MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: "", MetaSealedKeyS3: "", MetaKeyID: "", MetaDataEncryptionKey: "", }, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 6 { ExpectedErr: Errorf("The internal sealed key for SSE-S3 is invalid"), Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, S3SealedKey: "", - S3KMSKeyID: "", S3KMSSealedKey: "", + MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, MetaSealedKeyS3: "", + MetaKeyID: "", MetaDataEncryptionKey: "", }, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, }, // 7 { ExpectedErr: Errorf("The internal sealed KMS data key for SSE-S3 is invalid"), Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, - S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "key-1", - S3KMSSealedKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64 + MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, + MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "key-1", + MetaDataEncryptionKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64 }, DataKey: []byte{}, KeyID: "key-1", SealedKey: SealedKey{}, }, // 8 { ExpectedErr: nil, Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, - S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "", S3KMSSealedKey: "", + MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, + MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "", MetaDataEncryptionKey: "", }, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{Algorithm: SealAlgorithm}, }, // 9 { ExpectedErr: nil, Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: SealAlgorithm, - S3SealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), S3KMSKeyID: "key-1", - S3KMSSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 48)), + MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: SealAlgorithm, + MetaSealedKeyS3: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), MetaKeyID: "key-1", + MetaDataEncryptionKey: base64.StdEncoding.EncodeToString(make([]byte, 48)), }, DataKey: make([]byte, 48), KeyID: "key-1", SealedKey: SealedKey{Algorithm: SealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}}, }, // 10 @@ -223,43 +223,43 @@ var ssecParseMetadataTests = []struct { SealedKey SealedKey }{ - {ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0 - {ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, SealedKey: SealedKey{}}, // 1 + {ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0 + {ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{MetaIV: ""}, SealedKey: SealedKey{}}, // 1 { ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-C"), - Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: ""}, SealedKey: SealedKey{}, + Metadata: map[string]string{MetaIV: "", MetaAlgorithm: ""}, SealedKey: SealedKey{}, }, // 2 { ExpectedErr: errInvalidInternalIV, - Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", SSECSealedKey: ""}, SealedKey: SealedKey{}, + Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeySSEC: ""}, SealedKey: SealedKey{}, }, // 3 { ExpectedErr: errInvalidInternalSealAlgorithm, Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: "", SSECSealedKey: "", + MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: "", MetaSealedKeySSEC: "", }, SealedKey: SealedKey{}, }, // 4 { ExpectedErr: Errorf("The internal sealed key for SSE-C is invalid"), Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, SSECSealedKey: "", + MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, MetaSealedKeySSEC: "", }, SealedKey: SealedKey{}, }, // 5 { ExpectedErr: nil, Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, - SSECSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), + MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, + MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(make([]byte, 64)), }, SealedKey: SealedKey{Algorithm: SealAlgorithm}, }, // 6 { ExpectedErr: nil, Metadata: map[string]string{ - SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: InsecureSealAlgorithm, - SSECSealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), + MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: InsecureSealAlgorithm, + MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), }, SealedKey: SealedKey{Algorithm: InsecureSealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}}, }, // 7 @@ -267,8 +267,8 @@ var ssecParseMetadataTests = []struct { func TestCreateMultipartMetadata(t *testing.T) { metadata := CreateMultipartMetadata(nil) - if v, ok := metadata[SSEMultipart]; !ok || v != "" { - t.Errorf("Metadata is missing the correct value for '%s': got '%s' - want '%s'", SSEMultipart, v, "") + if v, ok := metadata[MetaMultipart]; !ok || v != "" { + t.Errorf("Metadata is missing the correct value for '%s': got '%s' - want '%s'", MetaMultipart, v, "") } } @@ -411,20 +411,20 @@ var removeInternalEntriesTests = []struct { }{ { // 0 Metadata: map[string]string{ - SSEMultipart: "", - SSEIV: "", - SSESealAlgorithm: "", - SSECSealedKey: "", - S3SealedKey: "", - S3KMSKeyID: "", - S3KMSSealedKey: "", + MetaMultipart: "", + MetaIV: "", + MetaAlgorithm: "", + MetaSealedKeySSEC: "", + MetaSealedKeyS3: "", + MetaKeyID: "", + MetaDataEncryptionKey: "", }, Expected: map[string]string{}, }, { // 1 Metadata: map[string]string{ - SSEMultipart: "", - SSEIV: "", + MetaMultipart: "", + MetaIV: "", "X-Amz-Meta-A": "X", "X-Minio-Internal-B": "Y", }, diff --git a/cmd/crypto/sse-c.go b/cmd/crypto/sse-c.go new file mode 100644 index 000000000..c0b71c76b --- /dev/null +++ b/cmd/crypto/sse-c.go @@ -0,0 +1,157 @@ +/* + * Minio Cloud Storage, (C) 2019-2020 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package crypto + +import ( + "bytes" + "context" + "crypto/md5" + "encoding/base64" + "net/http" + + xhttp "github.com/minio/minio/cmd/http" + "github.com/minio/minio/cmd/logger" +) + +type ssec struct{} + +var ( + // SSEC represents AWS SSE-C. It provides functionality to handle + // SSE-C requests. + SSEC = ssec{} + + _ Type = SSEC +) + +// String returns the SSE domain as string. For SSE-C the +// domain is "SSE-C". +func (ssec) String() string { return "SSE-C" } + +// IsRequested returns true if the HTTP headers contains +// at least one SSE-C header. SSE-C copy headers are ignored. +func (ssec) IsRequested(h http.Header) bool { + if _, ok := h[xhttp.AmzServerSideEncryptionCustomerAlgorithm]; ok { + return true + } + if _, ok := h[xhttp.AmzServerSideEncryptionCustomerKey]; ok { + return true + } + if _, ok := h[xhttp.AmzServerSideEncryptionCustomerKeyMD5]; ok { + return true + } + return false +} + +// IsEncrypted returns true if the metadata contains an SSE-C +// entry inidicating that the object has been encrypted using +// SSE-C. +func (ssec) IsEncrypted(metadata map[string]string) bool { + if _, ok := metadata[MetaSealedKeySSEC]; ok { + return true + } + return false +} + +// ParseHTTP parses the SSE-C headers and returns the SSE-C client key +// on success. SSE-C copy headers are ignored. +func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) { + if h.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm) != xhttp.AmzEncryptionAES { + return key, ErrInvalidCustomerAlgorithm + } + if h.Get(xhttp.AmzServerSideEncryptionCustomerKey) == "" { + return key, ErrMissingCustomerKey + } + if h.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5) == "" { + return key, ErrMissingCustomerKeyMD5 + } + + clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCustomerKey)) + if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long + return key, ErrInvalidCustomerKey + } + keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) + if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) { + return key, ErrCustomerKeyMD5Mismatch + } + copy(key[:], clientKey) + return key, nil +} + +// UnsealObjectKey extracts and decrypts the sealed object key +// from the metadata using the SSE-C client key of the HTTP headers +// and returns the decrypted object key. +func (s3 ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { + clientKey, err := s3.ParseHTTP(h) + if err != nil { + return + } + return unsealObjectKey(clientKey, metadata, bucket, object) +} + +// CreateMetadata encodes the sealed key into the metadata +// and returns the modified metadata. It allocates a new +// metadata map if metadata is nil. +func (ssec) CreateMetadata(metadata map[string]string, sealedKey SealedKey) map[string]string { + if sealedKey.Algorithm != SealAlgorithm { + logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-C", sealedKey.Algorithm)) + } + + if metadata == nil { + metadata = make(map[string]string, 3) + } + metadata[MetaAlgorithm] = SealAlgorithm + metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) + metadata[MetaSealedKeySSEC] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) + return metadata +} + +// ParseMetadata extracts all SSE-C related values from the object metadata +// and checks whether they are well-formed. It returns the sealed object key +// on success. +func (ssec) ParseMetadata(metadata map[string]string) (sealedKey SealedKey, err error) { + // Extract all required values from object metadata + b64IV, ok := metadata[MetaIV] + if !ok { + return sealedKey, errMissingInternalIV + } + algorithm, ok := metadata[MetaAlgorithm] + if !ok { + return sealedKey, errMissingInternalSealAlgorithm + } + b64SealedKey, ok := metadata[MetaSealedKeySSEC] + if !ok { + return sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-C") + } + + // Check whether all extracted values are well-formed + iv, err := base64.StdEncoding.DecodeString(b64IV) + if err != nil || len(iv) != 32 { + return sealedKey, errInvalidInternalIV + } + if algorithm != SealAlgorithm && algorithm != InsecureSealAlgorithm { + return sealedKey, errInvalidInternalSealAlgorithm + } + encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) + if err != nil || len(encryptedKey) != 64 { + return sealedKey, Errorf("The internal sealed key for SSE-C is invalid") + } + + sealedKey.Algorithm = algorithm + copy(sealedKey.IV[:], iv) + copy(sealedKey.Key[:], encryptedKey) + return sealedKey, nil +} diff --git a/cmd/crypto/sse-kms.go b/cmd/crypto/sse-kms.go new file mode 100644 index 000000000..f8a06a39b --- /dev/null +++ b/cmd/crypto/sse-kms.go @@ -0,0 +1,201 @@ +/* + * Minio Cloud Storage, (C) 2019-2020 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package crypto + +import ( + "context" + "encoding/base64" + "errors" + "net/http" + "path" + "strings" + + jsoniter "github.com/json-iterator/go" + xhttp "github.com/minio/minio/cmd/http" + "github.com/minio/minio/cmd/logger" +) + +type ssekms struct{} + +var ( + // S3KMS represents AWS SSE-KMS. It provides functionality to + // handle SSE-KMS requests. + S3KMS = ssekms{} + + _ Type = S3KMS +) + +// String returns the SSE domain as string. For SSE-KMS the +// domain is "SSE-KMS". +func (ssekms) String() string { return "SSE-KMS" } + +// IsRequested returns true if the HTTP headers contains +// at least one SSE-KMS header. +func (ssekms) IsRequested(h http.Header) bool { + if _, ok := h[xhttp.AmzServerSideEncryptionKmsID]; ok { + return true + } + if _, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok { + return true + } + if _, ok := h[xhttp.AmzServerSideEncryption]; ok { + return strings.ToUpper(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionAES // Return only true if the SSE header is specified and does not contain the SSE-S3 value + } + return false +} + +// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID +// and the KMS context on success. +func (ssekms) ParseHTTP(h http.Header) (string, Context, error) { + algorithm := h.Get(xhttp.AmzServerSideEncryption) + if algorithm != xhttp.AmzEncryptionKMS { + return "", nil, ErrInvalidEncryptionMethod + } + + var ctx Context + if context, ok := h[xhttp.AmzServerSideEncryptionKmsContext]; ok { + var json = jsoniter.ConfigCompatibleWithStandardLibrary + if err := json.Unmarshal([]byte(context[0]), &ctx); err != nil { + return "", nil, err + } + } + return h.Get(xhttp.AmzServerSideEncryptionKmsID), ctx, nil +} + +// IsEncrypted returns true if the object metadata indicates +// that the object was uploaded using SSE-KMS. +func (ssekms) IsEncrypted(metadata map[string]string) bool { + if _, ok := metadata[MetaSealedKeyKMS]; ok { + return true + } + if _, ok := metadata[MetaKeyID]; ok { + return true + } + if _, ok := metadata[MetaDataEncryptionKey]; ok { + return true + } + return false +} + +// UnsealObjectKey extracts and decrypts the sealed object key +// from the metadata using KMS and returns the decrypted object +// key. +func (s3 ssekms) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { + keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata) + if err != nil { + return key, err + } + unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)}) + if err != nil { + return key, err + } + err = key.Unseal(unsealKey, sealedKey, s3.String(), bucket, object) + return key, err +} + +// CreateMetadata encodes the sealed object key into the metadata and returns +// 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 { + if sealedKey.Algorithm != SealAlgorithm { + logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) + } + + // There are two possibilites: + // - We use a KMS -> There must be non-empty key ID and a KMS data key. + // - We use a K/V -> There must be no key ID and no KMS data key. + // Otherwise, the caller has passed an invalid argument combination. + if keyID == "" && len(kmsKey) != 0 { + logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present")) + } + if keyID != "" && len(kmsKey) == 0 { + logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present")) + } + + if metadata == nil { + metadata = make(map[string]string, 5) + } + + metadata[MetaAlgorithm] = sealedKey.Algorithm + metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) + metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) + 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) + } + return metadata +} + +// ParseMetadata extracts all SSE-KMS related values from the object metadata +// and checks whether they are well-formed. It returns the sealed object key +// on success. If the metadata contains both, a KMS master key ID and a sealed +// KMS data key it returns both. If the metadata does not contain neither a +// KMS master key ID nor a sealed KMS data key it returns an empty keyID and +// KMS data key. Otherwise, it returns an error. +func (ssekms) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) { + // Extract all required values from object metadata + b64IV, ok := metadata[MetaIV] + if !ok { + return keyID, kmsKey, sealedKey, errMissingInternalIV + } + algorithm, ok := metadata[MetaAlgorithm] + if !ok { + return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm + } + b64SealedKey, ok := metadata[MetaSealedKeyKMS] + if !ok { + return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3") + } + + // There are two possibilites: + // - We use a KMS -> There must be a key ID and a KMS data key. + // - We use a K/V -> There must be no key ID and no KMS data key. + // Otherwise, the metadata is corrupted. + keyID, idPresent := metadata[MetaKeyID] + b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey] + if !idPresent && kmsKeyPresent { + return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3") + } + if idPresent && !kmsKeyPresent { + return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3") + } + + // Check whether all extracted values are well-formed + iv, err := base64.StdEncoding.DecodeString(b64IV) + if err != nil || len(iv) != 32 { + return keyID, kmsKey, sealedKey, errInvalidInternalIV + } + if algorithm != SealAlgorithm { + return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm + } + encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) + if err != nil || len(encryptedKey) != 64 { + return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid") + } + if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key. + kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) + if err != nil { + return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid") + } + } + + sealedKey.Algorithm = algorithm + copy(sealedKey.IV[:], iv) + copy(sealedKey.Key[:], encryptedKey) + return keyID, kmsKey, sealedKey, nil +} diff --git a/cmd/crypto/sse-s3.go b/cmd/crypto/sse-s3.go new file mode 100644 index 000000000..9471744f3 --- /dev/null +++ b/cmd/crypto/sse-s3.go @@ -0,0 +1,181 @@ +/* + * Minio Cloud Storage, (C) 2019-2020 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package crypto + +import ( + "context" + "encoding/base64" + "errors" + "net/http" + "path" + "strings" + + xhttp "github.com/minio/minio/cmd/http" + "github.com/minio/minio/cmd/logger" +) + +type sses3 struct{} + +var ( + // S3 represents AWS SSE-S3. It provides functionality to handle + // SSE-S3 requests. + S3 = sses3{} + + _ Type = S3 +) + +// String returns the SSE domain as string. For SSE-S3 the +// domain is "SSE-S3". +func (sses3) String() string { return "SSE-S3" } + +func (sses3) IsRequested(h http.Header) bool { + _, ok := h[xhttp.AmzServerSideEncryption] + return ok && strings.ToLower(h.Get(xhttp.AmzServerSideEncryption)) != xhttp.AmzEncryptionKMS // Return only true if the SSE header is specified and does not contain the SSE-KMS value +} + +// ParseHTTP parses the SSE-S3 related HTTP headers and checks +// whether they contain valid values. +func (sses3) ParseHTTP(h http.Header) error { + if h.Get(xhttp.AmzServerSideEncryption) != xhttp.AmzEncryptionAES { + return ErrInvalidEncryptionMethod + } + return nil +} + +// IsEncrypted returns true if the object metadata indicates +// that the object was uploaded using SSE-S3. +func (sses3) IsEncrypted(metadata map[string]string) bool { + if _, ok := metadata[MetaSealedKeyS3]; ok { + return true + } + if _, ok := metadata[MetaKeyID]; ok { + return true + } + if _, ok := metadata[MetaDataEncryptionKey]; ok { + return true + } + return false +} + +// UnsealObjectKey extracts and decrypts the sealed object key +// from the metadata using KMS and returns the decrypted object +// key. +func (s3 sses3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { + keyID, kmsKey, sealedKey, err := s3.ParseMetadata(metadata) + if err != nil { + return key, err + } + unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)}) + if err != nil { + return key, err + } + err = key.Unseal(unsealKey, sealedKey, s3.String(), bucket, object) + return key, err +} + +// CreateMetadata encodes the sealed object key into the metadata and returns +// 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 (sses3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string { + if sealedKey.Algorithm != SealAlgorithm { + logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm)) + } + + // There are two possibilites: + // - We use a KMS -> There must be non-empty key ID and a KMS data key. + // - We use a K/V -> There must be no key ID and no KMS data key. + // Otherwise, the caller has passed an invalid argument combination. + if keyID == "" && len(kmsKey) != 0 { + logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present")) + } + if keyID != "" && len(kmsKey) == 0 { + logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present")) + } + + if metadata == nil { + metadata = make(map[string]string, 5) + } + + metadata[MetaAlgorithm] = sealedKey.Algorithm + metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:]) + metadata[MetaSealedKeyS3] = base64.StdEncoding.EncodeToString(sealedKey.Key[:]) + 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) + } + return metadata +} + +// ParseMetadata extracts all SSE-S3 related values from the object metadata +// and checks whether they are well-formed. It returns the sealed object key +// on success. If the metadata contains both, a KMS master key ID and a sealed +// KMS data key it returns both. If the metadata does not contain neither a +// KMS master key ID nor a sealed KMS data key it returns an empty keyID and +// KMS data key. Otherwise, it returns an error. +func (sses3) ParseMetadata(metadata map[string]string) (keyID string, kmsKey []byte, sealedKey SealedKey, err error) { + // Extract all required values from object metadata + b64IV, ok := metadata[MetaIV] + if !ok { + return keyID, kmsKey, sealedKey, errMissingInternalIV + } + algorithm, ok := metadata[MetaAlgorithm] + if !ok { + return keyID, kmsKey, sealedKey, errMissingInternalSealAlgorithm + } + b64SealedKey, ok := metadata[MetaSealedKeyS3] + if !ok { + return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed key for SSE-S3") + } + + // There are two possibilites: + // - We use a KMS -> There must be a key ID and a KMS data key. + // - We use a K/V -> There must be no key ID and no KMS data key. + // Otherwise, the metadata is corrupted. + keyID, idPresent := metadata[MetaKeyID] + b64KMSSealedKey, kmsKeyPresent := metadata[MetaDataEncryptionKey] + if !idPresent && kmsKeyPresent { + return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3") + } + if idPresent && !kmsKeyPresent { + return keyID, kmsKey, sealedKey, Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3") + } + + // Check whether all extracted values are well-formed + iv, err := base64.StdEncoding.DecodeString(b64IV) + if err != nil || len(iv) != 32 { + return keyID, kmsKey, sealedKey, errInvalidInternalIV + } + if algorithm != SealAlgorithm { + return keyID, kmsKey, sealedKey, errInvalidInternalSealAlgorithm + } + encryptedKey, err := base64.StdEncoding.DecodeString(b64SealedKey) + if err != nil || len(encryptedKey) != 64 { + return keyID, kmsKey, sealedKey, Errorf("The internal sealed key for SSE-S3 is invalid") + } + if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key. + kmsKey, err = base64.StdEncoding.DecodeString(b64KMSSealedKey) + if err != nil { + return keyID, kmsKey, sealedKey, Errorf("The internal sealed KMS data key for SSE-S3 is invalid") + } + } + + sealedKey.Algorithm = algorithm + copy(sealedKey.IV[:], iv) + copy(sealedKey.Key[:], encryptedKey) + return keyID, kmsKey, sealedKey, nil +} diff --git a/cmd/crypto/sse.go b/cmd/crypto/sse.go index 87adcc1e9..4462a4584 100644 --- a/cmd/crypto/sse.go +++ b/cmd/crypto/sse.go @@ -17,44 +17,15 @@ package crypto import ( "context" "errors" + "fmt" "io" "net/http" - "path" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/ioutil" "github.com/minio/sio" ) -const ( - // SSEMultipart is the metadata key indicating that the object - // was uploaded using the S3 multipart API and stored using - // some from of server-side-encryption. - SSEMultipart = "X-Minio-Internal-Encrypted-Multipart" - - // SSEIV is the metadata key referencing the random initialization - // vector (IV) used for SSE-S3 and SSE-C key derivation. - SSEIV = "X-Minio-Internal-Server-Side-Encryption-Iv" - - // SSESealAlgorithm is the metadata key referencing the algorithm - // used by SSE-C and SSE-S3 to encrypt the object. - SSESealAlgorithm = "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm" - - // SSECSealedKey is the metadata key referencing the sealed object-key for SSE-C. - SSECSealedKey = "X-Minio-Internal-Server-Side-Encryption-Sealed-Key" - - // S3SealedKey is the metadata key referencing the sealed object-key for SSE-S3. - S3SealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key" - - // S3KMSKeyID is the metadata key referencing the KMS key-id used to - // generate/decrypt the S3-KMS-Sealed-Key. It is only used for SSE-S3 + KMS. - S3KMSKeyID = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id" - - // S3KMSSealedKey is the metadata key referencing the encrypted key generated - // by KMS. It is only used for SSE-S3 + KMS. - S3KMSSealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key" -) - const ( // SealAlgorithm is the encryption/sealing algorithm used to derive & seal // the key-encryption-key and to en/decrypt the object data. @@ -67,39 +38,34 @@ const ( InsecureSealAlgorithm = "DARE-SHA256" ) -// String returns the SSE domain as string. For SSE-S3 the -// domain is "SSE-S3". -func (s3) String() string { return "SSE-S3" } +// Type represents an AWS SSE type: +// • SSE-C +// • SSE-S3 +// • SSE-KMS +type Type interface { + fmt.Stringer -// UnsealObjectKey extracts and decrypts the sealed object key -// from the metadata using KMS and returns the decrypted object -// key. -func (sse s3) UnsealObjectKey(kms KMS, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { - keyID, kmsKey, sealedKey, err := sse.ParseMetadata(metadata) - if err != nil { - return - } - unsealKey, err := kms.UnsealKey(keyID, kmsKey, Context{bucket: path.Join(bucket, object)}) - if err != nil { - return - } - err = key.Unseal(unsealKey, sealedKey, sse.String(), bucket, object) - return -} + IsRequested(http.Header) bool -// String returns the SSE domain as string. For SSE-C the -// domain is "SSE-C". -func (ssec) String() string { return "SSE-C" } + IsEncrypted(map[string]string) bool +} -// UnsealObjectKey extracts and decrypts the sealed object key -// from the metadata using the SSE-C client key of the HTTP headers -// and returns the decrypted object key. -func (sse ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { - clientKey, err := sse.ParseHTTP(h) - if err != nil { - return +// IsRequested returns true and the SSE Type if the HTTP headers +// indicate that some form server-side encryption is requested. +// +// If no SSE headers are present then IsRequested returns false +// and no Type. +func IsRequested(h http.Header) (Type, bool) { + switch { + case S3.IsRequested(h): + return S3, true + case S3KMS.IsRequested(h): + return S3KMS, true + case SSEC.IsRequested(h): + return SSEC, true + default: + return nil, false } - return unsealObjectKey(clientKey, metadata, bucket, object) } // UnsealObjectKey extracts and decrypts the sealed object key diff --git a/cmd/disk-cache-backend.go b/cmd/disk-cache-backend.go index fc9759e67..ba396bec4 100644 --- a/cmd/disk-cache-backend.go +++ b/cmd/disk-cache-backend.go @@ -962,7 +962,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang } if globalCacheKMS != nil { // clean up internal SSE cache metadata - delete(gr.ObjInfo.UserDefined, crypto.SSEHeader) + delete(gr.ObjInfo.UserDefined, xhttp.AmzServerSideEncryption) } if !rngInfo.Empty() { // overlay Size with actual object size and not the range size diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 27d84a33d..def150262 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -386,13 +386,13 @@ func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, offset, header: h, bucket: bucket, object: object, - customerKeyHeader: h.Get(crypto.SSECKey), + customerKeyHeader: h.Get(xhttp.AmzServerSideEncryptionCustomerKey), copySource: copySource, metadata: cloneMSS(oi.UserDefined), } if w.copySource { - w.customerKeyHeader = h.Get(crypto.SSECopyKey) + w.customerKeyHeader = h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) } if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil { @@ -434,12 +434,12 @@ func (d *DecryptBlocksReader) buildDecrypter(partID int) error { var err error if d.copySource { if crypto.SSEC.IsEncrypted(d.metadata) { - d.header.Set(crypto.SSECopyKey, d.customerKeyHeader) + d.header.Set(xhttp.AmzServerSideEncryptionCopyCustomerKey, d.customerKeyHeader) key, err = ParseSSECopyCustomerRequest(d.header, d.metadata) } } else { if crypto.SSEC.IsEncrypted(d.metadata) { - d.header.Set(crypto.SSECKey, d.customerKeyHeader) + d.header.Set(xhttp.AmzServerSideEncryptionCustomerKey, d.customerKeyHeader) key, err = ParseSSECustomerHeader(d.header) } } diff --git a/cmd/encryption-v1_test.go b/cmd/encryption-v1_test.go index 29cc5b984..7dc7e4227 100644 --- a/cmd/encryption-v1_test.go +++ b/cmd/encryption-v1_test.go @@ -29,6 +29,7 @@ import ( "github.com/klauspost/compress/zstd" "github.com/minio/minio-go/v7/pkg/encrypt" "github.com/minio/minio/cmd/crypto" + xhttp "github.com/minio/minio/cmd/http" "github.com/minio/sio" ) @@ -38,20 +39,20 @@ var encryptRequestTests = []struct { }{ { header: map[string]string{ - crypto.SSECAlgorithm: "AES256", - crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", - crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", + xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256", + xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", + xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==", }, metadata: map[string]string{}, }, { header: map[string]string{ - crypto.SSECAlgorithm: "AES256", - crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", - crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", + xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256", + xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", + xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==", }, metadata: map[string]string{ - crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", + xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", }, }, } @@ -70,13 +71,13 @@ func TestEncryptRequest(t *testing.T) { if err != nil { t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) } - if kdf, ok := test.metadata[crypto.SSESealAlgorithm]; !ok { + if kdf, ok := test.metadata[crypto.MetaAlgorithm]; !ok { t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf) } - if iv, ok := test.metadata[crypto.SSEIV]; !ok { + if iv, ok := test.metadata[crypto.MetaIV]; !ok { t.Errorf("Test %d: crypto.SSEIV must be part of metadata: %v", i, iv) } - if mac, ok := test.metadata[crypto.SSECSealedKey]; !ok { + if mac, ok := test.metadata[crypto.MetaSealedKeySSEC]; !ok { t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac) } } @@ -93,33 +94,33 @@ var decryptObjectInfoTests = []struct { expErr: nil, }, { - info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, - request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, + info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}}, + request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}}, expErr: nil, }, { - info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, - request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, + info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}}, + request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}}, expErr: nil, }, { - info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSECSealedKey: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}}, + info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaSealedKeySSEC: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}}, request: &http.Request{Header: http.Header{}}, expErr: errEncryptedObject, }, { info: ObjectInfo{Size: 100, UserDefined: map[string]string{}}, - request: &http.Request{Method: http.MethodGet, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, + request: &http.Request{Method: http.MethodGet, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}}, expErr: errInvalidEncryptionParameters, }, { info: ObjectInfo{Size: 100, UserDefined: map[string]string{}}, - request: &http.Request{Method: http.MethodHead, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, + request: &http.Request{Method: http.MethodHead, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}}, expErr: errInvalidEncryptionParameters, }, { - info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, - request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, + info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}}, + request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}}, expErr: errObjectTampered, }, } @@ -221,10 +222,10 @@ func TestGetDecryptedRange_Issue50(t *testing.T) { Name: "object", Size: 595160760, UserDefined: map[string]string{ - crypto.SSEMultipart: "", - crypto.SSEIV: "HTexa=", - crypto.SSESealAlgorithm: "DAREv2-HMAC-SHA256", - crypto.SSECSealedKey: "IAA8PGAA==", + crypto.MetaMultipart: "", + crypto.MetaIV: "HTexa=", + crypto.MetaAlgorithm: "DAREv2-HMAC-SHA256", + crypto.MetaSealedKeySSEC: "IAA8PGAA==", ReservedMetadataPrefix + "actual-size": "594870264", "content-type": "application/octet-stream", "etag": "166b1545b4c1535294ee0686678bea8c-2", @@ -276,11 +277,11 @@ func TestGetDecryptedRange(t *testing.T) { } udMap = func(isMulti bool) map[string]string { m := map[string]string{ - crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm, - crypto.SSEMultipart: "1", + crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm, + crypto.MetaMultipart: "1", } if !isMulti { - delete(m, crypto.SSEMultipart) + delete(m, crypto.MetaMultipart) } return m } @@ -553,56 +554,56 @@ var getDefaultOptsTests = []struct { encryptionType encrypt.Type err error }{ - {headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, - crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, + {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, copySource: false, metadata: nil, encryptionType: encrypt.SSEC, err: nil}, // 0 - {headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, - crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, + {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, copySource: true, metadata: nil, encryptionType: "", err: nil}, // 1 - {headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, - crypto.SSECKey: []string{"Mz"}, - crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, + {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{"Mz"}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, copySource: false, metadata: nil, encryptionType: "", err: crypto.ErrInvalidCustomerKey}, // 2 - {headers: http.Header{crypto.SSEHeader: []string{"AES256"}}, + {headers: http.Header{xhttp.AmzServerSideEncryption: []string{"AES256"}}, copySource: false, metadata: nil, encryptionType: encrypt.S3, err: nil}, // 3 {headers: http.Header{}, copySource: false, - metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), - crypto.S3KMSKeyID: "kms-key", - crypto.S3KMSSealedKey: "m-key"}, + metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), + crypto.MetaKeyID: "kms-key", + crypto.MetaDataEncryptionKey: "m-key"}, encryptionType: encrypt.S3, err: nil}, // 4 {headers: http.Header{}, copySource: true, - metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), - crypto.S3KMSKeyID: "kms-key", - crypto.S3KMSSealedKey: "m-key"}, + metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), + crypto.MetaKeyID: "kms-key", + crypto.MetaDataEncryptionKey: "m-key"}, encryptionType: "", err: nil}, // 5 - {headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"}, - crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, + {headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"}, + xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, copySource: true, metadata: nil, encryptionType: encrypt.SSEC, err: nil}, // 6 - {headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"}, - crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, - crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, + {headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"}, + xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, + xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, copySource: false, metadata: nil, encryptionType: "", diff --git a/cmd/generic-handlers_test.go b/cmd/generic-handlers_test.go index e604e0bbc..9ca19d857 100644 --- a/cmd/generic-handlers_test.go +++ b/cmd/generic-handlers_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/minio/minio/cmd/crypto" + xhttp "github.com/minio/minio/cmd/http" ) // Tests getRedirectLocation function for all its criteria. @@ -181,15 +182,15 @@ var containsReservedMetadataTests = []struct { header: http.Header{"X-Minio-Key": []string{"value"}}, }, { - header: http.Header{crypto.SSEIV: []string{"iv"}}, + header: http.Header{crypto.MetaIV: []string{"iv"}}, shouldFail: true, }, { - header: http.Header{crypto.SSESealAlgorithm: []string{crypto.InsecureSealAlgorithm}}, + header: http.Header{crypto.MetaAlgorithm: []string{crypto.InsecureSealAlgorithm}}, shouldFail: true, }, { - header: http.Header{crypto.SSECSealedKey: []string{"mac"}}, + header: http.Header{crypto.MetaSealedKeySSEC: []string{"mac"}}, shouldFail: true, }, { @@ -217,11 +218,11 @@ var sseTLSHandlerTests = []struct { Header http.Header IsTLS, ShouldFail bool }{ - {URL: &url.URL{}, Header: http.Header{}, IsTLS: false, ShouldFail: false}, // 0 - {URL: &url.URL{}, Header: http.Header{crypto.SSECAlgorithm: []string{"AES256"}}, IsTLS: false, ShouldFail: true}, // 1 - {URL: &url.URL{}, Header: http.Header{crypto.SSECAlgorithm: []string{"AES256"}}, IsTLS: true, ShouldFail: false}, // 2 - {URL: &url.URL{}, Header: http.Header{crypto.SSECKey: []string{""}}, IsTLS: true, ShouldFail: false}, // 3 - {URL: &url.URL{}, Header: http.Header{crypto.SSECopyAlgorithm: []string{""}}, IsTLS: false, ShouldFail: true}, // 4 + {URL: &url.URL{}, Header: http.Header{}, IsTLS: false, ShouldFail: false}, // 0 + {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}}, IsTLS: false, ShouldFail: true}, // 1 + {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"}}, IsTLS: true, ShouldFail: false}, // 2 + {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerKey: []string{""}}, IsTLS: true, ShouldFail: false}, // 3 + {URL: &url.URL{}, Header: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{""}}, IsTLS: false, ShouldFail: true}, // 4 } func TestSSETLSHandler(t *testing.T) { diff --git a/cmd/http/headers.go b/cmd/http/headers.go index 5c4cea2ef..156c58fae 100644 --- a/cmd/http/headers.go +++ b/cmd/http/headers.go @@ -111,6 +111,20 @@ const ( AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length" AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5" + // AWS server-side encryption headers for SSE-S3, SSE-KMS and SSE-C. + AmzServerSideEncryption = "X-Amz-Server-Side-Encryption" + AmzServerSideEncryptionKmsID = AmzServerSideEncryption + "-Aws-Kms-Key-Id" + AmzServerSideEncryptionKmsContext = AmzServerSideEncryption + "-Context" + AmzServerSideEncryptionCustomerAlgorithm = AmzServerSideEncryption + "-Customer-Algorithm" + AmzServerSideEncryptionCustomerKey = AmzServerSideEncryption + "-Customer-Key" + AmzServerSideEncryptionCustomerKeyMD5 = AmzServerSideEncryption + "-Customer-Key-Md5" + AmzServerSideEncryptionCopyCustomerAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm" + AmzServerSideEncryptionCopyCustomerKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key" + AmzServerSideEncryptionCopyCustomerKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5" + + AmzEncryptionAES = "AES256" + AmzEncryptionKMS = "aws:kms" + // Signature v2 related constants AmzSignatureV2 = "Signature" AmzAccessKeyID = "AWSAccessKeyId" diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go index 33ef61add..5108c44b5 100644 --- a/cmd/object-api-utils.go +++ b/cmd/object-api-utils.go @@ -439,7 +439,9 @@ func isCompressible(header http.Header, object string) bool { globalCompressConfigMu.Lock() cfg := globalCompressConfig globalCompressConfigMu.Unlock() - if !cfg.Enabled || crypto.IsRequested(header) || excludeForCompression(header, object, cfg) { + + _, ok := crypto.IsRequested(header) + if !cfg.Enabled || ok || excludeForCompression(header, object, cfg) { return false } return true @@ -628,7 +630,7 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, opts ObjectOptions, cl // encrypted bytes. The header parameter is used to // provide encryption parameters. fn = func(inputReader io.Reader, h http.Header, pcfn CheckPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) { - copySource := h.Get(crypto.SSECopyAlgorithm) != "" + copySource := h.Get(xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) != "" cFns = append(cleanUpFns, cFns...) // Attach decrypter on inputReader diff --git a/cmd/object-api-utils_test.go b/cmd/object-api-utils_test.go index bbb7fe582..781288429 100644 --- a/cmd/object-api-utils_test.go +++ b/cmd/object-api-utils_test.go @@ -347,7 +347,7 @@ func TestIsCompressed(t *testing.T) { UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV2, "content-type": "application/octet-stream", "etag": "b3ff3ef3789147152fbfbc50efba4bfd-2", - crypto.SSEIV: "yes", + crypto.MetaIV: "yes", }, }, result: true, diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 20035394e..d30cdccff 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -108,7 +108,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); ok && !objectAPI.IsEncryptionSupported() { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) return } @@ -267,15 +267,15 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r if crypto.IsEncrypted(objInfo.UserDefined) { switch { case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) case crypto.SSEC.IsEncrypted(objInfo.UserDefined): // Validate the SSE-C Key set in the header. if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } - w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) - w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } } @@ -313,7 +313,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) return } @@ -446,10 +446,10 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req if crypto.IsEncrypted(objInfo.UserDefined) { switch { case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) case crypto.SSEC.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) - w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } } @@ -518,7 +518,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrBadRequest)) return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) return } @@ -640,15 +640,15 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re if crypto.IsEncrypted(objInfo.UserDefined) { switch { case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) case crypto.SSEC.IsEncrypted(objInfo.UserDefined): // Validate the SSE-C Key set in the header. if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) return } - w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) - w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } } @@ -811,7 +811,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) return } @@ -884,7 +884,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(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) } var srcOpts, dstOpts ObjectOptions @@ -1030,7 +1030,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re pReader := NewPutObjReader(srcInfo.Reader, nil, nil) // Check if either the source is encrypted or the destination will be encrypted. - objectEncryption := crypto.IsSourceEncrypted(srcInfo.UserDefined) || crypto.IsRequested(r.Header) + _, objectEncryption := crypto.IsRequested(r.Header) + objectEncryption = objectEncryption || crypto.IsSourceEncrypted(srcInfo.UserDefined) var encMetadata = make(map[string]string) if objectAPI.IsEncryptionSupported() && !isCompressed { // Encryption parameters not applicable for this object. @@ -1321,7 +1322,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) return } @@ -1451,7 +1452,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(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) } actualSize := size @@ -1528,7 +1529,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req } var objectEncryptionKey crypto.ObjectKey if objectAPI.IsEncryptionSupported() { - if crypto.IsRequested(r.Header) && !HasSuffix(object, SlashSeparator) { // handle SSE requests + if _, ok := crypto.IsRequested(r.Header); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests if crypto.SSECopy.IsRequested(r.Header) { writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) return @@ -1569,11 +1570,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req case crypto.IsEncrypted(objInfo.UserDefined): switch { case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) objInfo.ETag, _ = DecryptETag(objectEncryptionKey, ObjectInfo{ETag: objInfo.ETag}) case crypto.SSEC.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) - w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) if len(objInfo.ETag) >= 32 && strings.Count(objInfo.ETag, "-") != 1 { objInfo.ETag = objInfo.ETag[len(objInfo.ETag)-32:] @@ -1623,7 +1624,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) return } @@ -1645,7 +1646,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(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) } // Validate storage class metadata if present @@ -1659,7 +1660,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r var encMetadata = map[string]string{} if objectAPI.IsEncryptionSupported() { - if crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); ok { if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return @@ -1751,7 +1752,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) return } @@ -2067,7 +2068,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http return } - if !objectAPI.IsEncryptionSupported() && crypto.IsRequested(r.Header) { + if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) return } diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go index d14436c98..0483906e3 100644 --- a/cmd/object-handlers_test.go +++ b/cmd/object-handlers_test.go @@ -37,7 +37,6 @@ import ( "testing" humanize "github.com/dustin/go-humanize" - "github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/pkg/auth" ioutilx "github.com/minio/minio/pkg/ioutil" @@ -221,9 +220,9 @@ func testAPIHeadObjectHandlerWithEncryption(obj ObjectLayer, instanceType, bucke key32Bytes = generateBytesData(32 * humanize.Byte) key32BytesMd5 = md5.Sum(key32Bytes) metaWithSSEC = map[string]string{ - crypto.SSECAlgorithm: crypto.SSEAlgorithmAES256, - crypto.SSECKey: base64.StdEncoding.EncodeToString(key32Bytes), - crypto.SSECKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), + xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES, + xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(key32Bytes), + xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), } mapCopy = func(m map[string]string) map[string]string { r := make(map[string]string, len(m)) @@ -667,9 +666,9 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str key32Bytes = generateBytesData(32 * humanize.Byte) key32BytesMd5 = md5.Sum(key32Bytes) metaWithSSEC = map[string]string{ - crypto.SSECAlgorithm: crypto.SSEAlgorithmAES256, - crypto.SSECKey: base64.StdEncoding.EncodeToString(key32Bytes), - crypto.SSECKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), + xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES, + xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(key32Bytes), + xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), } mapCopy = func(m map[string]string) map[string]string { r := make(map[string]string, len(m)) @@ -865,9 +864,9 @@ func testAPIGetObjectWithPartNumberHandler(obj ObjectLayer, instanceType, bucket key32Bytes = generateBytesData(32 * humanize.Byte) key32BytesMd5 = md5.Sum(key32Bytes) metaWithSSEC = map[string]string{ - crypto.SSECAlgorithm: crypto.SSEAlgorithmAES256, - crypto.SSECKey: base64.StdEncoding.EncodeToString(key32Bytes), - crypto.SSECKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), + xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES, + xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(key32Bytes), + xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), } mapCopy = func(m map[string]string) map[string]string { r := make(map[string]string, len(m)) diff --git a/cmd/signature-v4-utils_test.go b/cmd/signature-v4-utils_test.go index 493f01d91..01c048054 100644 --- a/cmd/signature-v4-utils_test.go +++ b/cmd/signature-v4-utils_test.go @@ -20,7 +20,7 @@ import ( "net/http" "testing" - "github.com/minio/minio/cmd/crypto" + xhttp "github.com/minio/minio/cmd/http" ) // TestSkipContentSha256Cksum - Test validate the logic which decides whether @@ -160,7 +160,7 @@ func TestExtractSignedHeaders(t *testing.T) { t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode) } // set headers value through Get parameter - inputQuery.Add("x-amz-server-side-encryption", crypto.SSEAlgorithmAES256) + inputQuery.Add("x-amz-server-side-encryption", xhttp.AmzEncryptionAES) r.URL.RawQuery = inputQuery.Encode() _, errCode = extractSignedHeaders(signedHeaders, r) if errCode != ErrNone { diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 971666ff1..16a3d385f 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -1171,7 +1171,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(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) } // Require Content-Length to be set in the request @@ -1239,7 +1239,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { } if objectAPI.IsEncryptionSupported() { - if crypto.IsRequested(r.Header) && !HasSuffix(object, SlashSeparator) { // handle SSE requests + if _, ok := crypto.IsRequested(r.Header); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests rawReader := hashReader var objectEncryptionKey crypto.ObjectKey reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata) @@ -1288,10 +1288,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { if crypto.IsEncrypted(objInfo.UserDefined) { switch { case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) case crypto.SSEC.IsRequested(r.Header): - w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) - w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } } @@ -1447,10 +1447,10 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { if crypto.IsEncrypted(objInfo.UserDefined) { switch { case crypto.S3.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) + w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) case crypto.SSEC.IsEncrypted(objInfo.UserDefined): - w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) - w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) + w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) } } }