mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 10:11:09 +01:00 
			
		
		
		
	To conform with AWS S3 Spec on ETag for SSE-S3 encrypted objects, encrypt client sent MD5Sum and store it on backend as ETag.Extend this behavior to SSE-C encrypted objects.
		
			
				
	
	
		
			678 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			678 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * Minio Cloud Storage, (C) 2017, 2018 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 cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"net/http"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	humanize "github.com/dustin/go-humanize"
 | 
						|
	"github.com/minio/minio/cmd/crypto"
 | 
						|
	"github.com/minio/sio"
 | 
						|
)
 | 
						|
 | 
						|
var hasServerSideEncryptionHeaderTests = []struct {
 | 
						|
	headers    map[string]string
 | 
						|
	sseRequest bool
 | 
						|
}{
 | 
						|
	{headers: map[string]string{crypto.SSECAlgorithm: "AES256", crypto.SSECKey: "key", crypto.SSECKeyMD5: "md5"}, sseRequest: true},                             // 0
 | 
						|
	{headers: map[string]string{crypto.SSECAlgorithm: "AES256"}, sseRequest: true},                                                                              // 1
 | 
						|
	{headers: map[string]string{crypto.SSECKey: "key"}, sseRequest: true},                                                                                       // 2
 | 
						|
	{headers: map[string]string{crypto.SSECKeyMD5: "md5"}, sseRequest: true},                                                                                    // 3
 | 
						|
	{headers: map[string]string{}, sseRequest: false},                                                                                                           // 4
 | 
						|
	{headers: map[string]string{crypto.SSECopyAlgorithm + " ": "AES256", " " + crypto.SSECopyKey: "key", crypto.SSECopyKeyMD5 + " ": "md5"}, sseRequest: false}, // 5
 | 
						|
	{headers: map[string]string{crypto.SSECopyAlgorithm: "", crypto.SSECopyKey: "", crypto.SSECopyKeyMD5: ""}, sseRequest: false},                               // 6
 | 
						|
	{headers: map[string]string{crypto.SSEHeader: ""}, sseRequest: true},                                                                                        // 7
 | 
						|
}
 | 
						|
 | 
						|
func TestHasServerSideEncryptionHeader(t *testing.T) {
 | 
						|
	for i, test := range hasServerSideEncryptionHeaderTests {
 | 
						|
		headers := http.Header{}
 | 
						|
		for k, v := range test.headers {
 | 
						|
			headers.Set(k, v)
 | 
						|
		}
 | 
						|
		if hasServerSideEncryptionHeader(headers) != test.sseRequest {
 | 
						|
			t.Errorf("Test %d: Expected hasServerSideEncryptionHeader to return %v", i, test.sseRequest)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var hasSSECopyCustomerHeaderTests = []struct {
 | 
						|
	headers    map[string]string
 | 
						|
	sseRequest bool
 | 
						|
}{
 | 
						|
	{headers: map[string]string{crypto.SSECopyAlgorithm: "AES256", crypto.SSECopyKey: "key", crypto.SSECopyKeyMD5: "md5"}, sseRequest: true},                    // 0
 | 
						|
	{headers: map[string]string{crypto.SSECopyAlgorithm: "AES256"}, sseRequest: true},                                                                           // 1
 | 
						|
	{headers: map[string]string{crypto.SSECopyKey: "key"}, sseRequest: true},                                                                                    // 2
 | 
						|
	{headers: map[string]string{crypto.SSECopyKeyMD5: "md5"}, sseRequest: true},                                                                                 // 3
 | 
						|
	{headers: map[string]string{}, sseRequest: false},                                                                                                           // 4
 | 
						|
	{headers: map[string]string{crypto.SSECopyAlgorithm + " ": "AES256", " " + crypto.SSECopyKey: "key", crypto.SSECopyKeyMD5 + " ": "md5"}, sseRequest: false}, // 5
 | 
						|
	{headers: map[string]string{crypto.SSECopyAlgorithm: "", crypto.SSECopyKey: "", crypto.SSECopyKeyMD5: ""}, sseRequest: true},                                // 6
 | 
						|
	{headers: map[string]string{crypto.SSEHeader: ""}, sseRequest: false},                                                                                       // 7
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func TestIsSSECopyCustomerRequest(t *testing.T) {
 | 
						|
	for i, test := range hasSSECopyCustomerHeaderTests {
 | 
						|
		headers := http.Header{}
 | 
						|
		for k, v := range test.headers {
 | 
						|
			headers.Set(k, v)
 | 
						|
		}
 | 
						|
		if crypto.SSECopy.IsRequested(headers) != test.sseRequest {
 | 
						|
			t.Errorf("Test %d: Expected crypto.SSECopy.IsRequested to return %v", i, test.sseRequest)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var hasSSECustomerHeaderTests = []struct {
 | 
						|
	headers    map[string]string
 | 
						|
	sseRequest bool
 | 
						|
}{
 | 
						|
	{headers: map[string]string{crypto.SSECAlgorithm: "AES256", crypto.SSECKey: "key", crypto.SSECKeyMD5: "md5"}, sseRequest: true},                    // 0
 | 
						|
	{headers: map[string]string{crypto.SSECAlgorithm: "AES256"}, sseRequest: true},                                                                     // 1
 | 
						|
	{headers: map[string]string{crypto.SSECKey: "key"}, sseRequest: true},                                                                              // 2
 | 
						|
	{headers: map[string]string{crypto.SSECKeyMD5: "md5"}, sseRequest: true},                                                                           // 3
 | 
						|
	{headers: map[string]string{}, sseRequest: false},                                                                                                  // 4
 | 
						|
	{headers: map[string]string{crypto.SSECAlgorithm + " ": "AES256", " " + crypto.SSECKey: "key", crypto.SSECKeyMD5 + " ": "md5"}, sseRequest: false}, // 5
 | 
						|
	{headers: map[string]string{crypto.SSECAlgorithm: "", crypto.SSECKey: "", crypto.SSECKeyMD5: ""}, sseRequest: true},                                // 6
 | 
						|
	{headers: map[string]string{crypto.SSEHeader: ""}, sseRequest: false},                                                                              // 7
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func TestHasSSECustomerHeader(t *testing.T) {
 | 
						|
	for i, test := range hasSSECustomerHeaderTests {
 | 
						|
		headers := http.Header{}
 | 
						|
		for k, v := range test.headers {
 | 
						|
			headers.Set(k, v)
 | 
						|
		}
 | 
						|
		if crypto.SSEC.IsRequested(headers) != test.sseRequest {
 | 
						|
			t.Errorf("Test %d: Expected hasSSECustomerHeader to return %v", i, test.sseRequest)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var encryptRequestTests = []struct {
 | 
						|
	header   map[string]string
 | 
						|
	metadata map[string]string
 | 
						|
}{
 | 
						|
	{
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECKeyMD5:    "bY4wkxQejw9mUJfo72k53A==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{},
 | 
						|
	},
 | 
						|
	{
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECKeyMD5:    "bY4wkxQejw9mUJfo72k53A==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{
 | 
						|
			crypto.SSECKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
		},
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func TestEncryptRequest(t *testing.T) {
 | 
						|
	defer func(flag bool) { globalIsSSL = flag }(globalIsSSL)
 | 
						|
	globalIsSSL = true
 | 
						|
	for i, test := range encryptRequestTests {
 | 
						|
		content := bytes.NewReader(make([]byte, 64))
 | 
						|
		req := &http.Request{Header: http.Header{}}
 | 
						|
		for k, v := range test.header {
 | 
						|
			req.Header.Set(k, v)
 | 
						|
		}
 | 
						|
		_, _, err := EncryptRequest(content, req, "bucket", "object", test.metadata)
 | 
						|
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Test %d: Failed to encrypt request: %v", i, err)
 | 
						|
		}
 | 
						|
		if kdf, ok := test.metadata[crypto.SSESealAlgorithm]; !ok {
 | 
						|
			t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf)
 | 
						|
		}
 | 
						|
		if iv, ok := test.metadata[crypto.SSEIV]; !ok {
 | 
						|
			t.Errorf("Test %d: crypto.SSEIV must be part of metadata: %v", i, iv)
 | 
						|
		}
 | 
						|
		if mac, ok := test.metadata[crypto.SSECSealedKey]; !ok {
 | 
						|
			t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var decryptRequestTests = []struct {
 | 
						|
	bucket, object string
 | 
						|
	header         map[string]string
 | 
						|
	metadata       map[string]string
 | 
						|
	shouldFail     bool
 | 
						|
}{
 | 
						|
	{
 | 
						|
		bucket: "bucket",
 | 
						|
		object: "object",
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=",
 | 
						|
			crypto.SSECKeyMD5:    "7PpPLAK26ONlVUGOWlusfg==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{
 | 
						|
			crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256,
 | 
						|
			crypto.SSEIV:            "7nQqotA8xgrPx6QK7Ap3GCfjKitqJSrGP7xzgErSJlw=",
 | 
						|
			crypto.SSECSealedKey:    "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA==",
 | 
						|
		},
 | 
						|
		shouldFail: false,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		bucket: "bucket",
 | 
						|
		object: "object",
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=",
 | 
						|
			crypto.SSECKeyMD5:    "7PpPLAK26ONlVUGOWlusfg==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{
 | 
						|
			crypto.SSESealAlgorithm: SSESealAlgorithmDareV2HmacSha256,
 | 
						|
			crypto.SSEIV:            "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=",
 | 
						|
			crypto.SSECSealedKey:    "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==",
 | 
						|
		},
 | 
						|
		shouldFail: false,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		bucket: "bucket",
 | 
						|
		object: "object",
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECKeyMD5:    "bY4wkxQejw9mUJfo72k53A==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{
 | 
						|
			crypto.SSESealAlgorithm: "HMAC-SHA3",
 | 
						|
			crypto.SSEIV:            "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECSealedKey:    "SY5E9AvI2tI7/nUrUAssIGE32Hcs4rR9z/CUuPqu5N4=",
 | 
						|
		},
 | 
						|
		shouldFail: true,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		bucket: "bucket",
 | 
						|
		object: "object",
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECKeyMD5:    "bY4wkxQejw9mUJfo72k53A==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{
 | 
						|
			crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256,
 | 
						|
			crypto.SSEIV:            "RrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECSealedKey:    "SY5E9AvI2tI7/nUrUAssIGE32Hcs4rR9z/CUuPqu5N4=",
 | 
						|
		},
 | 
						|
		shouldFail: true,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		bucket: "bucket",
 | 
						|
		object: "object",
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECKeyMD5:    "bY4wkxQejw9mUJfo72k53A==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{
 | 
						|
			crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256,
 | 
						|
			crypto.SSEIV:            "XAm0dRrJsEsyPb1UuFNezv1bl9ehxuYsgUVC/MUctE2k=",
 | 
						|
			crypto.SSECSealedKey:    "SY5E9AvI2tI7/nUrUAssIGE32Hds4rR9z/CUuPqu5N4=",
 | 
						|
		},
 | 
						|
		shouldFail: true,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		bucket: "bucket",
 | 
						|
		object: "object-2",
 | 
						|
		header: map[string]string{
 | 
						|
			crypto.SSECAlgorithm: "AES256",
 | 
						|
			crypto.SSECKey:       "MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ=",
 | 
						|
			crypto.SSECKeyMD5:    "7PpPLAK26ONlVUGOWlusfg==",
 | 
						|
		},
 | 
						|
		metadata: map[string]string{
 | 
						|
			crypto.SSESealAlgorithm: SSESealAlgorithmDareV2HmacSha256,
 | 
						|
			crypto.SSEIV:            "qEqmsONcorqlcZXJxaw32H04eyXyXwUgjHzlhkaIYrU=",
 | 
						|
			crypto.SSECSealedKey:    "IAAfAIM14ugTGcM/dIrn4iQMrkl1sjKyeBQ8FBEvRebYj8vWvxG+0cJRpC6NXRU1wJN50JaUOATjO7kz0wZ2mA==",
 | 
						|
		},
 | 
						|
		shouldFail: true,
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func TestDecryptRequest(t *testing.T) {
 | 
						|
	defer func(flag bool) { globalIsSSL = flag }(globalIsSSL)
 | 
						|
	globalIsSSL = true
 | 
						|
	for i, test := range decryptRequestTests[1:] {
 | 
						|
		client := bytes.NewBuffer(nil)
 | 
						|
		req := &http.Request{Header: http.Header{}}
 | 
						|
		for k, v := range test.header {
 | 
						|
			req.Header.Set(k, v)
 | 
						|
		}
 | 
						|
		_, err := DecryptRequest(client, req, test.bucket, test.object, test.metadata)
 | 
						|
		if err != nil && !test.shouldFail {
 | 
						|
			t.Fatalf("Test %d: Failed to encrypt request: %v", i, err)
 | 
						|
		}
 | 
						|
		if err == nil && test.shouldFail {
 | 
						|
			t.Fatalf("Test %d: should fail but passed", i)
 | 
						|
		}
 | 
						|
		if key, ok := test.metadata[crypto.SSECKey]; ok {
 | 
						|
			t.Errorf("Test %d: Client provided key survived in metadata - key: %s", i, key)
 | 
						|
		}
 | 
						|
		if kdf, ok := test.metadata[crypto.SSESealAlgorithm]; ok && !test.shouldFail {
 | 
						|
			t.Errorf("Test %d: ServerSideEncryptionKDF should not be part of metadata: %v", i, kdf)
 | 
						|
		}
 | 
						|
		if iv, ok := test.metadata[crypto.SSEIV]; ok && !test.shouldFail {
 | 
						|
			t.Errorf("Test %d: crypto.SSEIV should not be part of metadata: %v", i, iv)
 | 
						|
		}
 | 
						|
		if mac, ok := test.metadata[crypto.SSECSealedKey]; ok && !test.shouldFail {
 | 
						|
			t.Errorf("Test %d: ServerSideEncryptionKeyMAC should not be part of metadata: %v", i, mac)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var decryptObjectInfoTests = []struct {
 | 
						|
	info    ObjectInfo
 | 
						|
	headers http.Header
 | 
						|
	expErr  error
 | 
						|
}{
 | 
						|
	{
 | 
						|
		info:    ObjectInfo{Size: 100},
 | 
						|
		headers: http.Header{},
 | 
						|
		expErr:  nil,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		info:    ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256}},
 | 
						|
		headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
 | 
						|
		expErr:  nil,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		info:    ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256}},
 | 
						|
		headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
 | 
						|
		expErr:  nil,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		info:    ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSECSealedKey: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}},
 | 
						|
		headers: http.Header{},
 | 
						|
		expErr:  errEncryptedObject,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		info:    ObjectInfo{Size: 100, UserDefined: map[string]string{}},
 | 
						|
		headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
 | 
						|
		expErr:  errInvalidEncryptionParameters,
 | 
						|
	},
 | 
						|
	{
 | 
						|
		info:    ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256}},
 | 
						|
		headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
 | 
						|
		expErr:  errObjectTampered,
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func TestDecryptObjectInfo(t *testing.T) {
 | 
						|
	for i, test := range decryptObjectInfoTests {
 | 
						|
		if encrypted, err := DecryptObjectInfo(&test.info, test.headers); err != test.expErr {
 | 
						|
			t.Errorf("Test %d: Decryption returned wrong error code: got %d , want %d", i, err, test.expErr)
 | 
						|
		} else if enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted {
 | 
						|
			t.Errorf("Test %d: Decryption thinks object is encrypted but it is not", i)
 | 
						|
		} else if !encrypted && enc != encrypted {
 | 
						|
			t.Errorf("Test %d: Decryption thinks object is not encrypted but it is", i)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Tests for issue reproduced when getting the right encrypted
 | 
						|
// offset of the object.
 | 
						|
func TestGetDecryptedRange_Issue50(t *testing.T) {
 | 
						|
	rs, err := parseRequestRangeSpec("bytes=594870256-594870263")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	objInfo := ObjectInfo{
 | 
						|
		Bucket: "bucket",
 | 
						|
		Name:   "object",
 | 
						|
		Size:   595160760,
 | 
						|
		UserDefined: map[string]string{
 | 
						|
			crypto.SSEMultipart:                    "",
 | 
						|
			crypto.SSEIV:                           "HTexa=",
 | 
						|
			crypto.SSESealAlgorithm:                "DAREv2-HMAC-SHA256",
 | 
						|
			crypto.SSECSealedKey:                   "IAA8PGAA==",
 | 
						|
			ReservedMetadataPrefix + "actual-size": "594870264",
 | 
						|
			"content-type":                         "application/octet-stream",
 | 
						|
			"etag":                                 "166b1545b4c1535294ee0686678bea8c-2",
 | 
						|
		},
 | 
						|
		Parts: []objectPartInfo{
 | 
						|
			{
 | 
						|
				Number:     1,
 | 
						|
				Name:       "part.1",
 | 
						|
				ETag:       "etag1",
 | 
						|
				Size:       297580380,
 | 
						|
				ActualSize: 297435132,
 | 
						|
			},
 | 
						|
			{
 | 
						|
				Number:     2,
 | 
						|
				Name:       "part.2",
 | 
						|
				ETag:       "etag2",
 | 
						|
				Size:       297580380,
 | 
						|
				ActualSize: 297435132,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	encOff, encLength, skipLen, seqNumber, partStart, err := objInfo.GetDecryptedRange(rs)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Test: failed %s", err)
 | 
						|
	}
 | 
						|
	if encOff != 595127964 {
 | 
						|
		t.Fatalf("Test: expected %d, got %d", 595127964, encOff)
 | 
						|
	}
 | 
						|
	if encLength != 32796 {
 | 
						|
		t.Fatalf("Test: expected %d, got %d", 32796, encLength)
 | 
						|
	}
 | 
						|
	if skipLen != 32756 {
 | 
						|
		t.Fatalf("Test: expected %d, got %d", 32756, skipLen)
 | 
						|
	}
 | 
						|
	if seqNumber != 4538 {
 | 
						|
		t.Fatalf("Test: expected %d, got %d", 4538, seqNumber)
 | 
						|
	}
 | 
						|
	if partStart != 1 {
 | 
						|
		t.Fatalf("Test: expected %d, got %d", 1, partStart)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestGetDecryptedRange(t *testing.T) {
 | 
						|
	var (
 | 
						|
		pkgSz     = int64(64) * humanize.KiByte
 | 
						|
		minPartSz = int64(5) * humanize.MiByte
 | 
						|
		maxPartSz = int64(5) * humanize.GiByte
 | 
						|
 | 
						|
		getEncSize = func(s int64) int64 {
 | 
						|
			v, _ := sio.EncryptedSize(uint64(s))
 | 
						|
			return int64(v)
 | 
						|
		}
 | 
						|
		udMap = func(isMulti bool) map[string]string {
 | 
						|
			m := map[string]string{
 | 
						|
				crypto.SSESealAlgorithm: SSESealAlgorithmDareSha256,
 | 
						|
				crypto.SSEMultipart:     "1",
 | 
						|
			}
 | 
						|
			if !isMulti {
 | 
						|
				delete(m, crypto.SSEMultipart)
 | 
						|
			}
 | 
						|
			return m
 | 
						|
		}
 | 
						|
	)
 | 
						|
 | 
						|
	// Single part object tests
 | 
						|
	var (
 | 
						|
		mkSPObj = func(s int64) ObjectInfo {
 | 
						|
			return ObjectInfo{
 | 
						|
				Size:        getEncSize(s),
 | 
						|
				UserDefined: udMap(false),
 | 
						|
			}
 | 
						|
		}
 | 
						|
	)
 | 
						|
 | 
						|
	testSP := []struct {
 | 
						|
		decSz int64
 | 
						|
		oi    ObjectInfo
 | 
						|
	}{
 | 
						|
		{0, mkSPObj(0)},
 | 
						|
		{1, mkSPObj(1)},
 | 
						|
		{pkgSz - 1, mkSPObj(pkgSz - 1)},
 | 
						|
		{pkgSz, mkSPObj(pkgSz)},
 | 
						|
		{2*pkgSz - 1, mkSPObj(2*pkgSz - 1)},
 | 
						|
		{minPartSz, mkSPObj(minPartSz)},
 | 
						|
		{maxPartSz, mkSPObj(maxPartSz)},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range testSP {
 | 
						|
		{
 | 
						|
			// nil range
 | 
						|
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(nil)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("Case %d: unexpected err: %v", i, err)
 | 
						|
			}
 | 
						|
			if skip != 0 || sn != 0 || ps != 0 || o != 0 || l != getEncSize(test.decSz) {
 | 
						|
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if test.decSz >= 10 {
 | 
						|
			// first 10 bytes
 | 
						|
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, 0, 9})
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("Case %d: unexpected err: %v", i, err)
 | 
						|
			}
 | 
						|
			var rLen = pkgSz + 32
 | 
						|
			if test.decSz < pkgSz {
 | 
						|
				rLen = test.decSz + 32
 | 
						|
			}
 | 
						|
			if skip != 0 || sn != 0 || ps != 0 || o != 0 || l != rLen {
 | 
						|
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		kb32 := int64(32) * humanize.KiByte
 | 
						|
		if test.decSz >= (64+32)*humanize.KiByte {
 | 
						|
			// Skip the first 32Kib, and read the next 64Kib
 | 
						|
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, kb32, 3*kb32 - 1})
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("Case %d: unexpected err: %v", i, err)
 | 
						|
			}
 | 
						|
			var rLen = (pkgSz + 32) * 2
 | 
						|
			if test.decSz < 2*pkgSz {
 | 
						|
				rLen = (pkgSz + 32) + (test.decSz - pkgSz + 32)
 | 
						|
			}
 | 
						|
			if skip != kb32 || sn != 0 || ps != 0 || o != 0 || l != rLen {
 | 
						|
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if test.decSz >= (64*2+32)*humanize.KiByte {
 | 
						|
			// Skip the first 96Kib and read the next 64Kib
 | 
						|
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, 3 * kb32, 5*kb32 - 1})
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("Case %d: unexpected err: %v", i, err)
 | 
						|
			}
 | 
						|
			var rLen = (pkgSz + 32) * 2
 | 
						|
			if test.decSz-pkgSz < 2*pkgSz {
 | 
						|
				rLen = (pkgSz + 32) + (test.decSz - pkgSz + 32*2)
 | 
						|
			}
 | 
						|
			if skip != kb32 || sn != 1 || ps != 0 || o != pkgSz+32 || l != rLen {
 | 
						|
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	// Multipart object tests
 | 
						|
	var (
 | 
						|
		// make a multipart object-info given part sizes
 | 
						|
		mkMPObj = func(sizes []int64) ObjectInfo {
 | 
						|
			r := make([]objectPartInfo, len(sizes))
 | 
						|
			sum := int64(0)
 | 
						|
			for i, s := range sizes {
 | 
						|
				r[i].Number = i
 | 
						|
				r[i].Size = int64(getEncSize(s))
 | 
						|
				sum += r[i].Size
 | 
						|
			}
 | 
						|
			return ObjectInfo{
 | 
						|
				Size:        sum,
 | 
						|
				UserDefined: udMap(true),
 | 
						|
				Parts:       r,
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// Simple useful utilities
 | 
						|
		repeat = func(k int64, n int) []int64 {
 | 
						|
			a := []int64{}
 | 
						|
			for i := 0; i < n; i++ {
 | 
						|
				a = append(a, k)
 | 
						|
			}
 | 
						|
			return a
 | 
						|
		}
 | 
						|
		lsum = func(s []int64) int64 {
 | 
						|
			sum := int64(0)
 | 
						|
			for _, i := range s {
 | 
						|
				if i < 0 {
 | 
						|
					return -1
 | 
						|
				}
 | 
						|
				sum += i
 | 
						|
			}
 | 
						|
			return sum
 | 
						|
		}
 | 
						|
		esum = func(oi ObjectInfo) int64 {
 | 
						|
			sum := int64(0)
 | 
						|
			for _, i := range oi.Parts {
 | 
						|
				sum += i.Size
 | 
						|
			}
 | 
						|
			return sum
 | 
						|
		}
 | 
						|
	)
 | 
						|
 | 
						|
	s1 := []int64{5487701, 5487799, 3}
 | 
						|
	s2 := repeat(5487701, 5)
 | 
						|
	s3 := repeat(maxPartSz, 10000)
 | 
						|
	testMPs := []struct {
 | 
						|
		decSizes []int64
 | 
						|
		oi       ObjectInfo
 | 
						|
	}{
 | 
						|
		{s1, mkMPObj(s1)},
 | 
						|
		{s2, mkMPObj(s2)},
 | 
						|
		{s3, mkMPObj(s3)},
 | 
						|
	}
 | 
						|
 | 
						|
	// This function is a reference (re-)implementation of
 | 
						|
	// decrypted range computation, written solely for the purpose
 | 
						|
	// of the unit tests.
 | 
						|
	//
 | 
						|
	// `s` gives the decrypted part sizes, and the other
 | 
						|
	// parameters describe the desired read segment. When
 | 
						|
	// `isFromEnd` is true, `skipLen` argument is ignored.
 | 
						|
	decryptedRangeRef := func(s []int64, skipLen, readLen int64, isFromEnd bool) (o, l, skip int64, sn uint32, ps int) {
 | 
						|
		oSize := lsum(s)
 | 
						|
		if isFromEnd {
 | 
						|
			skipLen = oSize - readLen
 | 
						|
		}
 | 
						|
		if skipLen < 0 || readLen < 0 || oSize < 0 || skipLen+readLen > oSize {
 | 
						|
			t.Fatalf("Impossible read specified: %d %d %d", skipLen, readLen, oSize)
 | 
						|
		}
 | 
						|
 | 
						|
		var cumulativeSum, cumulativeEncSum int64
 | 
						|
		toRead := readLen
 | 
						|
		readStart := false
 | 
						|
		for i, v := range s {
 | 
						|
			partOffset := int64(0)
 | 
						|
			partDarePkgOffset := int64(0)
 | 
						|
			if !readStart && cumulativeSum+v > skipLen {
 | 
						|
				// Read starts at the current part
 | 
						|
				readStart = true
 | 
						|
 | 
						|
				partOffset = skipLen - cumulativeSum
 | 
						|
 | 
						|
				// All return values except `l` are
 | 
						|
				// calculated here.
 | 
						|
				sn = uint32(partOffset / pkgSz)
 | 
						|
				skip = partOffset % pkgSz
 | 
						|
				ps = i
 | 
						|
				o = cumulativeEncSum + int64(sn)*(pkgSz+32)
 | 
						|
 | 
						|
				partDarePkgOffset = partOffset - skip
 | 
						|
			}
 | 
						|
			if readStart {
 | 
						|
				currentPartBytes := v - partOffset
 | 
						|
				currentPartDareBytes := v - partDarePkgOffset
 | 
						|
				if currentPartBytes < toRead {
 | 
						|
					toRead -= currentPartBytes
 | 
						|
					l += getEncSize(currentPartDareBytes)
 | 
						|
				} else {
 | 
						|
					// current part has the last
 | 
						|
					// byte required
 | 
						|
					lbPartOffset := partOffset + toRead - 1
 | 
						|
 | 
						|
					// round up the lbPartOffset
 | 
						|
					// to the end of the
 | 
						|
					// corresponding DARE package
 | 
						|
					lbPkgEndOffset := lbPartOffset - (lbPartOffset % pkgSz) + pkgSz
 | 
						|
					if lbPkgEndOffset > v {
 | 
						|
						lbPkgEndOffset = v
 | 
						|
					}
 | 
						|
					bytesToDrop := v - lbPkgEndOffset
 | 
						|
 | 
						|
					// Last segment to update `l`
 | 
						|
					l += getEncSize(currentPartDareBytes - bytesToDrop)
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			cumulativeSum += v
 | 
						|
			cumulativeEncSum += getEncSize(v)
 | 
						|
		}
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	for i, test := range testMPs {
 | 
						|
		{
 | 
						|
			// nil range
 | 
						|
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(nil)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("Case %d: unexpected err: %v", i, err)
 | 
						|
			}
 | 
						|
			if o != 0 || l != esum(test.oi) || skip != 0 || sn != 0 || ps != 0 {
 | 
						|
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Skip 1Mib and read 1Mib (in the decrypted object)
 | 
						|
		//
 | 
						|
		// The check below ensures the object is large enough
 | 
						|
		// for the read.
 | 
						|
		if lsum(test.decSizes) >= 2*humanize.MiByte {
 | 
						|
			skipLen, readLen := int64(1)*humanize.MiByte, int64(1)*humanize.MiByte
 | 
						|
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, skipLen, skipLen + readLen - 1})
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("Case %d: unexpected err: %v", i, err)
 | 
						|
			}
 | 
						|
 | 
						|
			oRef, lRef, skipRef, snRef, psRef := decryptedRangeRef(test.decSizes, skipLen, readLen, false)
 | 
						|
			if o != oRef || l != lRef || skip != skipRef || sn != snRef || ps != psRef {
 | 
						|
				t.Errorf("Case %d: test failed: %d %d %d %d %d (Ref: %d %d %d %d %d)",
 | 
						|
					i, o, l, skip, sn, ps, oRef, lRef, skipRef, snRef, psRef)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Read the last 6Mib+1 bytes of the (decrypted)
 | 
						|
		// object
 | 
						|
		//
 | 
						|
		// The check below ensures the object is large enough
 | 
						|
		// for the read.
 | 
						|
		readLen := int64(6)*humanize.MiByte + 1
 | 
						|
		if lsum(test.decSizes) >= readLen {
 | 
						|
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{true, -readLen, -1})
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("Case %d: unexpected err: %v", i, err)
 | 
						|
			}
 | 
						|
 | 
						|
			oRef, lRef, skipRef, snRef, psRef := decryptedRangeRef(test.decSizes, 0, readLen, true)
 | 
						|
			if o != oRef || l != lRef || skip != skipRef || sn != snRef || ps != psRef {
 | 
						|
				t.Errorf("Case %d: test failed: %d %d %d %d %d (Ref: %d %d %d %d %d)",
 | 
						|
					i, o, l, skip, sn, ps, oRef, lRef, skipRef, snRef, psRef)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
}
 |