refactor cmd/crypto code for SSE handling and parsing (#11045)

This commit refactors the code in `cmd/crypto`
and separates SSE-S3, SSE-C and SSE-KMS.

This commit should not cause any behavior change
except for:
  - `IsRequested(http.Header)`

which now returns the requested type {SSE-C, SSE-S3,
SSE-KMS} and does not consider SSE-C copy headers.

However, SSE-C copy headers alone are anyway not valid.
This commit is contained in:
Andreas Auernhammer 2020-12-22 18:19:32 +01:00 committed by GitHub
parent 35fafb837b
commit 8cdf2106b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 861 additions and 646 deletions

View File

@ -748,7 +748,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@ -895,7 +895,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption { if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption {
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if !crypto.SSEC.IsRequested(r.Header) { 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 return
} }
if objectAPI.IsEncryptionSupported() { 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) { if crypto.SSECopy.IsRequested(r.Header) {
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
return return

View File

@ -28,7 +28,6 @@ import (
miniogo "github.com/minio/minio-go/v7" miniogo "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
sse "github.com/minio/minio/pkg/bucket/encryption" 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 == "" { if r.OutputLocation.S3.Prefix == "" {
return fmt.Errorf("Prefix is a required parameter in OutputLocation") 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{} return NotImplemented{}
} }
} }
@ -573,7 +572,7 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
} }
meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String() meta[xhttp.AmzObjectTagging] = rreq.OutputLocation.S3.Tagging.String()
if rreq.OutputLocation.S3.Encryption.EncryptionType != "" { if rreq.OutputLocation.S3.Encryption.EncryptionType != "" {
meta[crypto.SSEHeader] = crypto.SSEAlgorithmAES256 meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
} }
return ObjectOptions{ return ObjectOptions{
Versioned: globalBucketVersioningSys.Enabled(bucket), Versioned: globalBucketVersioningSys.Enabled(bucket),

View File

@ -19,231 +19,62 @@ import (
"crypto/md5" "crypto/md5"
"encoding/base64" "encoding/base64"
"net/http" "net/http"
"strings"
jsoniter "github.com/json-iterator/go"
xhttp "github.com/minio/minio/cmd/http" 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 // RemoveSensitiveHeaders removes confidential encryption
// information - e.g. the SSE-C key - from the HTTP headers. // information - e.g. the SSE-C key - from the HTTP headers.
// It has the same semantics as RemoveSensitiveEntires. // It has the same semantics as RemoveSensitiveEntires.
func RemoveSensitiveHeaders(h http.Header) { func RemoveSensitiveHeaders(h http.Header) {
h.Del(SSECKey) h.Del(xhttp.AmzServerSideEncryptionCustomerKey)
h.Del(SSECopyKey) h.Del(xhttp.AmzServerSideEncryptionCopyCustomerKey)
h.Del(xhttp.AmzMetaUnencryptedContentLength) h.Del(xhttp.AmzMetaUnencryptedContentLength)
h.Del(xhttp.AmzMetaUnencryptedContentMD5) 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 ( 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 // SSECopy represents AWS SSE-C for copy requests. It provides
// functionality to handle SSE-C copy requests. // functionality to handle SSE-C copy requests.
SSECopy = ssecCopy{} SSECopy = ssecCopy{}
) )
type ssec struct{}
type ssecCopy 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 // IsRequested returns true if the HTTP headers contains
// at least one SSE-C copy header. Regular SSE-C headers // at least one SSE-C copy header. Regular SSE-C headers
// are ignored. // are ignored.
func (ssecCopy) IsRequested(h http.Header) bool { func (ssecCopy) IsRequested(h http.Header) bool {
if _, ok := h[SSECopyAlgorithm]; ok { if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm]; ok {
return true return true
} }
if _, ok := h[SSECopyKey]; ok { if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKey]; ok {
return true return true
} }
if _, ok := h[SSECopyKeyMD5]; ok { if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5]; ok {
return true return true
} }
return false 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 // ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
// on success. Regular SSE-C headers are ignored. // on success. Regular SSE-C headers are ignored.
func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) { 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 return key, ErrInvalidCustomerAlgorithm
} }
if h.Get(SSECopyKey) == "" { if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) == "" {
return key, ErrMissingCustomerKey return key, ErrMissingCustomerKey
} }
if h.Get(SSECopyKeyMD5) == "" { if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) == "" {
return key, ErrMissingCustomerKeyMD5 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 if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
return key, ErrInvalidCustomerKey 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) { if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
return key, ErrCustomerKeyMD5Mismatch return key, ErrCustomerKeyMD5Mismatch
} }

View File

@ -18,26 +18,29 @@ import (
"net/http" "net/http"
"sort" "sort"
"testing" "testing"
xhttp "github.com/minio/minio/cmd/http"
) )
func TestIsRequested(t *testing.T) { func TestIsRequested(t *testing.T) {
for i, test := range kmsIsRequestedTests { 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) t.Errorf("SSE-KMS: Test %d: Wanted %v but got %v", i, test.Expected, got)
} }
} }
for i, test := range s3IsRequestedTests { 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) t.Errorf("SSE-S3: Test %d: Wanted %v but got %v", i, test.Expected, got)
} }
} }
for i, test := range ssecIsRequestedTests { for i, test := range ssecIsRequestedTests {
if got := IsRequested(test.Header) && SSEC.IsRequested(test.Header); got != test.Expected { _, got := IsRequested(test.Header)
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got) got = got && SSEC.IsRequested(test.Header)
} if got != test.Expected {
}
for i, test := range ssecCopyIsRequestedTests {
if got := IsRequested(test.Header) && SSECopy.IsRequested(test.Header); got != test.Expected {
t.Errorf("SSE-C: Test %d: Wanted %v but got %v", i, test.Expected, got) 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 Header http.Header
Expected bool Expected bool
}{ }{
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"AES256"}}, Expected: true}, // 0 {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{"AES-256"}}, Expected: true}, // 1
{Header: http.Header{"X-Amz-Server-Side-Encryption": []string{""}}, Expected: true}, // 2 {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-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{xhttp.AmzEncryptionKMS}}, Expected: false}, // 4
} }
func TestS3IsRequested(t *testing.T) { func TestS3IsRequested(t *testing.T) {
@ -403,7 +406,7 @@ func TestSSECopyParse(t *testing.T) {
if err == nil && key == zeroKey { if err == nil && key == zeroKey {
t.Errorf("Test %d: parsed client key is zero key", i) 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) 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{ Header: http.Header{
SSECKey: []string{""}, xhttp.AmzServerSideEncryptionCustomerKey: []string{""},
SSECopyKey: []string{""}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{""},
}, },
ExpectedHeader: http.Header{}, ExpectedHeader: http.Header{},
}, },
{ // Standard SSE-C request headers { // Standard SSE-C request headers
Header: http.Header{ Header: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
ExpectedHeader: http.Header{ ExpectedHeader: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
}, },
{ // Standard SSE-C + SSE-C-copy request headers { // Standard SSE-C + SSE-C-copy request headers
Header: http.Header{ Header: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
ExpectedHeader: http.Header{ ExpectedHeader: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
}, },
}, },
{ // Standard SSE-C + metadata request headers { // Standard SSE-C + metadata request headers
Header: http.Header{ Header: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
"X-Amz-Meta-Test-1": []string{"Test-1"}, "X-Amz-Meta-Test-1": []string{"Test-1"},
}, },
ExpectedHeader: http.Header{ ExpectedHeader: http.Header{
SSECAlgorithm: []string{SSEAlgorithmAES256}, xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES},
SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="},
"X-Amz-Meta-Test-1": []string{"Test-1"}, "X-Amz-Meta-Test-1": []string{"Test-1"},
}, },
}, },
{ // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w { // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w

View File

@ -15,19 +15,42 @@
package crypto package crypto
import ( import (
"context"
"encoding/base64"
"errors"
xhttp "github.com/minio/minio/cmd/http" 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 // IsMultiPart returns true if the object metadata indicates
// that it was uploaded using some form of server-side-encryption // that it was uploaded using some form of server-side-encryption
// and the S3 multipart API. // and the S3 multipart API.
func IsMultiPart(metadata map[string]string) bool { func IsMultiPart(metadata map[string]string) bool {
if _, ok := metadata[SSEMultipart]; ok { if _, ok := metadata[MetaMultipart]; ok {
return true return true
} }
return false return false
@ -37,8 +60,8 @@ func IsMultiPart(metadata map[string]string) bool {
// information - e.g. the SSE-C key - from the metadata map. // information - e.g. the SSE-C key - from the metadata map.
// It has the same semantics as RemoveSensitiveHeaders. // It has the same semantics as RemoveSensitiveHeaders.
func RemoveSensitiveEntries(metadata map[string]string) { // The functions is tested in TestRemoveSensitiveHeaders for compatibility reasons func RemoveSensitiveEntries(metadata map[string]string) { // The functions is tested in TestRemoveSensitiveHeaders for compatibility reasons
delete(metadata, SSECKey) delete(metadata, xhttp.AmzServerSideEncryptionCustomerKey)
delete(metadata, SSECopyKey) delete(metadata, xhttp.AmzServerSideEncryptionCopyCustomerKey)
delete(metadata, xhttp.AmzMetaUnencryptedContentLength) delete(metadata, xhttp.AmzMetaUnencryptedContentLength)
delete(metadata, xhttp.AmzMetaUnencryptedContentMD5) delete(metadata, xhttp.AmzMetaUnencryptedContentMD5)
} }
@ -46,31 +69,36 @@ func RemoveSensitiveEntries(metadata map[string]string) { // The functions is te
// RemoveSSEHeaders removes all crypto-specific SSE // RemoveSSEHeaders removes all crypto-specific SSE
// header entries from the metadata map. // header entries from the metadata map.
func RemoveSSEHeaders(metadata map[string]string) { func RemoveSSEHeaders(metadata map[string]string) {
delete(metadata, SSEHeader) delete(metadata, xhttp.AmzServerSideEncryption)
delete(metadata, SSEKmsID) delete(metadata, xhttp.AmzServerSideEncryptionKmsID)
delete(metadata, SSEKmsContext) delete(metadata, xhttp.AmzServerSideEncryptionKmsContext)
delete(metadata, SSECKeyMD5) delete(metadata, xhttp.AmzServerSideEncryptionCustomerAlgorithm)
delete(metadata, SSECAlgorithm) 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 // RemoveInternalEntries removes all crypto-specific internal
// metadata entries from the metadata map. // metadata entries from the metadata map.
func RemoveInternalEntries(metadata map[string]string) { func RemoveInternalEntries(metadata map[string]string) {
delete(metadata, SSEMultipart) delete(metadata, MetaMultipart)
delete(metadata, SSEIV) delete(metadata, MetaAlgorithm)
delete(metadata, SSESealAlgorithm) delete(metadata, MetaIV)
delete(metadata, SSECSealedKey) delete(metadata, MetaSealedKeySSEC)
delete(metadata, S3SealedKey) delete(metadata, MetaSealedKeyS3)
delete(metadata, S3KMSKeyID) delete(metadata, MetaSealedKeyKMS)
delete(metadata, S3KMSSealedKey) delete(metadata, MetaKeyID)
delete(metadata, MetaDataEncryptionKey)
} }
// IsSourceEncrypted returns true if the source is encrypted // IsSourceEncrypted returns true if the source is encrypted
func IsSourceEncrypted(metadata map[string]string) bool { func IsSourceEncrypted(metadata map[string]string) bool {
if _, ok := metadata[SSECAlgorithm]; ok { if _, ok := metadata[xhttp.AmzServerSideEncryptionCustomerAlgorithm]; ok {
return true return true
} }
if _, ok := metadata[SSEHeader]; ok { if _, ok := metadata[xhttp.AmzServerSideEncryption]; ok {
return true return true
} }
return false return false
@ -82,10 +110,10 @@ func IsSourceEncrypted(metadata map[string]string) bool {
// IsEncrypted only checks whether the metadata contains at least // IsEncrypted only checks whether the metadata contains at least
// one entry indicating SSE-C or SSE-S3. // one entry indicating SSE-C or SSE-S3.
func IsEncrypted(metadata map[string]string) bool { func IsEncrypted(metadata map[string]string) bool {
if _, ok := metadata[SSEIV]; ok { if _, ok := metadata[MetaIV]; ok {
return true return true
} }
if _, ok := metadata[SSESealAlgorithm]; ok { if _, ok := metadata[MetaAlgorithm]; ok {
return true return true
} }
if IsMultiPart(metadata) { if IsMultiPart(metadata) {
@ -97,28 +125,7 @@ func IsEncrypted(metadata map[string]string) bool {
if SSEC.IsEncrypted(metadata) { if SSEC.IsEncrypted(metadata) {
return true return true
} }
return false if S3KMS.IsEncrypted(metadata) {
}
// 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 {
return true return true
} }
return false return false
@ -129,159 +136,11 @@ func (ssec) IsEncrypted(metadata map[string]string) bool {
// metadata is nil. // metadata is nil.
func CreateMultipartMetadata(metadata map[string]string) map[string]string { func CreateMultipartMetadata(metadata map[string]string) map[string]string {
if metadata == nil { if metadata == nil {
return map[string]string{SSEMultipart: ""} return map[string]string{MetaMultipart: ""}
} }
metadata[SSEMultipart] = "" metadata[MetaMultipart] = ""
return metadata 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)
}
metadata[SSESealAlgorithm] = SealAlgorithm
metadata[SSEIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
metadata[SSECSealedKey] = 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[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. // IsETagSealed returns true if the etag seems to be encrypted.
func IsETagSealed(etag []byte) bool { return len(etag) > 16 } func IsETagSealed(etag []byte) bool { return len(etag) > 16 }

View File

@ -27,9 +27,9 @@ var isMultipartTests = []struct {
Metadata map[string]string Metadata map[string]string
Multipart bool 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{"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{"": ""}}, // 3
{Multipart: false, Metadata: map[string]string{"X-Minio-Internal-EncryptedMultipart": ""}}, // 4 {Multipart: false, Metadata: map[string]string{"X-Minio-Internal-EncryptedMultipart": ""}}, // 4
} }
@ -46,13 +46,13 @@ var isEncryptedTests = []struct {
Metadata map[string]string Metadata map[string]string
Encrypted bool Encrypted bool
}{ }{
{Encrypted: true, Metadata: map[string]string{SSEMultipart: ""}}, // 0 {Encrypted: true, Metadata: map[string]string{MetaMultipart: ""}}, // 0
{Encrypted: true, Metadata: map[string]string{SSEIV: ""}}, // 1 {Encrypted: true, Metadata: map[string]string{MetaIV: ""}}, // 1
{Encrypted: true, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 {Encrypted: true, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
{Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 {Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
{Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4 {Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
{Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 {Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5
{Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 {Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
} }
@ -69,13 +69,13 @@ var s3IsEncryptedTests = []struct {
Metadata map[string]string Metadata map[string]string
Encrypted bool Encrypted bool
}{ }{
{Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0 {Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0
{Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1 {Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1
{Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 {Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
{Encrypted: false, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 {Encrypted: false, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
{Encrypted: true, Metadata: map[string]string{S3SealedKey: ""}}, // 4 {Encrypted: true, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
{Encrypted: true, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 {Encrypted: true, Metadata: map[string]string{MetaKeyID: ""}}, // 5
{Encrypted: true, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 {Encrypted: true, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 {Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8
} }
@ -92,13 +92,13 @@ var ssecIsEncryptedTests = []struct {
Metadata map[string]string Metadata map[string]string
Encrypted bool Encrypted bool
}{ }{
{Encrypted: false, Metadata: map[string]string{SSEMultipart: ""}}, // 0 {Encrypted: false, Metadata: map[string]string{MetaMultipart: ""}}, // 0
{Encrypted: false, Metadata: map[string]string{SSEIV: ""}}, // 1 {Encrypted: false, Metadata: map[string]string{MetaIV: ""}}, // 1
{Encrypted: false, Metadata: map[string]string{SSESealAlgorithm: ""}}, // 2 {Encrypted: false, Metadata: map[string]string{MetaAlgorithm: ""}}, // 2
{Encrypted: true, Metadata: map[string]string{SSECSealedKey: ""}}, // 3 {Encrypted: true, Metadata: map[string]string{MetaSealedKeySSEC: ""}}, // 3
{Encrypted: false, Metadata: map[string]string{S3SealedKey: ""}}, // 4 {Encrypted: false, Metadata: map[string]string{MetaSealedKeyS3: ""}}, // 4
{Encrypted: false, Metadata: map[string]string{S3KMSKeyID: ""}}, // 5 {Encrypted: false, Metadata: map[string]string{MetaKeyID: ""}}, // 5
{Encrypted: false, Metadata: map[string]string{S3KMSSealedKey: ""}}, // 6 {Encrypted: false, Metadata: map[string]string{MetaDataEncryptionKey: ""}}, // 6
{Encrypted: false, Metadata: map[string]string{"": ""}}, // 7 {Encrypted: false, Metadata: map[string]string{"": ""}}, // 7
{Encrypted: false, Metadata: map[string]string{"X-Minio-Internal-Server-Side-Encryption": ""}}, // 8 {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: 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{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 1 }, // 1
{ {
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-S3"), 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 }, // 2
{ {
ExpectedErr: Errorf("The object metadata is missing the internal KMS key-ID for SSE-S3"), 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 }, // 3
{ {
ExpectedErr: Errorf("The object metadata is missing the internal sealed KMS data key for SSE-S3"), 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{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 4 }, // 4
{ {
ExpectedErr: errInvalidInternalIV, 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{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 5 }, // 5
{ {
ExpectedErr: errInvalidInternalSealAlgorithm, ExpectedErr: errInvalidInternalSealAlgorithm,
Metadata: map[string]string{ 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{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 6 }, // 6
{ {
ExpectedErr: Errorf("The internal sealed key for SSE-S3 is invalid"), ExpectedErr: Errorf("The internal sealed key for SSE-S3 is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, S3SealedKey: "", MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm, MetaSealedKeyS3: "",
S3KMSKeyID: "", S3KMSSealedKey: "", MetaKeyID: "", MetaDataEncryptionKey: "",
}, },
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{},
}, // 7 }, // 7
{ {
ExpectedErr: Errorf("The internal sealed KMS data key for SSE-S3 is invalid"), ExpectedErr: Errorf("The internal sealed KMS data key for SSE-S3 is invalid"),
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "key-1", MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "key-1",
S3KMSSealedKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64 MetaDataEncryptionKey: ".MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=", // invalid base64
}, },
DataKey: []byte{}, KeyID: "key-1", SealedKey: SealedKey{}, DataKey: []byte{}, KeyID: "key-1", SealedKey: SealedKey{},
}, // 8 }, // 8
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), S3KMSKeyID: "", S3KMSSealedKey: "", MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaKeyID: "", MetaDataEncryptionKey: "",
}, },
DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{Algorithm: SealAlgorithm}, DataKey: []byte{}, KeyID: "", SealedKey: SealedKey{Algorithm: SealAlgorithm},
}, // 9 }, // 9
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: SealAlgorithm,
S3SealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), S3KMSKeyID: "key-1", MetaSealedKeyS3: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), MetaKeyID: "key-1",
S3KMSSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 48)), 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}}, DataKey: make([]byte, 48), KeyID: "key-1", SealedKey: SealedKey{Algorithm: SealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
}, // 10 }, // 10
@ -223,43 +223,43 @@ var ssecParseMetadataTests = []struct {
SealedKey SealedKey SealedKey SealedKey
}{ }{
{ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0 {ExpectedErr: errMissingInternalIV, Metadata: map[string]string{}, SealedKey: SealedKey{}}, // 0
{ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{SSEIV: ""}, SealedKey: SealedKey{}}, // 1 {ExpectedErr: errMissingInternalSealAlgorithm, Metadata: map[string]string{MetaIV: ""}, SealedKey: SealedKey{}}, // 1
{ {
ExpectedErr: Errorf("The object metadata is missing the internal sealed key for SSE-C"), 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 }, // 2
{ {
ExpectedErr: errInvalidInternalIV, ExpectedErr: errInvalidInternalIV,
Metadata: map[string]string{SSEIV: "", SSESealAlgorithm: "", SSECSealedKey: ""}, SealedKey: SealedKey{}, Metadata: map[string]string{MetaIV: "", MetaAlgorithm: "", MetaSealedKeySSEC: ""}, SealedKey: SealedKey{},
}, // 3 }, // 3
{ {
ExpectedErr: errInvalidInternalSealAlgorithm, ExpectedErr: errInvalidInternalSealAlgorithm,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: "", SSECSealedKey: "", MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: "", MetaSealedKeySSEC: "",
}, },
SealedKey: SealedKey{}, SealedKey: SealedKey{},
}, // 4 }, // 4
{ {
ExpectedErr: Errorf("The internal sealed key for SSE-C is invalid"), ExpectedErr: Errorf("The internal sealed key for SSE-C is invalid"),
Metadata: map[string]string{ 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{}, SealedKey: SealedKey{},
}, // 5 }, // 5
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), SSESealAlgorithm: SealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(make([]byte, 32)), MetaAlgorithm: SealAlgorithm,
SSECSealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(make([]byte, 64)),
}, },
SealedKey: SealedKey{Algorithm: SealAlgorithm}, SealedKey: SealedKey{Algorithm: SealAlgorithm},
}, // 6 }, // 6
{ {
ExpectedErr: nil, ExpectedErr: nil,
Metadata: map[string]string{ Metadata: map[string]string{
SSEIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), SSESealAlgorithm: InsecureSealAlgorithm, MetaIV: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 31)...)), MetaAlgorithm: InsecureSealAlgorithm,
SSECSealedKey: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)), MetaSealedKeySSEC: base64.StdEncoding.EncodeToString(append([]byte{1}, make([]byte, 63)...)),
}, },
SealedKey: SealedKey{Algorithm: InsecureSealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}}, SealedKey: SealedKey{Algorithm: InsecureSealAlgorithm, Key: [64]byte{1}, IV: [32]byte{1}},
}, // 7 }, // 7
@ -267,8 +267,8 @@ var ssecParseMetadataTests = []struct {
func TestCreateMultipartMetadata(t *testing.T) { func TestCreateMultipartMetadata(t *testing.T) {
metadata := CreateMultipartMetadata(nil) metadata := CreateMultipartMetadata(nil)
if v, ok := metadata[SSEMultipart]; !ok || v != "" { if v, ok := metadata[MetaMultipart]; !ok || v != "" {
t.Errorf("Metadata is missing the correct value for '%s': got '%s' - want '%s'", SSEMultipart, 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 { // 0
Metadata: map[string]string{ Metadata: map[string]string{
SSEMultipart: "", MetaMultipart: "",
SSEIV: "", MetaIV: "",
SSESealAlgorithm: "", MetaAlgorithm: "",
SSECSealedKey: "", MetaSealedKeySSEC: "",
S3SealedKey: "", MetaSealedKeyS3: "",
S3KMSKeyID: "", MetaKeyID: "",
S3KMSSealedKey: "", MetaDataEncryptionKey: "",
}, },
Expected: map[string]string{}, Expected: map[string]string{},
}, },
{ // 1 { // 1
Metadata: map[string]string{ Metadata: map[string]string{
SSEMultipart: "", MetaMultipart: "",
SSEIV: "", MetaIV: "",
"X-Amz-Meta-A": "X", "X-Amz-Meta-A": "X",
"X-Minio-Internal-B": "Y", "X-Minio-Internal-B": "Y",
}, },

157
cmd/crypto/sse-c.go Normal file
View File

@ -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
}

201
cmd/crypto/sse-kms.go Normal file
View File

@ -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
}

181
cmd/crypto/sse-s3.go Normal file
View File

@ -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
}

View File

@ -17,44 +17,15 @@ package crypto
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"path"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/ioutil" "github.com/minio/minio/pkg/ioutil"
"github.com/minio/sio" "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 ( const (
// SealAlgorithm is the encryption/sealing algorithm used to derive & seal // SealAlgorithm is the encryption/sealing algorithm used to derive & seal
// the key-encryption-key and to en/decrypt the object data. // the key-encryption-key and to en/decrypt the object data.
@ -67,39 +38,34 @@ const (
InsecureSealAlgorithm = "DARE-SHA256" InsecureSealAlgorithm = "DARE-SHA256"
) )
// String returns the SSE domain as string. For SSE-S3 the // Type represents an AWS SSE type:
// domain is "SSE-S3". // • SSE-C
func (s3) String() string { return "SSE-S3" } // • SSE-S3
// • SSE-KMS
type Type interface {
fmt.Stringer
// UnsealObjectKey extracts and decrypts the sealed object key IsRequested(http.Header) bool
// from the metadata using KMS and returns the decrypted object
// key. IsEncrypted(map[string]string) bool
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
} }
// String returns the SSE domain as string. For SSE-C the // IsRequested returns true and the SSE Type if the HTTP headers
// domain is "SSE-C". // indicate that some form server-side encryption is requested.
func (ssec) String() string { return "SSE-C" } //
// If no SSE headers are present then IsRequested returns false
// UnsealObjectKey extracts and decrypts the sealed object key // and no Type.
// from the metadata using the SSE-C client key of the HTTP headers func IsRequested(h http.Header) (Type, bool) {
// and returns the decrypted object key. switch {
func (sse ssec) UnsealObjectKey(h http.Header, metadata map[string]string, bucket, object string) (key ObjectKey, err error) { case S3.IsRequested(h):
clientKey, err := sse.ParseHTTP(h) return S3, true
if err != nil { case S3KMS.IsRequested(h):
return 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 // UnsealObjectKey extracts and decrypts the sealed object key

View File

@ -962,7 +962,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
} }
if globalCacheKMS != nil { if globalCacheKMS != nil {
// clean up internal SSE cache metadata // clean up internal SSE cache metadata
delete(gr.ObjInfo.UserDefined, crypto.SSEHeader) delete(gr.ObjInfo.UserDefined, xhttp.AmzServerSideEncryption)
} }
if !rngInfo.Empty() { if !rngInfo.Empty() {
// overlay Size with actual object size and not the range size // overlay Size with actual object size and not the range size

View File

@ -386,13 +386,13 @@ func DecryptBlocksRequestR(inputReader io.Reader, h http.Header, offset,
header: h, header: h,
bucket: bucket, bucket: bucket,
object: object, object: object,
customerKeyHeader: h.Get(crypto.SSECKey), customerKeyHeader: h.Get(xhttp.AmzServerSideEncryptionCustomerKey),
copySource: copySource, copySource: copySource,
metadata: cloneMSS(oi.UserDefined), metadata: cloneMSS(oi.UserDefined),
} }
if w.copySource { 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 { 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 var err error
if d.copySource { if d.copySource {
if crypto.SSEC.IsEncrypted(d.metadata) { 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) key, err = ParseSSECopyCustomerRequest(d.header, d.metadata)
} }
} else { } else {
if crypto.SSEC.IsEncrypted(d.metadata) { 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) key, err = ParseSSECustomerHeader(d.header)
} }
} }

View File

@ -29,6 +29,7 @@ import (
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"github.com/minio/minio-go/v7/pkg/encrypt" "github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/sio" "github.com/minio/sio"
) )
@ -38,20 +39,20 @@ var encryptRequestTests = []struct {
}{ }{
{ {
header: map[string]string{ header: map[string]string{
crypto.SSECAlgorithm: "AES256", xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
}, },
metadata: map[string]string{}, metadata: map[string]string{},
}, },
{ {
header: map[string]string{ header: map[string]string{
crypto.SSECAlgorithm: "AES256", xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
crypto.SSECKeyMD5: "bY4wkxQejw9mUJfo72k53A==", xhttp.AmzServerSideEncryptionCustomerKeyMD5: "bY4wkxQejw9mUJfo72k53A==",
}, },
metadata: map[string]string{ metadata: map[string]string{
crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=", xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
}, },
}, },
} }
@ -70,13 +71,13 @@ func TestEncryptRequest(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) 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) 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) 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) t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac)
} }
} }
@ -93,33 +94,33 @@ var decryptObjectInfoTests = []struct {
expErr: nil, expErr: nil,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
expErr: nil, expErr: nil,
}, },
{ {
info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
expErr: nil, 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{}}, request: &http.Request{Header: http.Header{}},
expErr: errEncryptedObject, expErr: errEncryptedObject,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}}, 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, expErr: errInvalidEncryptionParameters,
}, },
{ {
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}}, 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, expErr: errInvalidEncryptionParameters,
}, },
{ {
info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}}, info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}}, request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
expErr: errObjectTampered, expErr: errObjectTampered,
}, },
} }
@ -221,10 +222,10 @@ func TestGetDecryptedRange_Issue50(t *testing.T) {
Name: "object", Name: "object",
Size: 595160760, Size: 595160760,
UserDefined: map[string]string{ UserDefined: map[string]string{
crypto.SSEMultipart: "", crypto.MetaMultipart: "",
crypto.SSEIV: "HTexa=", crypto.MetaIV: "HTexa=",
crypto.SSESealAlgorithm: "DAREv2-HMAC-SHA256", crypto.MetaAlgorithm: "DAREv2-HMAC-SHA256",
crypto.SSECSealedKey: "IAA8PGAA==", crypto.MetaSealedKeySSEC: "IAA8PGAA==",
ReservedMetadataPrefix + "actual-size": "594870264", ReservedMetadataPrefix + "actual-size": "594870264",
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "166b1545b4c1535294ee0686678bea8c-2", "etag": "166b1545b4c1535294ee0686678bea8c-2",
@ -276,11 +277,11 @@ func TestGetDecryptedRange(t *testing.T) {
} }
udMap = func(isMulti bool) map[string]string { udMap = func(isMulti bool) map[string]string {
m := map[string]string{ m := map[string]string{
crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm, crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm,
crypto.SSEMultipart: "1", crypto.MetaMultipart: "1",
} }
if !isMulti { if !isMulti {
delete(m, crypto.SSEMultipart) delete(m, crypto.MetaMultipart)
} }
return m return m
} }
@ -553,56 +554,56 @@ var getDefaultOptsTests = []struct {
encryptionType encrypt.Type encryptionType encrypt.Type
err error err error
}{ }{
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: encrypt.SSEC, encryptionType: encrypt.SSEC,
err: nil}, // 0 err: nil}, // 0
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
crypto.SSECKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: true, copySource: true,
metadata: nil, metadata: nil,
encryptionType: "", encryptionType: "",
err: nil}, // 1 err: nil}, // 1
{headers: http.Header{crypto.SSECAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
crypto.SSECKey: []string{"Mz"}, xhttp.AmzServerSideEncryptionCustomerKey: []string{"Mz"},
crypto.SSECKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: "", encryptionType: "",
err: crypto.ErrInvalidCustomerKey}, // 2 err: crypto.ErrInvalidCustomerKey}, // 2
{headers: http.Header{crypto.SSEHeader: []string{"AES256"}}, {headers: http.Header{xhttp.AmzServerSideEncryption: []string{"AES256"}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: encrypt.S3, encryptionType: encrypt.S3,
err: nil}, // 3 err: nil}, // 3
{headers: http.Header{}, {headers: http.Header{},
copySource: false, copySource: false,
metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
crypto.S3KMSKeyID: "kms-key", crypto.MetaKeyID: "kms-key",
crypto.S3KMSSealedKey: "m-key"}, crypto.MetaDataEncryptionKey: "m-key"},
encryptionType: encrypt.S3, encryptionType: encrypt.S3,
err: nil}, // 4 err: nil}, // 4
{headers: http.Header{}, {headers: http.Header{},
copySource: true, copySource: true,
metadata: map[string]string{crypto.S3SealedKey: base64.StdEncoding.EncodeToString(make([]byte, 64)), metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
crypto.S3KMSKeyID: "kms-key", crypto.MetaKeyID: "kms-key",
crypto.S3KMSSealedKey: "m-key"}, crypto.MetaDataEncryptionKey: "m-key"},
encryptionType: "", encryptionType: "",
err: nil}, // 5 err: nil}, // 5
{headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: true, copySource: true,
metadata: nil, metadata: nil,
encryptionType: encrypt.SSEC, encryptionType: encrypt.SSEC,
err: nil}, // 6 err: nil}, // 6
{headers: http.Header{crypto.SSECopyAlgorithm: []string{"AES256"}, {headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
crypto.SSECopyKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="}, xhttp.AmzServerSideEncryptionCopyCustomerKey: []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
crypto.SSECopyKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}}, xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
copySource: false, copySource: false,
metadata: nil, metadata: nil,
encryptionType: "", encryptionType: "",

View File

@ -24,6 +24,7 @@ import (
"testing" "testing"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
) )
// Tests getRedirectLocation function for all its criteria. // 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{"X-Minio-Key": []string{"value"}},
}, },
{ {
header: http.Header{crypto.SSEIV: []string{"iv"}}, header: http.Header{crypto.MetaIV: []string{"iv"}},
shouldFail: true, shouldFail: true,
}, },
{ {
header: http.Header{crypto.SSESealAlgorithm: []string{crypto.InsecureSealAlgorithm}}, header: http.Header{crypto.MetaAlgorithm: []string{crypto.InsecureSealAlgorithm}},
shouldFail: true, shouldFail: true,
}, },
{ {
header: http.Header{crypto.SSECSealedKey: []string{"mac"}}, header: http.Header{crypto.MetaSealedKeySSEC: []string{"mac"}},
shouldFail: true, shouldFail: true,
}, },
{ {
@ -217,11 +218,11 @@ var sseTLSHandlerTests = []struct {
Header http.Header Header http.Header
IsTLS, ShouldFail bool IsTLS, ShouldFail bool
}{ }{
{URL: &url.URL{}, Header: http.Header{}, IsTLS: false, ShouldFail: false}, // 0 {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{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []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{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []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{xhttp.AmzServerSideEncryptionCustomerKey: []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{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{""}}, IsTLS: false, ShouldFail: true}, // 4
} }
func TestSSETLSHandler(t *testing.T) { func TestSSETLSHandler(t *testing.T) {

View File

@ -111,6 +111,20 @@ const (
AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length" AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length"
AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5" 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 // Signature v2 related constants
AmzSignatureV2 = "Signature" AmzSignatureV2 = "Signature"
AmzAccessKeyID = "AWSAccessKeyId" AmzAccessKeyID = "AWSAccessKeyId"

View File

@ -439,7 +439,9 @@ func isCompressible(header http.Header, object string) bool {
globalCompressConfigMu.Lock() globalCompressConfigMu.Lock()
cfg := globalCompressConfig cfg := globalCompressConfig
globalCompressConfigMu.Unlock() 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 false
} }
return true return true
@ -628,7 +630,7 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, opts ObjectOptions, cl
// encrypted bytes. The header parameter is used to // encrypted bytes. The header parameter is used to
// provide encryption parameters. // provide encryption parameters.
fn = func(inputReader io.Reader, h http.Header, pcfn CheckPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) { 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...) cFns = append(cleanUpFns, cFns...)
// Attach decrypter on inputReader // Attach decrypter on inputReader

View File

@ -347,7 +347,7 @@ func TestIsCompressed(t *testing.T) {
UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV2, UserDefined: map[string]string{"X-Minio-Internal-compression": compressionAlgorithmV2,
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"etag": "b3ff3ef3789147152fbfbc50efba4bfd-2", "etag": "b3ff3ef3789147152fbfbc50efba4bfd-2",
crypto.SSEIV: "yes", crypto.MetaIV: "yes",
}, },
}, },
result: true, result: true,

View File

@ -108,7 +108,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@ -267,15 +267,15 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
if crypto.IsEncrypted(objInfo.UserDefined) { if crypto.IsEncrypted(objInfo.UserDefined) {
switch { switch {
case crypto.S3.IsEncrypted(objInfo.UserDefined): 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): case crypto.SSEC.IsEncrypted(objInfo.UserDefined):
// Validate the SSE-C Key set in the header. // Validate the SSE-C Key set in the header.
if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@ -446,10 +446,10 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
if crypto.IsEncrypted(objInfo.UserDefined) { if crypto.IsEncrypted(objInfo.UserDefined) {
switch { switch {
case crypto.S3.IsEncrypted(objInfo.UserDefined): 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): case crypto.SSEC.IsEncrypted(objInfo.UserDefined):
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) 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)) writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrBadRequest))
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@ -640,15 +640,15 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
if crypto.IsEncrypted(objInfo.UserDefined) { if crypto.IsEncrypted(objInfo.UserDefined) {
switch { switch {
case crypto.S3.IsEncrypted(objInfo.UserDefined): 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): case crypto.SSEC.IsEncrypted(objInfo.UserDefined):
// Validate the SSE-C Key set in the header. // Validate the SSE-C Key set in the header.
if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil {
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
return return
} }
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) 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 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@ -884,7 +884,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
_, err = globalBucketSSEConfigSys.Get(dstBucket) _, err = globalBucketSSEConfigSys.Get(dstBucket)
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
} }
var srcOpts, dstOpts ObjectOptions var srcOpts, dstOpts ObjectOptions
@ -1030,7 +1030,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
pReader := NewPutObjReader(srcInfo.Reader, nil, nil) pReader := NewPutObjReader(srcInfo.Reader, nil, nil)
// Check if either the source is encrypted or the destination will be encrypted. // 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) var encMetadata = make(map[string]string)
if objectAPI.IsEncryptionSupported() && !isCompressed { if objectAPI.IsEncryptionSupported() && !isCompressed {
// Encryption parameters not applicable for this object. // Encryption parameters not applicable for this object.
@ -1321,7 +1322,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@ -1451,7 +1452,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
_, err = globalBucketSSEConfigSys.Get(bucket) _, err = globalBucketSSEConfigSys.Get(bucket)
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
} }
actualSize := size actualSize := size
@ -1528,7 +1529,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
} }
var objectEncryptionKey crypto.ObjectKey var objectEncryptionKey crypto.ObjectKey
if objectAPI.IsEncryptionSupported() { 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) { if crypto.SSECopy.IsRequested(r.Header) {
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r))
return return
@ -1569,11 +1570,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
case crypto.IsEncrypted(objInfo.UserDefined): case crypto.IsEncrypted(objInfo.UserDefined):
switch { switch {
case crypto.S3.IsEncrypted(objInfo.UserDefined): 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}) objInfo.ETag, _ = DecryptETag(objectEncryptionKey, ObjectInfo{ETag: objInfo.ETag})
case crypto.SSEC.IsEncrypted(objInfo.UserDefined): case crypto.SSEC.IsEncrypted(objInfo.UserDefined):
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
if len(objInfo.ETag) >= 32 && strings.Count(objInfo.ETag, "-") != 1 { if len(objInfo.ETag) >= 32 && strings.Count(objInfo.ETag, "-") != 1 {
objInfo.ETag = objInfo.ETag[len(objInfo.ETag)-32:] objInfo.ETag = objInfo.ETag[len(objInfo.ETag)-32:]
@ -1623,7 +1624,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@ -1645,7 +1646,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
_, err = globalBucketSSEConfigSys.Get(bucket) _, err = globalBucketSSEConfigSys.Get(bucket)
// This request header needs to be set prior to setting ObjectOptions // This request header needs to be set prior to setting ObjectOptions
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
} }
// Validate storage class metadata if present // Validate storage class metadata if present
@ -1659,7 +1660,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
var encMetadata = map[string]string{} var encMetadata = map[string]string{}
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
if crypto.IsRequested(r.Header) { if _, ok := crypto.IsRequested(r.Header); ok {
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil { if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
@ -1751,7 +1752,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@ -2067,7 +2068,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
return 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)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }

View File

@ -37,7 +37,6 @@ import (
"testing" "testing"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
ioutilx "github.com/minio/minio/pkg/ioutil" ioutilx "github.com/minio/minio/pkg/ioutil"
@ -221,9 +220,9 @@ func testAPIHeadObjectHandlerWithEncryption(obj ObjectLayer, instanceType, bucke
key32Bytes = generateBytesData(32 * humanize.Byte) key32Bytes = generateBytesData(32 * humanize.Byte)
key32BytesMd5 = md5.Sum(key32Bytes) key32BytesMd5 = md5.Sum(key32Bytes)
metaWithSSEC = map[string]string{ metaWithSSEC = map[string]string{
crypto.SSECAlgorithm: crypto.SSEAlgorithmAES256, xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES,
crypto.SSECKey: base64.StdEncoding.EncodeToString(key32Bytes), xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(key32Bytes),
crypto.SSECKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]),
} }
mapCopy = func(m map[string]string) map[string]string { mapCopy = func(m map[string]string) map[string]string {
r := make(map[string]string, len(m)) r := make(map[string]string, len(m))
@ -667,9 +666,9 @@ func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName str
key32Bytes = generateBytesData(32 * humanize.Byte) key32Bytes = generateBytesData(32 * humanize.Byte)
key32BytesMd5 = md5.Sum(key32Bytes) key32BytesMd5 = md5.Sum(key32Bytes)
metaWithSSEC = map[string]string{ metaWithSSEC = map[string]string{
crypto.SSECAlgorithm: crypto.SSEAlgorithmAES256, xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES,
crypto.SSECKey: base64.StdEncoding.EncodeToString(key32Bytes), xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(key32Bytes),
crypto.SSECKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]),
} }
mapCopy = func(m map[string]string) map[string]string { mapCopy = func(m map[string]string) map[string]string {
r := make(map[string]string, len(m)) r := make(map[string]string, len(m))
@ -865,9 +864,9 @@ func testAPIGetObjectWithPartNumberHandler(obj ObjectLayer, instanceType, bucket
key32Bytes = generateBytesData(32 * humanize.Byte) key32Bytes = generateBytesData(32 * humanize.Byte)
key32BytesMd5 = md5.Sum(key32Bytes) key32BytesMd5 = md5.Sum(key32Bytes)
metaWithSSEC = map[string]string{ metaWithSSEC = map[string]string{
crypto.SSECAlgorithm: crypto.SSEAlgorithmAES256, xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES,
crypto.SSECKey: base64.StdEncoding.EncodeToString(key32Bytes), xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(key32Bytes),
crypto.SSECKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]), xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(key32BytesMd5[:]),
} }
mapCopy = func(m map[string]string) map[string]string { mapCopy = func(m map[string]string) map[string]string {
r := make(map[string]string, len(m)) r := make(map[string]string, len(m))

View File

@ -20,7 +20,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http"
) )
// TestSkipContentSha256Cksum - Test validate the logic which decides whether // 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) t.Fatalf("Expected the APIErrorCode to %d, but got %d", ErrUnsignedHeaders, errCode)
} }
// set headers value through Get parameter // 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() r.URL.RawQuery = inputQuery.Encode()
_, errCode = extractSignedHeaders(signedHeaders, r) _, errCode = extractSignedHeaders(signedHeaders, r)
if errCode != ErrNone { if errCode != ErrNone {

View File

@ -1171,7 +1171,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
// Check if bucket encryption is enabled // Check if bucket encryption is enabled
_, err = globalBucketSSEConfigSys.Get(bucket) _, err = globalBucketSSEConfigSys.Get(bucket)
if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) {
r.Header.Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
} }
// Require Content-Length to be set in the request // 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 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 rawReader := hashReader
var objectEncryptionKey crypto.ObjectKey var objectEncryptionKey crypto.ObjectKey
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata) 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) { if crypto.IsEncrypted(objInfo.UserDefined) {
switch { switch {
case crypto.S3.IsEncrypted(objInfo.UserDefined): 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): case crypto.SSEC.IsRequested(r.Header):
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) 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) { if crypto.IsEncrypted(objInfo.UserDefined) {
switch { switch {
case crypto.S3.IsEncrypted(objInfo.UserDefined): 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): case crypto.SSEC.IsEncrypted(objInfo.UserDefined):
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
} }
} }
} }